Fix note color swatches outside teleported editor panel.
Use hex palettes so Keep-style colors render when the create panel is portaled to body. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -67,6 +67,7 @@ const draftActivityId = ref('');
|
|||||||
const draftReminder = ref('');
|
const draftReminder = ref('');
|
||||||
const draftLocation = ref('');
|
const draftLocation = ref('');
|
||||||
const draftAnalyze = ref(true);
|
const draftAnalyze = ref(true);
|
||||||
|
const draftPinned = ref(false);
|
||||||
const draftChecklist = ref<NoteChecklistItemDto[]>([]);
|
const draftChecklist = ref<NoteChecklistItemDto[]>([]);
|
||||||
const draftFiles = ref<File[]>([]);
|
const draftFiles = ref<File[]>([]);
|
||||||
const existingAttachments = ref<NoteAttachmentDto[]>([]);
|
const existingAttachments = ref<NoteAttachmentDto[]>([]);
|
||||||
@@ -130,17 +131,18 @@ const draftColorCss = computed(() => colorToCss(draftColor.value));
|
|||||||
const personName = (id: string) =>
|
const personName = (id: string) =>
|
||||||
people.value.find((p) => p.id === id)?.fullName ?? '';
|
people.value.find((p) => p.id === id)?.fullName ?? '';
|
||||||
|
|
||||||
|
/** Hex only — panel is Teleported outside `.notes-page`, so CSS vars would not inherit. */
|
||||||
|
const NOTE_COLOR_HEX: Record<ColorKey, string> = {
|
||||||
|
sand: '#fff8e1',
|
||||||
|
rose: '#fce4ec',
|
||||||
|
sky: '#e3f2fd',
|
||||||
|
mint: '#e0f2f1',
|
||||||
|
lavender: '#f3e5f5',
|
||||||
|
default: '#ffffff',
|
||||||
|
};
|
||||||
|
|
||||||
function colorToCss(raw: string | null | undefined): string {
|
function colorToCss(raw: string | null | undefined): string {
|
||||||
const key = normalizeColor(raw);
|
return NOTE_COLOR_HEX[normalizeColor(raw)];
|
||||||
const map: Record<ColorKey, string> = {
|
|
||||||
sand: 'var(--note-sand)',
|
|
||||||
rose: 'var(--note-rose)',
|
|
||||||
sky: 'var(--note-sky)',
|
|
||||||
mint: 'var(--note-mint)',
|
|
||||||
lavender: 'var(--note-lavender)',
|
|
||||||
default: 'var(--note-default)',
|
|
||||||
};
|
|
||||||
return map[key];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeColor(raw: string | null | undefined): ColorKey {
|
function normalizeColor(raw: string | null | undefined): ColorKey {
|
||||||
@@ -359,6 +361,7 @@ function resetDraft() {
|
|||||||
draftReminder.value = '';
|
draftReminder.value = '';
|
||||||
draftLocation.value = '';
|
draftLocation.value = '';
|
||||||
draftAnalyze.value = true;
|
draftAnalyze.value = true;
|
||||||
|
draftPinned.value = false;
|
||||||
draftChecklist.value = [];
|
draftChecklist.value = [];
|
||||||
draftFiles.value = [];
|
draftFiles.value = [];
|
||||||
existingAttachments.value = [];
|
existingAttachments.value = [];
|
||||||
@@ -381,6 +384,7 @@ function openEditor(mode: OpenMode = 'default', note?: NoteDto) {
|
|||||||
draftReminder.value = note.reminderAt ? note.reminderAt.slice(0, 16) : '';
|
draftReminder.value = note.reminderAt ? note.reminderAt.slice(0, 16) : '';
|
||||||
draftLocation.value = note.locationText ?? '';
|
draftLocation.value = note.locationText ?? '';
|
||||||
draftAnalyze.value = note.analyzeEnabled !== false;
|
draftAnalyze.value = note.analyzeEnabled !== false;
|
||||||
|
draftPinned.value = note.pinned;
|
||||||
draftChecklist.value = [...(note.checklist ?? [])];
|
draftChecklist.value = [...(note.checklist ?? [])];
|
||||||
draftFiles.value = [];
|
draftFiles.value = [];
|
||||||
existingAttachments.value = [...note.attachments];
|
existingAttachments.value = [...note.attachments];
|
||||||
@@ -496,6 +500,7 @@ async function saveNote() {
|
|||||||
: null,
|
: null,
|
||||||
locationText: draftLocation.value.trim() || null,
|
locationText: draftLocation.value.trim() || null,
|
||||||
analyzeEnabled: draftAnalyze.value,
|
analyzeEnabled: draftAnalyze.value,
|
||||||
|
pinned: draftPinned.value,
|
||||||
checklist,
|
checklist,
|
||||||
};
|
};
|
||||||
const pending = [...draftFiles.value];
|
const pending = [...draftFiles.value];
|
||||||
@@ -534,6 +539,24 @@ async function togglePin(id: string) {
|
|||||||
menuOpenId.value = null;
|
menuOpenId.value = null;
|
||||||
await api.patch(`/notes/${id}/pin`);
|
await api.patch(`/notes/${id}/pin`);
|
||||||
await load();
|
await load();
|
||||||
|
if (editingId.value === id) {
|
||||||
|
const n = notes.value.find((x) => x.id === id);
|
||||||
|
if (n) draftPinned.value = n.pinned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onPinClick() {
|
||||||
|
const next = !draftPinned.value;
|
||||||
|
draftPinned.value = next;
|
||||||
|
if (!editingId.value) return;
|
||||||
|
const note = notes.value.find((n) => n.id === editingId.value);
|
||||||
|
if (note && note.pinned === next) return;
|
||||||
|
try {
|
||||||
|
await api.patch(`/notes/${editingId.value}/pin`);
|
||||||
|
if (note) note.pinned = next;
|
||||||
|
} catch {
|
||||||
|
draftPinned.value = !next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeNote(id: string) {
|
async function removeNote(id: string) {
|
||||||
@@ -864,7 +887,13 @@ onBeforeUnmount(() => {
|
|||||||
class="panel-root desktop-panel"
|
class="panel-root desktop-panel"
|
||||||
>
|
>
|
||||||
<div class="panel-backdrop" @click="closeEditor" />
|
<div class="panel-backdrop" @click="closeEditor" />
|
||||||
<aside class="editor-panel" :style="{ '--canvas': draftColorCss }">
|
<aside
|
||||||
|
class="editor-panel"
|
||||||
|
:style="{
|
||||||
|
'--canvas': draftColorCss,
|
||||||
|
background: draftColorCss,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<header class="panel-head">
|
<header class="panel-head">
|
||||||
<h2>{{ editorTitle }}</h2>
|
<h2>{{ editorTitle }}</h2>
|
||||||
<button type="button" class="icon-btn" @click="closeEditor">
|
<button type="button" class="icon-btn" @click="closeEditor">
|
||||||
@@ -887,18 +916,38 @@ onBeforeUnmount(() => {
|
|||||||
placeholder="یادداشت خود را بنویسید…"
|
placeholder="یادداشت خود را بنویسید…"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="swatches">
|
<div class="palette-row">
|
||||||
|
<div class="swatches" role="listbox" aria-label="رنگ یادداشت">
|
||||||
|
<button
|
||||||
|
v-for="sw in colorSwatches"
|
||||||
|
:key="sw.value"
|
||||||
|
type="button"
|
||||||
|
class="swatch"
|
||||||
|
:class="[
|
||||||
|
`tone-${sw.value}`,
|
||||||
|
{ selected: draftColor === sw.value },
|
||||||
|
]"
|
||||||
|
:style="{ backgroundColor: NOTE_COLOR_HEX[sw.value] }"
|
||||||
|
:title="sw.label"
|
||||||
|
:aria-label="sw.label"
|
||||||
|
:aria-selected="draftColor === sw.value"
|
||||||
|
@click="draftColor = sw.value"
|
||||||
|
>
|
||||||
|
<UiIcon
|
||||||
|
v-if="draftColor === sw.value"
|
||||||
|
name="check-plain"
|
||||||
|
:size="14"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
v-for="sw in colorSwatches"
|
|
||||||
:key="sw.value"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="swatch"
|
class="media-btn pin-btn"
|
||||||
:class="{ selected: draftColor === sw.value }"
|
:class="{ on: draftPinned }"
|
||||||
:style="{ background: colorToCss(sw.value) }"
|
title="سنجاق"
|
||||||
:title="sw.label"
|
@click="onPinClick"
|
||||||
@click="draftColor = sw.value"
|
|
||||||
>
|
>
|
||||||
<UiIcon v-if="draftColor === sw.value" name="check-plain" :size="14" />
|
سنجاق
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1059,7 +1108,7 @@ onBeforeUnmount(() => {
|
|||||||
<div
|
<div
|
||||||
v-if="editorOpen && mobileEditor"
|
v-if="editorOpen && mobileEditor"
|
||||||
class="mobile-editor"
|
class="mobile-editor"
|
||||||
:style="{ '--canvas': draftColorCss }"
|
:style="{ '--canvas': draftColorCss, background: draftColorCss }"
|
||||||
>
|
>
|
||||||
<header class="mob-head">
|
<header class="mob-head">
|
||||||
<button type="button" class="icon-btn" @click="closeEditor">
|
<button type="button" class="icon-btn" @click="closeEditor">
|
||||||
@@ -1089,17 +1138,34 @@ onBeforeUnmount(() => {
|
|||||||
placeholder="یادداشت خود را بنویسید…"
|
placeholder="یادداشت خود را بنویسید…"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="swatches">
|
<div class="palette-row">
|
||||||
|
<div class="swatches" role="listbox" aria-label="رنگ یادداشت">
|
||||||
|
<button
|
||||||
|
v-for="sw in colorSwatches"
|
||||||
|
:key="`m-${sw.value}`"
|
||||||
|
type="button"
|
||||||
|
class="swatch"
|
||||||
|
:class="[`tone-${sw.value}`, { selected: draftColor === sw.value }]"
|
||||||
|
:style="{ backgroundColor: NOTE_COLOR_HEX[sw.value] }"
|
||||||
|
:title="sw.label"
|
||||||
|
:aria-label="sw.label"
|
||||||
|
:aria-selected="draftColor === sw.value"
|
||||||
|
@click="draftColor = sw.value"
|
||||||
|
>
|
||||||
|
<UiIcon
|
||||||
|
v-if="draftColor === sw.value"
|
||||||
|
name="check-plain"
|
||||||
|
:size="14"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
v-for="sw in colorSwatches"
|
|
||||||
:key="`m-${sw.value}`"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="swatch"
|
class="media-btn pin-btn"
|
||||||
:class="{ selected: draftColor === sw.value }"
|
:class="{ on: draftPinned }"
|
||||||
:style="{ background: colorToCss(sw.value) }"
|
@click="onPinClick"
|
||||||
@click="draftColor = sw.value"
|
|
||||||
>
|
>
|
||||||
<UiIcon v-if="draftColor === sw.value" name="check-plain" :size="14" />
|
سنجاق
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1825,23 +1891,60 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
.swatches {
|
.swatches {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.45rem;
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.palette-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.65rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.swatch {
|
.swatch {
|
||||||
width: 28px;
|
width: 32px;
|
||||||
height: 28px;
|
height: 32px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
border: 1.5px solid rgba(15, 23, 42, 0.18);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: var(--teal);
|
color: #0f766e;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
.swatch.tone-default {
|
||||||
|
border-color: #94a3b8;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
#f1f5f9 25%,
|
||||||
|
transparent 25%,
|
||||||
|
transparent 75%,
|
||||||
|
#f1f5f9 75%
|
||||||
|
);
|
||||||
|
background-size: 8px 8px;
|
||||||
}
|
}
|
||||||
.swatch.selected {
|
.swatch.selected {
|
||||||
box-shadow: 0 0 0 2px var(--teal);
|
box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--teal, #0d9488);
|
||||||
|
}
|
||||||
|
.pin-btn.on {
|
||||||
|
border-color: #f59e0b;
|
||||||
|
color: #b45309;
|
||||||
|
background: #fffbeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-root {
|
||||||
|
--note-sand: #fff8e1;
|
||||||
|
--note-rose: #fce4ec;
|
||||||
|
--note-sky: #e3f2fd;
|
||||||
|
--note-mint: #e0f2f1;
|
||||||
|
--note-lavender: #f3e5f5;
|
||||||
|
--note-default: #ffffff;
|
||||||
|
--teal: #0d9488;
|
||||||
|
--teal-soft: #ccfbf1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-row {
|
.media-row {
|
||||||
|
|||||||
Reference in New Issue
Block a user