mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(visibility): M6 soft-migrate isPublic→visibility on memoro/cards/presi/uload
Brings four legacy isPublic-only modules onto the unified visibility
system. The Picker (private/space/public) replaces the ad-hoc
boolean toggle in memoro + cards + presi DetailViews. Each store
now mirrors `visibility ↔ isPublic` on every flip so older readers
(search index, snapshot pipelines, sync server) keep working
through the soak window — M6.1 will hard-drop the legacy field
once the field has propagated to all rows.
Per-module:
- memoro: setVisibility on memosStore + Picker in DetailView
properties row. Picker reads the unified `visibility` with a
fallback to legacy isPublic.
- cards (decks): replaces the "Öffentlich Ja/Nein" toggle button
with the Picker. createDeck initializes both fields; updateDeck
mirrors when callers pass legacy isPublic.
- presi (decks): same pattern as cards. setVisibility added to the
store factory's exported surface.
- uload (tags): no active CRUD UI, so this is type-only soft-migrate
+ seed-data update. Future tag-management view writes visibility
directly. No store mutation method needed yet.
Out of scope (intentional):
- picture isPublic hard-drop: deferred. Picture has been on
visibility since M3 (commit 0e9f574df), the soak window is mature
enough to consider, but pre-launch there's no urgency. Defer to
M6.1 with the rest of the legacy-field cleanup.
- events isPublished/publicToken: STAYS. This isn't legacy — it's
the orthogonal mana-events RSVP-snapshot system. visibility
controls website-embed eligibility; isPublished controls RSVP
page existence. Different concerns; both stay.
Picker hides 'unlisted' for all four (no server-publish-snapshot
flow wired for these collections — same pattern as habits/quiz/events).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bd559e739e
commit
ad5987f1dd
15 changed files with 192 additions and 32 deletions
|
|
@ -18,7 +18,8 @@ export function toDeck(local: LocalDeck): Deck {
|
|||
title: local.name,
|
||||
description: local.description ?? undefined,
|
||||
color: local.color,
|
||||
isPublic: local.isPublic,
|
||||
isPublic: local.isPublic ?? local.visibility === 'public',
|
||||
visibility: local.visibility ?? (local.isPublic === true ? 'public' : 'space'),
|
||||
tags: [],
|
||||
cardCount: local.cardCount,
|
||||
createdAt: local.createdAt ?? new Date().toISOString(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import { db } from '$lib/data/database';
|
|||
import { cardDeckTable, cardTable } from '../collections';
|
||||
import { toDeck } from '../queries';
|
||||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getActiveSpace } from '$lib/data/scope';
|
||||
import { getEffectiveUserId } from '$lib/data/current-user';
|
||||
import { defaultVisibilityFor, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
import { createBlock, updateBlock } from '$lib/data/time-blocks/service';
|
||||
import type { LocalDeck } from '../types';
|
||||
import type { Deck, CreateDeckInput, UpdateDeckInput } from '../types';
|
||||
|
|
@ -24,13 +28,17 @@ export const deckStore = {
|
|||
async createDeck(input: CreateDeckInput): Promise<Deck | null> {
|
||||
error = null;
|
||||
try {
|
||||
const initialPublic = input.isPublic ?? false;
|
||||
const newLocal: LocalDeck = {
|
||||
id: crypto.randomUUID(),
|
||||
name: input.title,
|
||||
description: input.description ?? null,
|
||||
color: '#6366f1',
|
||||
cardCount: 0,
|
||||
isPublic: input.isPublic ?? false,
|
||||
isPublic: initialPublic,
|
||||
// Initialize the unified field too — if the create flow set
|
||||
// isPublic, mirror it as 'public'; otherwise space-default.
|
||||
visibility: initialPublic ? 'public' : defaultVisibilityFor(getActiveSpace()?.type),
|
||||
};
|
||||
|
||||
const plaintextSnapshot = toDeck(newLocal);
|
||||
|
|
@ -51,7 +59,12 @@ export const deckStore = {
|
|||
const localUpdates: Partial<LocalDeck> = {};
|
||||
if (updates.title !== undefined) localUpdates.name = updates.title;
|
||||
if (updates.description !== undefined) localUpdates.description = updates.description;
|
||||
if (updates.isPublic !== undefined) localUpdates.isPublic = updates.isPublic;
|
||||
if (updates.isPublic !== undefined) {
|
||||
// Legacy callers still pass isPublic — mirror to visibility
|
||||
// so the unified field stays in sync until M6.1 hard-drop.
|
||||
localUpdates.isPublic = updates.isPublic;
|
||||
localUpdates.visibility = updates.isPublic ? 'public' : 'space';
|
||||
}
|
||||
|
||||
const diff: Partial<LocalDeck> = {
|
||||
...localUpdates,
|
||||
|
|
@ -65,6 +78,34 @@ export const deckStore = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Flip a deck's visibility. M6 soft-migration: writes both
|
||||
* `visibility` and the legacy `isPublic` mirror so the picker
|
||||
* coexists with the older "public" badge UI until M6.1 hard-drop.
|
||||
*/
|
||||
async setVisibility(id: string, next: VisibilityLevel) {
|
||||
const existing = await cardDeckTable.get(id);
|
||||
if (!existing) throw new Error(`Deck ${id} not found`);
|
||||
const before: VisibilityLevel = existing.visibility ?? (existing.isPublic ? 'public' : 'space');
|
||||
if (before === next) return;
|
||||
|
||||
const stamp = new Date().toISOString();
|
||||
await cardDeckTable.update(id, {
|
||||
visibility: next,
|
||||
isPublic: next === 'public',
|
||||
visibilityChangedAt: stamp,
|
||||
visibilityChangedBy: getEffectiveUserId(),
|
||||
updatedAt: stamp,
|
||||
});
|
||||
|
||||
emitDomainEvent('VisibilityChanged', 'cards', 'cardDecks', id, {
|
||||
recordId: id,
|
||||
collection: 'cardDecks',
|
||||
before,
|
||||
after: next,
|
||||
});
|
||||
},
|
||||
|
||||
async deleteDeck(id: string) {
|
||||
error = null;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import type { BaseRecord } from '@mana/local-store';
|
||||
import type { VisibilityLevel } from '@mana/shared-privacy';
|
||||
|
||||
export interface LocalDeck extends BaseRecord {
|
||||
name: string;
|
||||
|
|
@ -10,7 +11,11 @@ export interface LocalDeck extends BaseRecord {
|
|||
color: string;
|
||||
cardCount: number;
|
||||
lastStudied?: string | null;
|
||||
/** @deprecated Use `visibility`. Mirror kept until M6.1 hard-drop. */
|
||||
isPublic: boolean;
|
||||
visibility?: VisibilityLevel;
|
||||
visibilityChangedAt?: string;
|
||||
visibilityChangedBy?: string;
|
||||
activeStudyBlockId?: string | null;
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +36,9 @@ export interface Deck {
|
|||
title: string;
|
||||
description?: string;
|
||||
color: string;
|
||||
/** @deprecated Use `visibility`. */
|
||||
isPublic: boolean;
|
||||
visibility: VisibilityLevel;
|
||||
tags: string[];
|
||||
cardCount: number;
|
||||
createdAt: string;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
|
||||
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
|
||||
import { deckStore } from '../stores/decks.svelte';
|
||||
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalDeck, LocalCard } from '../types';
|
||||
|
||||
|
|
@ -18,7 +19,6 @@
|
|||
let editName = $state('');
|
||||
let editDescription = $state('');
|
||||
let editColor = $state('#6366f1');
|
||||
let editIsPublic = $state(false);
|
||||
|
||||
const detail = useDetailEntity<LocalDeck>({
|
||||
id: () => deckId,
|
||||
|
|
@ -27,7 +27,6 @@
|
|||
editName = val.name;
|
||||
editDescription = val.description ?? '';
|
||||
editColor = val.color ?? '#6366f1';
|
||||
editIsPublic = val.isPublic;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -51,7 +50,6 @@
|
|||
await deckStore.updateDeck(deckId, {
|
||||
title: editName.trim() || detail.entity?.name || 'Unbenannt',
|
||||
description: editDescription.trim() || undefined,
|
||||
isPublic: editIsPublic,
|
||||
});
|
||||
// Color is not in UpdateDeckInput, update directly
|
||||
await db.table('decks').update(deckId, {
|
||||
|
|
@ -59,11 +57,6 @@
|
|||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
async function handlePublicToggle() {
|
||||
editIsPublic = !editIsPublic;
|
||||
await deckStore.updateDeck(deckId, { isPublic: editIsPublic });
|
||||
}
|
||||
</script>
|
||||
|
||||
<DetailViewShell
|
||||
|
|
@ -103,10 +96,12 @@
|
|||
</div>
|
||||
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Öffentlich</span>
|
||||
<button class="toggle-btn" class:active={editIsPublic} onclick={handlePublicToggle}>
|
||||
{editIsPublic ? 'Ja' : 'Nein'}
|
||||
</button>
|
||||
<span class="prop-label">Sichtbarkeit</span>
|
||||
<VisibilityPicker
|
||||
level={deck.visibility ?? (deck.isPublic ? 'public' : 'space')}
|
||||
onChange={(next: VisibilityLevel) => deckStore.setVisibility(deckId, next)}
|
||||
disabledLevels={['unlisted']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="prop-row">
|
||||
|
|
|
|||
|
|
@ -35,7 +35,12 @@ export function toMemo(local: LocalMemo): Memo {
|
|||
processingStatus: local.processingStatus,
|
||||
isArchived: local.isArchived,
|
||||
isPinned: local.isPinned,
|
||||
isPublic: local.isPublic,
|
||||
isPublic: local.isPublic ?? local.visibility === 'public',
|
||||
// Soft-fallback during M6 soak: legacy rows use isPublic, new
|
||||
// writes set visibility directly. Keep both in sync via the
|
||||
// store's setVisibility/setPublic methods until M6.1 drops the
|
||||
// legacy field.
|
||||
visibility: local.visibility ?? (local.isPublic === true ? 'public' : 'space'),
|
||||
language: local.language,
|
||||
createdAt: local.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: local.updatedAt ?? new Date().toISOString(),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import { createArchiveOps } from '@mana/shared-stores';
|
|||
import { MemoroEvents } from '@mana/shared-utils/analytics';
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getActiveSpace } from '$lib/data/scope';
|
||||
import { getEffectiveUserId } from '$lib/data/current-user';
|
||||
import { defaultVisibilityFor, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
import { transcribeAudio } from '$lib/voice/transcribe';
|
||||
import { llmTaskQueue } from '$lib/llm-queue';
|
||||
import { generateTitleTask } from '$lib/llm-tasks/generate-title';
|
||||
|
|
@ -43,6 +46,7 @@ export const memosStore = {
|
|||
isArchived: false,
|
||||
isPinned: false,
|
||||
isPublic: false,
|
||||
visibility: defaultVisibilityFor(getActiveSpace()?.type),
|
||||
blueprintId: data.blueprintId ?? null,
|
||||
language: data.language ?? null,
|
||||
// createdAt + updatedAt are required by LocalMemo's type but the
|
||||
|
|
@ -168,6 +172,36 @@ export const memosStore = {
|
|||
archive: (id: string) => memoArchive.archive(id),
|
||||
unarchive: (id: string) => memoArchive.unarchive(id),
|
||||
|
||||
/**
|
||||
* Flip a memo's visibility. M6 soft-migration: writes both
|
||||
* `visibility` and the legacy `isPublic` mirror so older readers
|
||||
* (search index, server snapshots) keep working until the M6.1
|
||||
* hard-drop. Public memos surface in the user's website embed
|
||||
* once a memoro embed-resolver lands.
|
||||
*/
|
||||
async setVisibility(id: string, next: VisibilityLevel) {
|
||||
const existing = await memoTable.get(id);
|
||||
if (!existing) throw new Error(`Memo ${id} not found`);
|
||||
const before: VisibilityLevel = existing.visibility ?? (existing.isPublic ? 'public' : 'space');
|
||||
if (before === next) return;
|
||||
|
||||
const stamp = new Date().toISOString();
|
||||
await memoTable.update(id, {
|
||||
visibility: next,
|
||||
isPublic: next === 'public',
|
||||
visibilityChangedAt: stamp,
|
||||
visibilityChangedBy: getEffectiveUserId(),
|
||||
updatedAt: stamp,
|
||||
});
|
||||
|
||||
emitDomainEvent('VisibilityChanged', 'memoro', 'memos', id, {
|
||||
recordId: id,
|
||||
collection: 'memos',
|
||||
before,
|
||||
after: next,
|
||||
});
|
||||
},
|
||||
|
||||
/** Pin a memo. */
|
||||
async pin(id: string) {
|
||||
await memoTable.update(id, {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import type { BaseRecord } from '@mana/local-store';
|
||||
import type { VisibilityLevel } from '@mana/shared-privacy';
|
||||
|
||||
export type ProcessingStatus = 'pending' | 'processing' | 'completed' | 'failed';
|
||||
|
||||
|
|
@ -15,7 +16,17 @@ export interface LocalMemo extends BaseRecord {
|
|||
processingStatus: ProcessingStatus;
|
||||
isArchived: boolean;
|
||||
isPinned: boolean;
|
||||
isPublic: boolean;
|
||||
/**
|
||||
* @deprecated Soft-migrating to unified `visibility`. Kept for the
|
||||
* soak window so the converter can fall back to `isPublic` for
|
||||
* legacy rows that haven't been touched since the M6 rollout.
|
||||
* Hard-drop once `visibility` has propagated to all rows.
|
||||
*/
|
||||
isPublic?: boolean;
|
||||
/** Unified visibility (M6 pilot — replaces isPublic). */
|
||||
visibility?: VisibilityLevel;
|
||||
visibilityChangedAt?: string;
|
||||
visibilityChangedBy?: string;
|
||||
blueprintId: string | null;
|
||||
language: string | null;
|
||||
location?: Record<string, unknown>;
|
||||
|
|
@ -84,7 +95,9 @@ export interface Memo {
|
|||
processingStatus: ProcessingStatus;
|
||||
isArchived: boolean;
|
||||
isPinned: boolean;
|
||||
/** @deprecated Use `visibility`. Mirror kept until M6.1 hard-drop. */
|
||||
isPublic: boolean;
|
||||
visibility: VisibilityLevel;
|
||||
language: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import type { QueuedTask } from '@mana/shared-llm';
|
||||
import type { LlmTier } from '@mana/shared-llm';
|
||||
import { PushPin } from '@mana/shared-icons';
|
||||
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalMemo, ProcessingStatus } from '../types';
|
||||
|
||||
|
|
@ -189,6 +190,15 @@
|
|||
placeholder="z.B. de"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Sichtbarkeit</span>
|
||||
<VisibilityPicker
|
||||
level={memo.visibility ?? (memo.isPublic ? 'public' : 'space')}
|
||||
onChange={(next: VisibilityLevel) => memosStore.setVisibility(memoId, next)}
|
||||
disabledLevels={['unlisted']}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ export function toDeck(local: LocalDeck): Deck {
|
|||
title: local.title,
|
||||
description: local.description ?? undefined,
|
||||
themeId: local.themeId ?? undefined,
|
||||
isPublic: local.isPublic,
|
||||
isPublic: local.isPublic ?? local.visibility === 'public',
|
||||
visibility: local.visibility ?? (local.isPublic === true ? 'public' : 'space'),
|
||||
createdAt: local.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: local.updatedAt ?? new Date().toISOString(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import { presiDeckTable, slideTable } from '../collections';
|
|||
import { toDeck, toSlide } from '../queries';
|
||||
import { PresiEvents } from '@mana/shared-utils/analytics';
|
||||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getActiveSpace } from '$lib/data/scope';
|
||||
import { getEffectiveUserId } from '$lib/data/current-user';
|
||||
import { defaultVisibilityFor, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
import { createBlock, updateBlock } from '$lib/data/time-blocks/service';
|
||||
import type {
|
||||
LocalDeck,
|
||||
|
|
@ -36,6 +40,7 @@ function createDecksStore() {
|
|||
description: dto.description || null,
|
||||
themeId: dto.themeId || null,
|
||||
isPublic: false,
|
||||
visibility: defaultVisibilityFor(getActiveSpace()?.type),
|
||||
};
|
||||
const plaintextSnapshot = toDeck(newLocal);
|
||||
await encryptRecord('presiDecks', newLocal);
|
||||
|
|
@ -60,7 +65,11 @@ function createDecksStore() {
|
|||
if (dto.title !== undefined) localUpdates.title = dto.title;
|
||||
if (dto.description !== undefined) localUpdates.description = dto.description;
|
||||
if (dto.themeId !== undefined) localUpdates.themeId = dto.themeId;
|
||||
if (dto.isPublic !== undefined) localUpdates.isPublic = dto.isPublic;
|
||||
if (dto.isPublic !== undefined) {
|
||||
// Mirror to unified visibility during M6 soak.
|
||||
localUpdates.isPublic = dto.isPublic;
|
||||
localUpdates.visibility = dto.isPublic ? 'public' : 'space';
|
||||
}
|
||||
|
||||
await encryptRecord('presiDecks', localUpdates);
|
||||
await presiDeckTable.update(id, localUpdates);
|
||||
|
|
@ -72,6 +81,34 @@ function createDecksStore() {
|
|||
}
|
||||
}
|
||||
|
||||
async function setVisibility(id: string, next: VisibilityLevel): Promise<boolean> {
|
||||
try {
|
||||
const existing = await presiDeckTable.get(id);
|
||||
if (!existing) return false;
|
||||
const before: VisibilityLevel =
|
||||
existing.visibility ?? (existing.isPublic ? 'public' : 'space');
|
||||
if (before === next) return true;
|
||||
const stamp = new Date().toISOString();
|
||||
await presiDeckTable.update(id, {
|
||||
visibility: next,
|
||||
isPublic: next === 'public',
|
||||
visibilityChangedAt: stamp,
|
||||
visibilityChangedBy: getEffectiveUserId(),
|
||||
updatedAt: stamp,
|
||||
});
|
||||
emitDomainEvent('VisibilityChanged', 'presi', 'presiDecks', id, {
|
||||
recordId: id,
|
||||
collection: 'presiDecks',
|
||||
before,
|
||||
after: next,
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to set visibility';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteDeck(id: string): Promise<boolean> {
|
||||
error = null;
|
||||
try {
|
||||
|
|
@ -219,6 +256,7 @@ function createDecksStore() {
|
|||
},
|
||||
createDeck,
|
||||
updateDeck,
|
||||
setVisibility,
|
||||
deleteDeck,
|
||||
createSlide,
|
||||
updateSlide,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@
|
|||
*/
|
||||
|
||||
import type { BaseRecord } from '@mana/local-store';
|
||||
import type { VisibilityLevel } from '@mana/shared-privacy';
|
||||
|
||||
export interface LocalDeck extends BaseRecord {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
themeId?: string | null;
|
||||
/** @deprecated Use `visibility`. Mirror kept until M6.1 hard-drop. */
|
||||
isPublic: boolean;
|
||||
visibility?: VisibilityLevel;
|
||||
visibilityChangedAt?: string;
|
||||
visibilityChangedBy?: string;
|
||||
activeRehearsalBlockId?: string | null;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +39,9 @@ export interface Deck {
|
|||
title: string;
|
||||
description?: string;
|
||||
themeId?: string;
|
||||
/** @deprecated Use `visibility`. */
|
||||
isPublic: boolean;
|
||||
visibility: VisibilityLevel;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { useDetailEntity } from '$lib/data/detail-entity.svelte';
|
||||
import DetailViewShell from '$lib/components/DetailViewShell.svelte';
|
||||
import { decksStore } from '../stores/decks.svelte';
|
||||
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalDeck, LocalSlide } from '../types';
|
||||
|
||||
|
|
@ -17,7 +18,6 @@
|
|||
|
||||
let editTitle = $state('');
|
||||
let editDescription = $state('');
|
||||
let editIsPublic = $state(false);
|
||||
|
||||
const detail = useDetailEntity<LocalDeck>({
|
||||
id: () => deckId,
|
||||
|
|
@ -25,7 +25,6 @@
|
|||
onLoad: (val) => {
|
||||
editTitle = val.title;
|
||||
editDescription = val.description ?? '';
|
||||
editIsPublic = val.isPublic;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -49,14 +48,8 @@
|
|||
await decksStore.updateDeck(deckId, {
|
||||
title: editTitle.trim() || detail.entity?.title || 'Unbenannt',
|
||||
description: editDescription.trim() || undefined,
|
||||
isPublic: editIsPublic,
|
||||
});
|
||||
}
|
||||
|
||||
async function handlePublicToggle() {
|
||||
editIsPublic = !editIsPublic;
|
||||
await decksStore.updateDeck(deckId, { isPublic: editIsPublic });
|
||||
}
|
||||
</script>
|
||||
|
||||
<DetailViewShell
|
||||
|
|
@ -89,10 +82,12 @@
|
|||
|
||||
<div class="properties">
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Öffentlich</span>
|
||||
<button class="toggle-btn" class:active={editIsPublic} onclick={handlePublicToggle}>
|
||||
{editIsPublic ? 'Ja' : 'Nein'}
|
||||
</button>
|
||||
<span class="prop-label">Sichtbarkeit</span>
|
||||
<VisibilityPicker
|
||||
level={deck.visibility ?? (deck.isPublic ? 'public' : 'space')}
|
||||
onChange={(next: VisibilityLevel) => decksStore.setVisibility(deckId, next)}
|
||||
disabledLevels={['unlisted']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="prop-row">
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export const ULOAD_GUEST_SEED = {
|
|||
color: '#8b5cf6',
|
||||
icon: null,
|
||||
isPublic: false,
|
||||
visibility: 'space',
|
||||
usageCount: 0,
|
||||
},
|
||||
{
|
||||
|
|
@ -97,6 +98,7 @@ export const ULOAD_GUEST_SEED = {
|
|||
color: '#3b82f6',
|
||||
icon: null,
|
||||
isPublic: false,
|
||||
visibility: 'space',
|
||||
usageCount: 0,
|
||||
},
|
||||
{
|
||||
|
|
@ -106,6 +108,7 @@ export const ULOAD_GUEST_SEED = {
|
|||
color: '#10b981',
|
||||
icon: null,
|
||||
isPublic: false,
|
||||
visibility: 'space',
|
||||
usageCount: 0,
|
||||
},
|
||||
] satisfies LocalTag[],
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ export interface Tag {
|
|||
slug: string;
|
||||
color?: string;
|
||||
icon?: string;
|
||||
/** @deprecated Use `visibility`. */
|
||||
isPublic: boolean;
|
||||
visibility: import('@mana/shared-privacy').VisibilityLevel;
|
||||
usageCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
|
@ -104,7 +106,8 @@ export function toTag(local: LocalTag): Tag {
|
|||
slug: local.slug,
|
||||
color: local.color ?? undefined,
|
||||
icon: local.icon ?? undefined,
|
||||
isPublic: local.isPublic,
|
||||
isPublic: local.isPublic ?? local.visibility === 'public',
|
||||
visibility: local.visibility ?? (local.isPublic === true ? 'public' : 'space'),
|
||||
usageCount: local.usageCount,
|
||||
createdAt: local.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: local.updatedAt ?? new Date().toISOString(),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import type { BaseRecord } from '@mana/local-store';
|
||||
import type { VisibilityLevel } from '@mana/shared-privacy';
|
||||
|
||||
export interface LocalLink extends BaseRecord {
|
||||
shortCode: string;
|
||||
|
|
@ -29,7 +30,13 @@ export interface LocalTag extends BaseRecord {
|
|||
slug: string;
|
||||
color?: string | null;
|
||||
icon?: string | null;
|
||||
/**
|
||||
* @deprecated Use `visibility`. Mirror kept for the M6 soak window.
|
||||
* No active CRUD UI yet — the field is set only by seed data and
|
||||
* the future tag-management view will write `visibility` directly.
|
||||
*/
|
||||
isPublic: boolean;
|
||||
visibility?: VisibilityLevel;
|
||||
usageCount: number;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue