From e011f991616193a3d9ef55b14fac8c535c69e27f Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 17 Nov 2025 18:58:40 +0200 Subject: [PATCH 01/37] client: add support for custom menu items --- apps/client/src/menus/context_menu.ts | 245 ++++++++++++++------------ 1 file changed, 133 insertions(+), 112 deletions(-) diff --git a/apps/client/src/menus/context_menu.ts b/apps/client/src/menus/context_menu.ts index 9efdac65f..d4e9693d8 100644 --- a/apps/client/src/menus/context_menu.ts +++ b/apps/client/src/menus/context_menu.ts @@ -2,7 +2,7 @@ import { KeyboardActionNames } from "@triliumnext/commons"; import keyboardActionService, { getActionSync } from "../services/keyboard_actions.js"; import note_tooltip from "../services/note_tooltip.js"; import utils from "../services/utils.js"; -import { should } from "vitest"; +import { h, JSX, render } from "preact"; export interface ContextMenuOptions { x: number; @@ -15,6 +15,11 @@ export interface ContextMenuOptions { onHide?: () => void; } +export interface CustomMenuItem { + kind: "custom", + componentFn: () => JSX.Element; +} + export interface MenuSeparatorItem { kind: "separator"; } @@ -51,7 +56,7 @@ export interface MenuCommandItem { columns?: number; } -export type MenuItem = MenuCommandItem | MenuSeparatorItem | MenuHeader; +export type MenuItem = MenuCommandItem | CustomMenuItem | MenuSeparatorItem | MenuHeader; export type MenuHandler = (item: MenuCommandItem, e: JQuery.MouseDownEvent) => void; export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent; @@ -202,118 +207,14 @@ class ContextMenu { $group.append($("
").addClass("dropdown-header").text(item.title)); shouldResetGroup = true; } else { - const $icon = $(""); - - if ("uiIcon" in item || "checked" in item) { - const icon = (item.checked ? "bx bx-check" : item.uiIcon); - if (icon) { - $icon.addClass(icon); - } else { - $icon.append(" "); - } + if ("kind" in item && item.kind === "custom") { + // Custom menu item + $group.append(this.createCustomMenuItem(item)); + } else { + // Standard menu item + $group.append(this.createMenuItem(item)); } - const $link = $("") - .append($icon) - .append("   ") // some space between icon and text - .append(item.title); - - if ("badges" in item && item.badges) { - for (let badge of item.badges) { - const badgeElement = $(``).text(badge.title); - - if (badge.className) { - badgeElement.addClass(badge.className); - } - - $link.append(badgeElement); - } - } - - if ("keyboardShortcut" in item && item.keyboardShortcut) { - const shortcuts = getActionSync(item.keyboardShortcut).effectiveShortcuts; - if (shortcuts) { - const allShortcuts: string[] = []; - for (const effectiveShortcut of shortcuts) { - allShortcuts.push(effectiveShortcut.split("+") - .map(key => `${key}`) - .join("+")); - } - - if (allShortcuts.length) { - const container = $("").addClass("keyboard-shortcut"); - container.append($(allShortcuts.join(","))); - $link.append(container); - } - } - } else if ("shortcut" in item && item.shortcut) { - $link.append($("").text(item.shortcut)); - } - - const $item = $("
  • ") - .addClass("dropdown-item") - .append($link) - .on("contextmenu", (e) => false) - // important to use mousedown instead of click since the former does not change focus - // (especially important for focused text for spell check) - .on("mousedown", (e) => { - e.stopPropagation(); - - if (e.which !== 1) { - // only left click triggers menu items - return false; - } - - if (this.isMobile && "items" in item && item.items) { - const $item = $(e.target).closest(".dropdown-item"); - - $item.toggleClass("submenu-open"); - $item.find("ul.dropdown-menu").toggleClass("show"); - return false; - } - - if ("handler" in item && item.handler) { - item.handler(item, e); - } - - this.options?.selectMenuItemHandler(item, e); - - // it's important to stop the propagation especially for sub-menus, otherwise the event - // might be handled again by top-level menu - return false; - }); - - $item.on("mouseup", (e) => { - // Prevent submenu from failing to expand on mobile - if (!this.isMobile || !("items" in item && item.items)) { - e.stopPropagation(); - // Hide the content menu on mouse up to prevent the mouse event from propagating to the elements below. - this.hide(); - return false; - } - }); - - if ("enabled" in item && item.enabled !== undefined && !item.enabled) { - $item.addClass("disabled"); - } - - if ("items" in item && item.items) { - $item.addClass("dropdown-submenu"); - $link.addClass("dropdown-toggle"); - - const $subMenu = $("
      ").addClass("dropdown-menu"); - const hasColumns = !!item.columns && item.columns > 1; - if (!this.isMobile && hasColumns) { - $subMenu.css("column-count", item.columns!); - } - - this.addItems($subMenu, item.items, hasColumns); - - $item.append($subMenu); - } - - $group.append($item); - // After adding a menu item, if the previous item was a separator or header, // reset the group so that the next item will be appended directly to the parent. if (shouldResetGroup) { @@ -324,6 +225,126 @@ class ContextMenu { } } + private createCustomMenuItem(item: CustomMenuItem) { + const element = document.createElement("li"); + element.classList.add("dropdown-custom-item"); + render(h(item.componentFn, {}), element); + return element; + } + + private createMenuItem(item: MenuCommandItem) { + const $icon = $(""); + + if ("uiIcon" in item || "checked" in item) { + const icon = (item.checked ? "bx bx-check" : item.uiIcon); + if (icon) { + $icon.addClass(icon); + } else { + $icon.append(" "); + } + } + + const $link = $("") + .append($icon) + .append("   ") // some space between icon and text + .append(item.title); + + if ("badges" in item && item.badges) { + for (let badge of item.badges) { + const badgeElement = $(``).text(badge.title); + + if (badge.className) { + badgeElement.addClass(badge.className); + } + + $link.append(badgeElement); + } + } + + if ("keyboardShortcut" in item && item.keyboardShortcut) { + const shortcuts = getActionSync(item.keyboardShortcut).effectiveShortcuts; + if (shortcuts) { + const allShortcuts: string[] = []; + for (const effectiveShortcut of shortcuts) { + allShortcuts.push(effectiveShortcut.split("+") + .map(key => `${key}`) + .join("+")); + } + + if (allShortcuts.length) { + const container = $("").addClass("keyboard-shortcut"); + container.append($(allShortcuts.join(","))); + $link.append(container); + } + } + } else if ("shortcut" in item && item.shortcut) { + $link.append($("").text(item.shortcut)); + } + + const $item = $("
    • ") + .addClass("dropdown-item") + .append($link) + .on("contextmenu", (e) => false) + // important to use mousedown instead of click since the former does not change focus + // (especially important for focused text for spell check) + .on("mousedown", (e) => { + e.stopPropagation(); + + if (e.which !== 1) { + // only left click triggers menu items + return false; + } + + if (this.isMobile && "items" in item && item.items) { + const $item = $(e.target).closest(".dropdown-item"); + + $item.toggleClass("submenu-open"); + $item.find("ul.dropdown-menu").toggleClass("show"); + return false; + } + + if ("handler" in item && item.handler) { + item.handler(item, e); + } + + this.options?.selectMenuItemHandler(item, e); + + // it's important to stop the propagation especially for sub-menus, otherwise the event + // might be handled again by top-level menu + return false; + }); + + $item.on("mouseup", (e) => { + // Prevent submenu from failing to expand on mobile + if (!this.isMobile || !("items" in item && item.items)) { + e.stopPropagation(); + // Hide the content menu on mouse up to prevent the mouse event from propagating to the elements below. + this.hide(); + return false; + } + }); + + if ("enabled" in item && item.enabled !== undefined && !item.enabled) { + $item.addClass("disabled"); + } + + if ("items" in item && item.items) { + $item.addClass("dropdown-submenu"); + $link.addClass("dropdown-toggle"); + + const $subMenu = $("
        ").addClass("dropdown-menu"); + const hasColumns = !!item.columns && item.columns > 1; + if (!this.isMobile && hasColumns) { + $subMenu.css("column-count", item.columns!); + } + + this.addItems($subMenu, item.items, hasColumns); + + $item.append($subMenu); + } + return $item; + } + async hide() { this.options?.onHide?.(); this.$widget.removeClass("show"); From 5291a6856eb932226710a75f1c769e189f49155a Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 17 Nov 2025 19:14:34 +0200 Subject: [PATCH 02/37] client: create a placeholder for a color picker menu item --- .../src/menus/custom-items/ColorPickerMenuItem.tsx | 9 +++++++++ apps/client/src/menus/tree_context_menu.ts | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx new file mode 100644 index 000000000..882881063 --- /dev/null +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx @@ -0,0 +1,9 @@ +import FNote from "../../entities/fnote" + +export interface ColorPickerMenuItemProps { + note: FNote | null; +} + +export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { + return Color Picker +} \ No newline at end of file diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts index 7384573d8..b85648a85 100644 --- a/apps/client/src/menus/tree_context_menu.ts +++ b/apps/client/src/menus/tree_context_menu.ts @@ -1,3 +1,4 @@ +import ColorPickerMenuItem from "./custom-items/ColorPickerMenuItem.jsx"; import treeService from "../services/tree.js"; import froca from "../services/froca.js"; import clipboard from "../services/clipboard.js"; @@ -255,7 +256,12 @@ export default class TreeContextMenu implements SelectMenuItemEventListener ColorPickerMenuItem({note}) + }, ]; return items.filter((row) => row !== null) as MenuItem[]; } From 441c55eb313581e8c251f50dd1e23f6395c22a10 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 00:09:12 +0200 Subject: [PATCH 03/37] client/note color picker menu item: add initial implementation --- .../custom-items/ColorPickerMenuItem.css | 15 +++++++++ .../custom-items/ColorPickerMenuItem.tsx | 33 +++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 apps/client/src/menus/custom-items/ColorPickerMenuItem.css diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.css b/apps/client/src/menus/custom-items/ColorPickerMenuItem.css new file mode 100644 index 000000000..205ac9a29 --- /dev/null +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.css @@ -0,0 +1,15 @@ +.color-picker-menu-item { + display: flex; + gap: 10px; +} + +.color-picker-menu-item > .color-cell { + width: 16px; + height: 16px; + border-radius: 4px; + background: transparent; +} + +.color-picker-menu-item > .color-cell.selected { + outline: 2px solid royalblue; +} \ No newline at end of file diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx index 882881063..d9b9a3d41 100644 --- a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx @@ -1,9 +1,38 @@ -import FNote from "../../entities/fnote" +import "./ColorPickerMenuItem.css"; +import { useState } from "preact/hooks"; +import attributes from "../../services/attributes"; +import FNote from "../../entities/fnote"; + +const COLORS = ["blue", "green", "cyan", "red", "magenta", "brown", "yellow", ""]; export interface ColorPickerMenuItemProps { note: FNote | null; } export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { - return Color Picker + const {note} = props; + if (!note) return null; + + const [currentColor, setCurrentColor] = useState(note.getLabel("color")?.value ?? ""); + + const onColorCellClicked = (color: string) => { + attributes.setLabel(note.noteId, "color", color); + setCurrentColor(color); + } + + return
        + {COLORS.map((color) => ( + onColorCellClicked(color)} /> + ))} +
        +} + +function ColorCell(props: {color: string, isSelected: boolean, onClick?: () => void}) { + return
        +
        ; } \ No newline at end of file From 8729fe48c382f77ec9b2f88523ff1752c3e07b02 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:09:22 +0200 Subject: [PATCH 04/37] client/note color picker menu item: add support to operate with note IDs as well --- .../custom-items/ColorPickerMenuItem.css | 4 ++ .../custom-items/ColorPickerMenuItem.tsx | 40 ++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.css b/apps/client/src/menus/custom-items/ColorPickerMenuItem.css index 205ac9a29..1f56926a9 100644 --- a/apps/client/src/menus/custom-items/ColorPickerMenuItem.css +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.css @@ -10,6 +10,10 @@ background: transparent; } +.color-picker-menu-item > .color-cell.disabled-color-cell { + cursor: not-allowed; +} + .color-picker-menu-item > .color-cell.selected { outline: 2px solid royalblue; } \ No newline at end of file diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx index d9b9a3d41..11ca42c48 100644 --- a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx @@ -1,23 +1,44 @@ import "./ColorPickerMenuItem.css"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import attributes from "../../services/attributes"; import FNote from "../../entities/fnote"; +import froca from "../../services/froca"; const COLORS = ["blue", "green", "cyan", "red", "magenta", "brown", "yellow", ""]; export interface ColorPickerMenuItemProps { - note: FNote | null; + /** The target Note instance or its ID string. */ + note: FNote | string | null; } export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { - const {note} = props; - if (!note) return null; + if (!props.note) return null; - const [currentColor, setCurrentColor] = useState(note.getLabel("color")?.value ?? ""); + const [note, setNote] = useState(null); + const [currentColor, setCurrentColor] = useState(null); + + + useEffect(() => { + const retrieveNote = async (noteId: string) => { + const result = await froca.getNote(noteId, true); + if (result) { + setNote(result); + setCurrentColor(result.getLabel("color")?.value ?? ""); + } + } + + if (typeof props.note === "string") { + retrieveNote(props.note); // Get the note from the given ID string + } else { + setNote(props.note); + } + }, []); const onColorCellClicked = (color: string) => { - attributes.setLabel(note.noteId, "color", color); - setCurrentColor(color); + if (note) { + attributes.setLabel(note.noteId, "color", color); + setCurrentColor(color); + } } return
        @@ -25,13 +46,14 @@ export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { onColorCellClicked(color)} /> ))}
        } -function ColorCell(props: {color: string, isSelected: boolean, onClick?: () => void}) { - return
        void}) { + return
        ; From e239bca0f2feec792a8028d4e1251e1ed5d88d14 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:10:10 +0200 Subject: [PATCH 05/37] client/note color picker menu item: fix data type --- apps/client/src/menus/context_menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/menus/context_menu.ts b/apps/client/src/menus/context_menu.ts index d4e9693d8..4e28d7768 100644 --- a/apps/client/src/menus/context_menu.ts +++ b/apps/client/src/menus/context_menu.ts @@ -17,7 +17,7 @@ export interface ContextMenuOptions { export interface CustomMenuItem { kind: "custom", - componentFn: () => JSX.Element; + componentFn: () => JSX.Element | null; } export interface MenuSeparatorItem { From 1ac7ce00fb11da89dfa3d2b38b506c8c73f93b5a Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:11:55 +0200 Subject: [PATCH 06/37] client/note color picker menu item: add to the calendar item context menu --- .../src/widgets/collections/calendar/context_menu.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/client/src/widgets/collections/calendar/context_menu.ts b/apps/client/src/widgets/collections/calendar/context_menu.ts index b15ba376d..31de26de5 100644 --- a/apps/client/src/widgets/collections/calendar/context_menu.ts +++ b/apps/client/src/widgets/collections/calendar/context_menu.ts @@ -1,8 +1,10 @@ +import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem"; import FNote from "../../../entities/fnote"; import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu"; import link_context_menu from "../../../menus/link_context_menu"; import branches from "../../../services/branches"; import froca from "../../../services/froca"; +import { note } from "mermaid/dist/rendering-util/rendering-elements/shapes/note.js"; import { t } from "../../../services/i18n"; export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, parentNote: FNote) { @@ -34,6 +36,11 @@ export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, par await branches.deleteNotes([ branchIdToDelete ], false, false); } } + }, + { kind: "separator" }, + { + kind: "custom", + componentFn: () => ColorPickerMenuItem({note: noteId}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, noteId), From 69ad40c27f6457ba0b5a7a9f6f7f6ecacf870fce Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:12:28 +0200 Subject: [PATCH 07/37] client/note color picker menu item: add to the board item context menu --- apps/client/src/widgets/collections/board/context_menu.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/client/src/widgets/collections/board/context_menu.ts b/apps/client/src/widgets/collections/board/context_menu.ts index 0c818a111..ba071cf64 100644 --- a/apps/client/src/widgets/collections/board/context_menu.ts +++ b/apps/client/src/widgets/collections/board/context_menu.ts @@ -1,3 +1,4 @@ +import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem"; import FNote from "../../../entities/fnote"; import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu"; import link_context_menu from "../../../menus/link_context_menu"; @@ -74,6 +75,11 @@ export function openNoteContextMenu(api: Api, event: ContextMenuEvent, note: FNo uiIcon: "bx bx-trash", handler: () => branches.deleteNotes([ branchId ], false, false) }, + { kind: "separator" }, + { + kind: "custom", + componentFn: () => ColorPickerMenuItem({note}) + } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, note.noteId), }); From 79830870ddf3713fca666c4ecc7bc9a41fce87d3 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:13:21 +0200 Subject: [PATCH 08/37] client/note color picker menu item: add to the geo map item context menu --- .../client/src/widgets/collections/geomap/context_menu.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/geomap/context_menu.ts b/apps/client/src/widgets/collections/geomap/context_menu.ts index dd583e266..a0a6c8fc6 100644 --- a/apps/client/src/widgets/collections/geomap/context_menu.ts +++ b/apps/client/src/widgets/collections/geomap/context_menu.ts @@ -2,6 +2,7 @@ import type { LatLng, LeafletMouseEvent } from "leaflet"; import appContext, { type CommandMappings } from "../../../components/app_context.js"; import contextMenu, { type MenuItem } from "../../../menus/context_menu.js"; import linkContextMenu from "../../../menus/link_context_menu.js"; +import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem.jsx"; import { t } from "../../../services/i18n.js"; import { createNewNote } from "./api.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; @@ -18,7 +19,12 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent, is items = [ ...items, { kind: "separator" }, - { title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" } + { title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" }, + { kind: "separator"}, + { + kind: "custom", + componentFn: () => ColorPickerMenuItem({note: noteId}) + } ]; } From 87fcc0afe616a22f61b0dfef5bbd33e17044620b Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:14:05 +0200 Subject: [PATCH 09/37] client/note color picker menu item: add to the table row context menu --- apps/client/src/widgets/collections/table/context_menu.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/client/src/widgets/collections/table/context_menu.ts b/apps/client/src/widgets/collections/table/context_menu.ts index eb0a303ae..11d4477ca 100644 --- a/apps/client/src/widgets/collections/table/context_menu.ts +++ b/apps/client/src/widgets/collections/table/context_menu.ts @@ -6,6 +6,7 @@ import { TableData } from "./rows.js"; import link_context_menu from "../../../menus/link_context_menu.js"; import froca from "../../../services/froca.js"; import branches from "../../../services/branches.js"; +import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem.jsx"; import Component from "../../../components/component.js"; import { RefObject } from "preact"; @@ -219,6 +220,11 @@ export function showRowContextMenu(parentComponent: Component, e: MouseEvent, ro title: t("table_context_menu.delete_row"), uiIcon: "bx bx-trash", handler: () => branches.deleteNotes([ rowData.branchId ], false, false) + }, + { kind: "separator"}, + { + kind: "custom", + componentFn: () => ColorPickerMenuItem({note: rowData.noteId}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, rowData.noteId), From e5ac8a0a67a63fd887627bdd5a834a518c03d1a3 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:16:32 +0200 Subject: [PATCH 10/37] client/note color picker menu item: refactor --- .../src/menus/custom-items/ColorPickerMenuItem.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx index 11ca42c48..729c7c50b 100644 --- a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx @@ -52,7 +52,14 @@ export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) {
        } -function ColorCell(props: {color: string, isSelected: boolean, isDisabled?: boolean, onClick?: () => void}) { +interface ColorCellProps { + color: string, + isSelected: boolean, + isDisabled?: boolean, + onClick?: () => void +} + +function ColorCell(props: ColorCellProps) { return
        From 870fef3ea63e081d05de20000522514827c508f0 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:17:13 +0200 Subject: [PATCH 11/37] client/note color picker menu item: fix a typo --- apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx index 729c7c50b..b9e6d876a 100644 --- a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx @@ -46,7 +46,7 @@ export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { onColorCellClicked(color)} /> ))}
        From 87afc64f16c82bd4690c1e825c5911c81d266f2e Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:21:26 +0200 Subject: [PATCH 12/37] client/note color picker menu item: fix current selection --- apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx index b9e6d876a..030a853e4 100644 --- a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx @@ -17,13 +17,11 @@ export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { const [note, setNote] = useState(null); const [currentColor, setCurrentColor] = useState(null); - useEffect(() => { const retrieveNote = async (noteId: string) => { const result = await froca.getNote(noteId, true); if (result) { setNote(result); - setCurrentColor(result.getLabel("color")?.value ?? ""); } } @@ -34,6 +32,10 @@ export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { } }, []); + useEffect(() => { + setCurrentColor(note?.getLabel("color")?.value ?? ""); + }, [note]); + const onColorCellClicked = (color: string) => { if (note) { attributes.setLabel(note.noteId, "color", color); From 72051c86605989400df6351f350dcbd833e7c038 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 01:24:05 +0200 Subject: [PATCH 13/37] client/note color picker menu item: add a separator to the tree context menu --- apps/client/src/menus/tree_context_menu.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts index b85648a85..b4b19cabd 100644 --- a/apps/client/src/menus/tree_context_menu.ts +++ b/apps/client/src/menus/tree_context_menu.ts @@ -258,6 +258,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener ColorPickerMenuItem({note}) From e6847355e73679d192625357c637a47bb2544dbc Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 02:12:41 +0200 Subject: [PATCH 14/37] client/note color picker menu item: improve the integration with the tree context menu --- apps/client/src/menus/tree_context_menu.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts index b4b19cabd..972f21c11 100644 --- a/apps/client/src/menus/tree_context_menu.ts +++ b/apps/client/src/menus/tree_context_menu.ts @@ -262,7 +262,13 @@ export default class TreeContextMenu implements SelectMenuItemEventListener ColorPickerMenuItem({note}) + componentFn: () => { + if (notOptionsOrHelp && selectedNotes.length === 1) { + return ColorPickerMenuItem({note}); + } else { + return null; + } + } }, ]; return items.filter((row) => row !== null) as MenuItem[]; From d441bccf8b9954130d8a452ef612cb5ac31ac392 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 02:18:14 +0200 Subject: [PATCH 15/37] client/note color picker menu item: refactor --- ...{ColorPickerMenuItem.css => NoteColorPickerMenuItem.css} | 0 ...{ColorPickerMenuItem.tsx => NoteColorPickerMenuItem.tsx} | 6 +++--- apps/client/src/menus/tree_context_menu.ts | 4 ++-- apps/client/src/widgets/collections/board/context_menu.ts | 4 ++-- .../client/src/widgets/collections/calendar/context_menu.ts | 4 ++-- apps/client/src/widgets/collections/geomap/context_menu.ts | 4 ++-- apps/client/src/widgets/collections/table/context_menu.ts | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename apps/client/src/menus/custom-items/{ColorPickerMenuItem.css => NoteColorPickerMenuItem.css} (100%) rename apps/client/src/menus/custom-items/{ColorPickerMenuItem.tsx => NoteColorPickerMenuItem.tsx} (92%) diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.css b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css similarity index 100% rename from apps/client/src/menus/custom-items/ColorPickerMenuItem.css rename to apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css diff --git a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx similarity index 92% rename from apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx rename to apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 030a853e4..3d0b5e7fd 100644 --- a/apps/client/src/menus/custom-items/ColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -1,4 +1,4 @@ -import "./ColorPickerMenuItem.css"; +import "./NoteColorPickerMenuItem.css"; import { useEffect, useState } from "preact/hooks"; import attributes from "../../services/attributes"; import FNote from "../../entities/fnote"; @@ -6,12 +6,12 @@ import froca from "../../services/froca"; const COLORS = ["blue", "green", "cyan", "red", "magenta", "brown", "yellow", ""]; -export interface ColorPickerMenuItemProps { +export interface NoteColorPickerMenuItemProps { /** The target Note instance or its ID string. */ note: FNote | string | null; } -export default function ColorPickerMenuItem(props: ColorPickerMenuItemProps) { +export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemProps) { if (!props.note) return null; const [note, setNote] = useState(null); diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts index 972f21c11..b02eaf970 100644 --- a/apps/client/src/menus/tree_context_menu.ts +++ b/apps/client/src/menus/tree_context_menu.ts @@ -1,4 +1,4 @@ -import ColorPickerMenuItem from "./custom-items/ColorPickerMenuItem.jsx"; +import NoteColorPickerMenuItem from "./custom-items/NoteColorPickerMenuItem.jsx"; import treeService from "../services/tree.js"; import froca from "../services/froca.js"; import clipboard from "../services/clipboard.js"; @@ -264,7 +264,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener { if (notOptionsOrHelp && selectedNotes.length === 1) { - return ColorPickerMenuItem({note}); + return NoteColorPickerMenuItem({note}); } else { return null; } diff --git a/apps/client/src/widgets/collections/board/context_menu.ts b/apps/client/src/widgets/collections/board/context_menu.ts index ba071cf64..551e96a49 100644 --- a/apps/client/src/widgets/collections/board/context_menu.ts +++ b/apps/client/src/widgets/collections/board/context_menu.ts @@ -1,5 +1,5 @@ -import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem"; import FNote from "../../../entities/fnote"; +import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem"; import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu"; import link_context_menu from "../../../menus/link_context_menu"; import attributes from "../../../services/attributes"; @@ -78,7 +78,7 @@ export function openNoteContextMenu(api: Api, event: ContextMenuEvent, note: FNo { kind: "separator" }, { kind: "custom", - componentFn: () => ColorPickerMenuItem({note}) + componentFn: () => NoteColorPickerMenuItem({note}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, note.noteId), diff --git a/apps/client/src/widgets/collections/calendar/context_menu.ts b/apps/client/src/widgets/collections/calendar/context_menu.ts index 31de26de5..5120fcc97 100644 --- a/apps/client/src/widgets/collections/calendar/context_menu.ts +++ b/apps/client/src/widgets/collections/calendar/context_menu.ts @@ -1,4 +1,4 @@ -import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem"; +import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem"; import FNote from "../../../entities/fnote"; import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu"; import link_context_menu from "../../../menus/link_context_menu"; @@ -40,7 +40,7 @@ export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, par { kind: "separator" }, { kind: "custom", - componentFn: () => ColorPickerMenuItem({note: noteId}) + componentFn: () => NoteColorPickerMenuItem({note: noteId}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, noteId), diff --git a/apps/client/src/widgets/collections/geomap/context_menu.ts b/apps/client/src/widgets/collections/geomap/context_menu.ts index a0a6c8fc6..c4cf6ebf4 100644 --- a/apps/client/src/widgets/collections/geomap/context_menu.ts +++ b/apps/client/src/widgets/collections/geomap/context_menu.ts @@ -2,7 +2,7 @@ import type { LatLng, LeafletMouseEvent } from "leaflet"; import appContext, { type CommandMappings } from "../../../components/app_context.js"; import contextMenu, { type MenuItem } from "../../../menus/context_menu.js"; import linkContextMenu from "../../../menus/link_context_menu.js"; -import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem.jsx"; +import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem.jsx"; import { t } from "../../../services/i18n.js"; import { createNewNote } from "./api.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; @@ -23,7 +23,7 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent, is { kind: "separator"}, { kind: "custom", - componentFn: () => ColorPickerMenuItem({note: noteId}) + componentFn: () => NoteColorPickerMenuItem({note: noteId}) } ]; } diff --git a/apps/client/src/widgets/collections/table/context_menu.ts b/apps/client/src/widgets/collections/table/context_menu.ts index 11d4477ca..1218dbf6c 100644 --- a/apps/client/src/widgets/collections/table/context_menu.ts +++ b/apps/client/src/widgets/collections/table/context_menu.ts @@ -6,8 +6,8 @@ import { TableData } from "./rows.js"; import link_context_menu from "../../../menus/link_context_menu.js"; import froca from "../../../services/froca.js"; import branches from "../../../services/branches.js"; -import ColorPickerMenuItem from "../../../menus/custom-items/ColorPickerMenuItem.jsx"; import Component from "../../../components/component.js"; +import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem.jsx"; import { RefObject } from "preact"; export function useContextMenu(parentNote: FNote, parentComponent: Component | null | undefined, tabulator: RefObject): Partial { @@ -224,7 +224,7 @@ export function showRowContextMenu(parentComponent: Component, e: MouseEvent, ro { kind: "separator"}, { kind: "custom", - componentFn: () => ColorPickerMenuItem({note: rowData.noteId}) + componentFn: () => NoteColorPickerMenuItem({note: rowData.noteId}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, rowData.noteId), From 01d6dee9fcca4eb017c6fcc20373c03ac5784f1b Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 17:23:57 +0200 Subject: [PATCH 16/37] client/note color picker menu item: improve label handling --- .../custom-items/NoteColorPickerMenuItem.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 3d0b5e7fd..60757aa15 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -4,7 +4,7 @@ import attributes from "../../services/attributes"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; -const COLORS = ["blue", "green", "cyan", "red", "magenta", "brown", "yellow", ""]; +const COLORS = ["blue", "green", "cyan", "red", "magenta", "brown", "yellow", null]; export interface NoteColorPickerMenuItemProps { /** The target Note instance or its ID string. */ @@ -33,17 +33,23 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr }, []); useEffect(() => { - setCurrentColor(note?.getLabel("color")?.value ?? ""); + setCurrentColor(note?.getLabel("color")?.value ?? null); }, [note]); - const onColorCellClicked = (color: string) => { + const onColorCellClicked = (color: string | null) => { if (note) { - attributes.setLabel(note.noteId, "color", color); + if (color !== null) { + attributes.setLabel(note.noteId, "color", color); + } else { + attributes.removeOwnedLabelByName(note, "color"); + } + setCurrentColor(color); } } - return
        + return
        {e.stopPropagation()}}> {COLORS.map((color) => ( void @@ -63,7 +69,7 @@ interface ColorCellProps { function ColorCell(props: ColorCellProps) { return
        ; } \ No newline at end of file From 5ecd8b41e50804195720729f26d120f9c2e97b32 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 19:05:20 +0200 Subject: [PATCH 17/37] client/note color picker menu item: add support to select a custom color --- .../custom-items/NoteColorPickerMenuItem.tsx | 77 +++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 60757aa15..14a908cc8 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -1,5 +1,6 @@ import "./NoteColorPickerMenuItem.css"; -import { useEffect, useState } from "preact/hooks"; +import { useEffect, useRef, useState} from "preact/hooks"; +import {ComponentChildren} from "preact"; import attributes from "../../services/attributes"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; @@ -55,21 +56,87 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr color={color} isSelected={(color === currentColor)} isDisabled={(note === null)} - onClick={() => onColorCellClicked(color)} /> + onSelect={() => onColorCellClicked(color)} /> ))} + +
        } interface ColorCellProps { + children?: ComponentChildren, + className?: string; color: string | null, isSelected: boolean, isDisabled?: boolean, - onClick?: () => void + onSelect?: (color: string | null) => void } function ColorCell(props: ColorCellProps) { - return
        + onClick={() => props.onSelect?.(props.color)}> + {props.children}
        ; +} + +function CustomColorCell(props: ColorCellProps) { + const colorInput = useRef(null); + let colorInputDebouncer: Debouncer; + + useEffect(() => { + colorInputDebouncer = new Debouncer(500, (color) => { + props.onSelect?.(color); + }); + + return () => { + colorInputDebouncer.destroy(); + } + }); + + return <> + {colorInput.current?.click()}}> + + {colorInputDebouncer.updateValue(colorInput.current?.value ?? null)}} + style="width: 0; height: 0; opacity: 0" /> + + +} + +type DebouncerCallback = (value: T) => void; + +class Debouncer { + + private debounceInterval: number; + private callback: DebouncerCallback; + private lastValue: T | undefined; + private timeoutId: any | null = null; + + constructor(debounceInterval: number, onUpdate: DebouncerCallback) { + this.debounceInterval = debounceInterval; + this.callback = onUpdate; + } + + updateValue(value: T) { + this.lastValue = value; + this.destroy(); + this.timeoutId = setTimeout(this.reportUpdate.bind(this), this.debounceInterval); + } + + destroy() { + if (this.timeoutId !== null) { + clearTimeout(this.timeoutId); + } + } + + private reportUpdate() { + if (this.lastValue !== undefined) { + this.callback(this.lastValue); + } + } } \ No newline at end of file From 45747183e7e329eae42a05131e2a5a4f7b33685a Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 19:20:17 +0200 Subject: [PATCH 18/37] client/refactor: extract the debouncer to a separate module --- .../custom-items/NoteColorPickerMenuItem.tsx | 34 +------------------ apps/client/src/utils/debouncer.ts | 32 +++++++++++++++++ 2 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 apps/client/src/utils/debouncer.ts diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 14a908cc8..b87387a8f 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -2,6 +2,7 @@ import "./NoteColorPickerMenuItem.css"; import { useEffect, useRef, useState} from "preact/hooks"; import {ComponentChildren} from "preact"; import attributes from "../../services/attributes"; +import Debouncer from "../../utils/debouncer"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; @@ -106,37 +107,4 @@ function CustomColorCell(props: ColorCellProps) { style="width: 0; height: 0; opacity: 0" /> -} - -type DebouncerCallback = (value: T) => void; - -class Debouncer { - - private debounceInterval: number; - private callback: DebouncerCallback; - private lastValue: T | undefined; - private timeoutId: any | null = null; - - constructor(debounceInterval: number, onUpdate: DebouncerCallback) { - this.debounceInterval = debounceInterval; - this.callback = onUpdate; - } - - updateValue(value: T) { - this.lastValue = value; - this.destroy(); - this.timeoutId = setTimeout(this.reportUpdate.bind(this), this.debounceInterval); - } - - destroy() { - if (this.timeoutId !== null) { - clearTimeout(this.timeoutId); - } - } - - private reportUpdate() { - if (this.lastValue !== undefined) { - this.callback(this.lastValue); - } - } } \ No newline at end of file diff --git a/apps/client/src/utils/debouncer.ts b/apps/client/src/utils/debouncer.ts new file mode 100644 index 000000000..b37c4118a --- /dev/null +++ b/apps/client/src/utils/debouncer.ts @@ -0,0 +1,32 @@ +export type DebouncerCallback = (value: T) => void; + +export default class Debouncer { + + private debounceInterval: number; + private callback: DebouncerCallback; + private lastValue: T | undefined; + private timeoutId: any | null = null; + + constructor(debounceInterval: number, onUpdate: DebouncerCallback) { + this.debounceInterval = debounceInterval; + this.callback = onUpdate; + } + + updateValue(value: T) { + this.lastValue = value; + this.destroy(); + this.timeoutId = setTimeout(this.reportUpdate.bind(this), this.debounceInterval); + } + + destroy() { + if (this.timeoutId !== null) { + clearTimeout(this.timeoutId); + } + } + + private reportUpdate() { + if (this.lastValue !== undefined) { + this.callback(this.lastValue); + } + } +} \ No newline at end of file From c81aef6d0596f1f065a3f2b934b3bb50ff84ea4f Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 22:18:15 +0200 Subject: [PATCH 19/37] client/note color picker menu item: update the color palette --- .../src/menus/custom-items/NoteColorPickerMenuItem.css | 4 ++-- .../src/menus/custom-items/NoteColorPickerMenuItem.tsx | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css index 1f56926a9..e94511138 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css @@ -4,8 +4,8 @@ } .color-picker-menu-item > .color-cell { - width: 16px; - height: 16px; + width: 13px; + height: 13px; border-radius: 4px; background: transparent; } diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index b87387a8f..66b4010d9 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -6,7 +6,10 @@ import Debouncer from "../../utils/debouncer"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; -const COLORS = ["blue", "green", "cyan", "red", "magenta", "brown", "yellow", null]; +const COLORS = [ + "#e64d4d", "#e6994d", "#e5e64d", "#99e64d", "#4de64d", "#4de699", + "#4de5e6", "#4d99e6", "#4d4de6", "#994de6", null +]; export interface NoteColorPickerMenuItemProps { /** The target Note instance or its ID string. */ From 79dc5e4344de27f2785c7ac54a86519480a230e6 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Wed, 19 Nov 2025 00:53:18 +0200 Subject: [PATCH 20/37] client/note color picker menu item: tweak style --- .../custom-items/NoteColorPickerMenuItem.css | 57 ++++++++++++++++--- .../custom-items/NoteColorPickerMenuItem.tsx | 27 +++++++-- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css index e94511138..35029699f 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css @@ -1,19 +1,60 @@ .color-picker-menu-item { display: flex; - gap: 10px; + gap: 8px; + justify-content: space-between; } -.color-picker-menu-item > .color-cell { - width: 13px; - height: 13px; +.color-picker-menu-item .color-cell { + width: 14px; + height: 14px; border-radius: 4px; - background: transparent; + background: var(--color); } -.color-picker-menu-item > .color-cell.disabled-color-cell { +.color-picker-menu-item .color-cell.disabled-color-cell { cursor: not-allowed; } -.color-picker-menu-item > .color-cell.selected { - outline: 2px solid royalblue; +.color-picker-menu-item .color-cell.selected { + outline: 2px solid var(--color); + outline-offset: 2px; +} + +.color-cell-reset::before, +.custom-color-cell::before { + position: absolute; + display: flex; + top: 0; + left: 0; + right: 0; + bottom: 0; + font-size: 14px; + justify-content: center; + align-items: center; + font-family: boxicons; + color: black; +} + +.color-cell-reset { + position: relative; + --color: rgba(255, 255, 255, .4); +} + +.color-cell-reset::before { + content: "\ec8d"; + mix-blend-mode: normal; + color: black; +} + +.custom-color-cell { + position: relative; + display: flex; + justify-content: center; + background: var(--color); +} + +.custom-color-cell::before { + content: "\ed35"; + color: var(--foreground); + font-size: 16px; } \ No newline at end of file diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 66b4010d9..3db626013 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -2,13 +2,14 @@ import "./NoteColorPickerMenuItem.css"; import { useEffect, useRef, useState} from "preact/hooks"; import {ComponentChildren} from "preact"; import attributes from "../../services/attributes"; +import Color, { ColorInstance } from "color"; import Debouncer from "../../utils/debouncer"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; const COLORS = [ - "#e64d4d", "#e6994d", "#e5e64d", "#99e64d", "#4de64d", "#4de699", - "#4de5e6", "#4d99e6", "#4d4de6", "#994de6", null + null, "#e64d4d", "#e6994d", "#e5e64d", "#99e64d", "#4de64d", "#4de699", + "#4de5e6", "#4d99e6", "#4d4de6", "#994de6" ]; export interface NoteColorPickerMenuItemProps { @@ -57,6 +58,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr onClick={(e) => {e.stopPropagation()}}> {COLORS.map((color) => ( props.onSelect?.(props.color)}> {props.children}
        ; @@ -98,7 +100,7 @@ function CustomColorCell(props: ColorCellProps) { } }); - return <> + return
        {colorInput.current?.click()}}> @@ -109,5 +111,20 @@ function CustomColorCell(props: ColorCellProps) { onChange={() => {colorInputDebouncer.updateValue(colorInput.current?.value ?? null)}} style="width: 0; height: 0; opacity: 0" /> - +
        +} + +function ensureContrast(color: string | null) { + if (color === null) return "inherit"; + + const colorHsl = Color(color).hsl(); + let l = colorHsl.lightness(); + + if (l >= 40) { + l = 0; + } else { + l = 100 + } + + return colorHsl.saturationl(0).lightness(l).hex(); } \ No newline at end of file From c25859cee9d260528a0db1aca32b5f128d85b148 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Wed, 19 Nov 2025 00:57:45 +0200 Subject: [PATCH 21/37] client/debouncer: report pending updates before destroying --- apps/client/src/utils/debouncer.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/utils/debouncer.ts b/apps/client/src/utils/debouncer.ts index b37c4118a..9057d039f 100644 --- a/apps/client/src/utils/debouncer.ts +++ b/apps/client/src/utils/debouncer.ts @@ -14,12 +14,15 @@ export default class Debouncer { updateValue(value: T) { this.lastValue = value; - this.destroy(); + if (this.timeoutId !== null) { + clearTimeout(this.timeoutId); + } this.timeoutId = setTimeout(this.reportUpdate.bind(this), this.debounceInterval); } destroy() { if (this.timeoutId !== null) { + this.reportUpdate(); clearTimeout(this.timeoutId); } } From 828a786414b4d5b744d1d68dfe7e286b6944ae56 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 01:35:44 +0200 Subject: [PATCH 22/37] client/note color picker menu item: improve --- .../custom-items/NoteColorPickerMenuItem.css | 23 ++++-- .../custom-items/NoteColorPickerMenuItem.tsx | 78 ++++++++++++++---- .../src/menus/custom-items/custom-culor.png | Bin 0 -> 1259 bytes 3 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 apps/client/src/menus/custom-items/custom-culor.png diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css index 35029699f..d062ea577 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css @@ -4,18 +4,22 @@ justify-content: space-between; } -.color-picker-menu-item .color-cell { +.color-picker-menu-item .color-cell { width: 14px; height: 14px; border-radius: 4px; - background: var(--color); + background-color: var(--color); } -.color-picker-menu-item .color-cell.disabled-color-cell { +.color-picker-menu-item .color-cell:not(.selected):hover { + transform: scale(1.2); +} + +.color-picker-menu-item .color-cell.disabled-color-cell { cursor: not-allowed; } -.color-picker-menu-item .color-cell.selected { +.color-picker-menu-item .color-cell.selected { outline: 2px solid var(--color); outline-offset: 2px; } @@ -28,7 +32,7 @@ left: 0; right: 0; bottom: 0; - font-size: 14px; + font-size: 18px; justify-content: center; align-items: center; font-family: boxicons; @@ -50,9 +54,16 @@ position: relative; display: flex; justify-content: center; - background: var(--color); + } +.custom-color-cell.custom-color-cell-empty { + background-image: url(./custom-culor.png); + background-size: cover; + --foreground: transparent; +} + + .custom-color-cell::before { content: "\ed35"; color: var(--foreground); diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 3db626013..96f7eba54 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -1,5 +1,5 @@ import "./NoteColorPickerMenuItem.css"; -import { useEffect, useRef, useState} from "preact/hooks"; +import { useCallback, useEffect, useRef, useState} from "preact/hooks"; import {ComponentChildren} from "preact"; import attributes from "../../services/attributes"; import Color, { ColorInstance } from "color"; @@ -22,12 +22,13 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr const [note, setNote] = useState(null); const [currentColor, setCurrentColor] = useState(null); + const [isCustomColor, setIsCustomColor] = useState(false); useEffect(() => { const retrieveNote = async (noteId: string) => { - const result = await froca.getNote(noteId, true); - if (result) { - setNote(result); + const noteInstance = await froca.getNote(noteId, true); + if (noteInstance) { + setNote(noteInstance); } } @@ -39,10 +40,27 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr }, []); useEffect(() => { - setCurrentColor(note?.getLabel("color")?.value ?? null); + const colorLabel = note?.getLabel("color")?.value ?? null; + if (colorLabel) { + let color: ColorInstance | null = null; + + try { + color = new Color(colorLabel); + } catch(ex) { + console.error(ex); + } + + if (color) { + setCurrentColor(color.hex().toLowerCase()); + } + } }, [note]); - const onColorCellClicked = (color: string | null) => { + useEffect(() => { + setIsCustomColor(COLORS.indexOf(currentColor) === -1); + }, [currentColor]) + + const onColorCellClicked = useCallback((color: string | null) => { if (note) { if (color !== null) { attributes.setLabel(note.noteId, "color", color); @@ -52,7 +70,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr setCurrentColor(color); } - } + }, [note, currentColor]); return
        {e.stopPropagation()}}> @@ -65,7 +83,9 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr onSelect={() => onColorCellClicked(color)} /> ))} - +
        } @@ -87,28 +107,50 @@ function ColorCell(props: ColorCellProps) { } function CustomColorCell(props: ColorCellProps) { + const [pickedColor, setPickedColor] = useState(null); const colorInput = useRef(null); - let colorInputDebouncer: Debouncer; + const colorInputDebouncer = useRef | null>(null); + const callbackRef = useRef(props.onSelect); useEffect(() => { - colorInputDebouncer = new Debouncer(500, (color) => { - props.onSelect?.(color); + colorInputDebouncer.current = new Debouncer(500, (color) => { + callbackRef.current?.(color); + setPickedColor(color); }); return () => { - colorInputDebouncer.destroy(); + colorInputDebouncer.current?.destroy(); } - }); + }, []); + + useEffect(() => { + if (props.isSelected && pickedColor === null) { + setPickedColor(props.color); + } + }, [props.isSelected]) + + useEffect(() => { + callbackRef.current = props.onSelect; + }, [props.onSelect]); + + const onSelect = useCallback(() => { + if (pickedColor !== null) { + callbackRef.current?.(pickedColor); + } + + colorInput.current?.click(); + }, [pickedColor]); return
        - {colorInput.current?.click()}}> + {colorInputDebouncer.updateValue(colorInput.current?.value ?? null)}} + value={pickedColor ?? props.color ?? "#40bfbf"} + onChange={() => {colorInputDebouncer.current?.updateValue(colorInput.current?.value ?? null)}} style="width: 0; height: 0; opacity: 0" />
        diff --git a/apps/client/src/menus/custom-items/custom-culor.png b/apps/client/src/menus/custom-items/custom-culor.png new file mode 100644 index 0000000000000000000000000000000000000000..4275c56d2e804b1ad6d1057a9b4ef28534491031 GIT binary patch literal 1259 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s1SJ3FdmIK*jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1enP_o1|q9iy!t)x7$D3u`~F*C13&(AeP!Bo#s z&(N@oL+l(-(T>yz&ooa@Ed~xChm}E!k(GfF$npYWX($`y91TWhusFy;hKx)Mf?v*lQd?d7fo&prj{uQbo{ikU4Ww!nAkig0G5O#-Ip`Skl- z^cqVpHf+DByGdYezTdS=nHPe!lBW1MXZ4<1p8hTC^3vTq_G~hd+jBC@?#NuDTL*or zg0!x$shB(WRhIP@FYnot&ptKX?knaq)2rz1qbZl7rvQ~N^UGw#+h^ z`fRCN$f~8~T(#9|Mb=x+IzM3%?vpmUI5T?D`6AWYT^DkB_+NzIS+^&2wY8XEVe+v( ziEB&^6Ha)kdT+QQAz_rDaNg_a$CZ%}r+jT&tYynD`O-Xrdi}aH zF)0D6kzQx+Ww>wYN%<1Hf5{vZhu}WRk1@YKT>d%#`_D%c*UDEGSpR){?#^$X)lK2y zb6?fG{Pwr3@@pyU-N&r`t@pQg&9~Q&yZ4xHdhy+sgQt^z@*jU(V4!(wullKk`BT}J z-k!hpbhOmQ^Yu1U|J?Pi+ZnxQ&uQIt1)asgH$}ckyc4^5IHi8xlgiRfnj3FDzx1~M zHn-}(jZfCkK6<%d#QaA?cPNkU`7LTsRJObj4=byjxM!aB``oai%Wr4r{{C~?I5B;7 zMbYsmR?}}ju*qI?Y|AXK#iqrRR_*$tK5hPK_lFsACtN+ZZ@*Qh>d#h|C+dFvW=eDM znoUt(7uoHNnGsqpd01t_#ngg`E%Rrdne=kb&M&hsc$cjcT`ZmRQSSAcuuh4>zcU`} zRI&Q7q-&Ri$Hu}hFWz(iS8-SO{Pp#R@X@EI3ezJ>HD6sgsT!QcK2M|mqUgrvFWlbM zOtYuHSIP_6nDnwYP;ODt)Ku=&(gkbmUe8INd_YQ@Hzrm-Idk3Y%%JjpyY%<>#wGS1 z*zj=2uG%ZPwnEM}nJw4;uFne!G!ANOX?r~D?Yei{mmf)+Te*C1X*kFGLs#Q@F0R=X jHO0U1%b9Xt7x!QPAGsO_tNzdJ1!Y%HS3j3^P6 Date: Thu, 20 Nov 2025 17:42:36 +0200 Subject: [PATCH 23/37] client/note color picker menu item: add a new color to the palette --- apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 96f7eba54..3e38c7809 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -9,7 +9,7 @@ import froca from "../../services/froca"; const COLORS = [ null, "#e64d4d", "#e6994d", "#e5e64d", "#99e64d", "#4de64d", "#4de699", - "#4de5e6", "#4d99e6", "#4d4de6", "#994de6" + "#4de5e6", "#4d99e6", "#4d4de6", "#994de6", "#e64db3" ]; export interface NoteColorPickerMenuItemProps { From fb163367d4105305d2cad468f99e9ce35c40700b Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 17:50:32 +0200 Subject: [PATCH 24/37] client/note color picker menu item: refactor --- .../custom-items/NoteColorPickerMenuItem.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 3e38c7809..3733b219b 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -8,7 +8,7 @@ import FNote from "../../entities/fnote"; import froca from "../../services/froca"; const COLORS = [ - null, "#e64d4d", "#e6994d", "#e5e64d", "#99e64d", "#4de64d", "#4de699", + "#e64d4d", "#e6994d", "#e5e64d", "#99e64d", "#4de64d", "#4de699", "#4de5e6", "#4d99e6", "#4d4de6", "#994de6", "#e64db3" ]; @@ -57,7 +57,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr }, [note]); useEffect(() => { - setIsCustomColor(COLORS.indexOf(currentColor) === -1); + setIsCustomColor(currentColor !== null && COLORS.indexOf(currentColor) === -1); }, [currentColor]) const onColorCellClicked = useCallback((color: string | null) => { @@ -74,17 +74,25 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr return
        {e.stopPropagation()}}> + + + + {COLORS.map((color) => ( onColorCellClicked(color)} /> + onSelect={onColorCellClicked} /> ))}
        } From 422b324f7c2c6149d25328ea10d879f22cc600a8 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 18:15:56 +0200 Subject: [PATCH 25/37] client/note color picker menu item: add tooltips --- .../src/menus/custom-items/NoteColorPickerMenuItem.tsx | 10 ++++++++-- apps/client/src/translations/en/translation.json | 5 +++++ apps/client/src/translations/ro/translation.json | 5 +++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 3733b219b..1917c587c 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -1,4 +1,5 @@ import "./NoteColorPickerMenuItem.css"; +import { t } from "../../services/i18n"; import { useCallback, useEffect, useRef, useState} from "preact/hooks"; import {ComponentChildren} from "preact"; import attributes from "../../services/attributes"; @@ -76,6 +77,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr onClick={(e) => {e.stopPropagation()}}> ( ))} - @@ -99,7 +103,8 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr interface ColorCellProps { children?: ComponentChildren, - className?: string; + className?: string, + tooltip?: string, color: string | null, isSelected: boolean, isDisabled?: boolean, @@ -109,6 +114,7 @@ interface ColorCellProps { function ColorCell(props: ColorCellProps) { return
        props.onSelect?.(props.color)}> {props.children}
        ; diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 54025d690..aa8c5926c 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2093,5 +2093,10 @@ }, "collections": { "rendering_error": "Unable to show content due to an error." + }, + "note-color": { + "clear-color": "Clear note color", + "set-color": "Set note color", + "set-custom-color": "Set custom note color" } } diff --git a/apps/client/src/translations/ro/translation.json b/apps/client/src/translations/ro/translation.json index b0e412b35..5750bb331 100644 --- a/apps/client/src/translations/ro/translation.json +++ b/apps/client/src/translations/ro/translation.json @@ -2095,5 +2095,10 @@ }, "calendar_view": { "delete_note": "Șterge notița..." + }, + "note-color": { + "clear-color": "Înlăturați culoarea notiței", + "set-color": "Setați culoarea notiței", + "set-custom-color": "Setați culoare personalizată pentru notiță" } } From f15e048763fa40dbc64af14b4d5a537650799b59 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 18:43:16 +0200 Subject: [PATCH 26/37] client/note color picker menu item: refactor stylesheet --- .../custom-items/NoteColorPickerMenuItem.css | 48 ++++++++++++------ .../{custom-culor.png => custom-color.png} | Bin 2 files changed, 32 insertions(+), 16 deletions(-) rename apps/client/src/menus/custom-items/{custom-culor.png => custom-color.png} (100%) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css index d062ea577..09eb356b8 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css @@ -1,3 +1,9 @@ +:root { + --note-color-picker-clear-color-cell-background: var(--primary-button-background-color); + --note-color-picker-clear-color-cell-color: var(--main-background-color); + --note-color-picker-clear-color-cell-selection-outline-color: var(--primary-button-border-color); +} + .color-picker-menu-item { display: flex; gap: 8px; @@ -5,8 +11,10 @@ } .color-picker-menu-item .color-cell { - width: 14px; - height: 14px; + --color-picker-cell-size: 14px; + + width: var(--color-picker-cell-size); + height: var(--color-picker-cell-size); border-radius: 4px; background-color: var(--color); } @@ -20,51 +28,59 @@ } .color-picker-menu-item .color-cell.selected { - outline: 2px solid var(--color); + outline: 2px solid var(--outline-color, var(--color)); outline-offset: 2px; } -.color-cell-reset::before, -.custom-color-cell::before { +.color-picker-menu-item .color-cell-reset::before, +.color-picker-menu-item .custom-color-cell::before { position: absolute; display: flex; top: 0; left: 0; right: 0; bottom: 0; - font-size: 18px; + font-size: calc(var(--color-picker-cell-size) * 1.3); justify-content: center; align-items: center; font-family: boxicons; - color: black; } -.color-cell-reset { +/* + * RESET COLOR CELL + */ + +.color-picker-menu-item .color-cell-reset { + --color: var(--note-color-picker-clear-color-cell-background); + --outline-color: var(--note-color-picker-clear-color-cell-selection-outline-color); + position: relative; - --color: rgba(255, 255, 255, .4); } -.color-cell-reset::before { +.color-picker-menu-item .color-cell-reset::before { content: "\ec8d"; mix-blend-mode: normal; - color: black; + color: var(--note-color-picker-clear-color-cell-color); } -.custom-color-cell { +/* + * CUSTOM COLOR CELL + */ + +.color-picker-menu-item .custom-color-cell { position: relative; display: flex; justify-content: center; } -.custom-color-cell.custom-color-cell-empty { - background-image: url(./custom-culor.png); +.color-picker-menu-item .custom-color-cell.custom-color-cell-empty { + background-image: url(./custom-color.png); background-size: cover; --foreground: transparent; } - -.custom-color-cell::before { +.color-picker-menu-item .custom-color-cell::before { content: "\ed35"; color: var(--foreground); font-size: 16px; diff --git a/apps/client/src/menus/custom-items/custom-culor.png b/apps/client/src/menus/custom-items/custom-color.png similarity index 100% rename from apps/client/src/menus/custom-items/custom-culor.png rename to apps/client/src/menus/custom-items/custom-color.png From 1de9f715fa95bc751e6aedf1a3d4f146ead268ca Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 18:46:27 +0200 Subject: [PATCH 27/37] client/note color picker: refactor --- .../custom-items/NoteColorPickerMenuItem.css | 24 +++++++++---------- .../custom-items/NoteColorPickerMenuItem.tsx | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css index 09eb356b8..93a56ec0c 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css @@ -4,13 +4,13 @@ --note-color-picker-clear-color-cell-selection-outline-color: var(--primary-button-border-color); } -.color-picker-menu-item { +.note-color-picker { display: flex; gap: 8px; justify-content: space-between; } -.color-picker-menu-item .color-cell { +.note-color-picker .color-cell { --color-picker-cell-size: 14px; width: var(--color-picker-cell-size); @@ -19,21 +19,21 @@ background-color: var(--color); } -.color-picker-menu-item .color-cell:not(.selected):hover { +.note-color-picker .color-cell:not(.selected):hover { transform: scale(1.2); } -.color-picker-menu-item .color-cell.disabled-color-cell { +.note-color-picker .color-cell.disabled-color-cell { cursor: not-allowed; } -.color-picker-menu-item .color-cell.selected { +.note-color-picker .color-cell.selected { outline: 2px solid var(--outline-color, var(--color)); outline-offset: 2px; } -.color-picker-menu-item .color-cell-reset::before, -.color-picker-menu-item .custom-color-cell::before { +.note-color-picker .color-cell-reset::before, +.note-color-picker .custom-color-cell::before { position: absolute; display: flex; top: 0; @@ -50,14 +50,14 @@ * RESET COLOR CELL */ -.color-picker-menu-item .color-cell-reset { +.note-color-picker .color-cell-reset { --color: var(--note-color-picker-clear-color-cell-background); --outline-color: var(--note-color-picker-clear-color-cell-selection-outline-color); position: relative; } -.color-picker-menu-item .color-cell-reset::before { +.note-color-picker .color-cell-reset::before { content: "\ec8d"; mix-blend-mode: normal; color: var(--note-color-picker-clear-color-cell-color); @@ -67,20 +67,20 @@ * CUSTOM COLOR CELL */ -.color-picker-menu-item .custom-color-cell { +.note-color-picker .custom-color-cell { position: relative; display: flex; justify-content: center; } -.color-picker-menu-item .custom-color-cell.custom-color-cell-empty { +.note-color-picker .custom-color-cell.custom-color-cell-empty { background-image: url(./custom-color.png); background-size: cover; --foreground: transparent; } -.color-picker-menu-item .custom-color-cell::before { +.note-color-picker .custom-color-cell::before { content: "\ed35"; color: var(--foreground); font-size: 16px; diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 1917c587c..c74f83919 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -73,7 +73,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr } }, [note, currentColor]); - return
        {e.stopPropagation()}}> Date: Thu, 20 Nov 2025 18:49:47 +0200 Subject: [PATCH 28/37] client/note color picker: refactor --- .../{NoteColorPickerMenuItem.css => NoteColorPicker.css} | 0 .../{NoteColorPickerMenuItem.tsx => NoteColorPicker.tsx} | 2 +- apps/client/src/menus/tree_context_menu.ts | 4 ++-- apps/client/src/widgets/collections/board/context_menu.ts | 4 ++-- apps/client/src/widgets/collections/calendar/context_menu.ts | 4 ++-- apps/client/src/widgets/collections/geomap/context_menu.ts | 4 ++-- apps/client/src/widgets/collections/table/context_menu.ts | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) rename apps/client/src/menus/custom-items/{NoteColorPickerMenuItem.css => NoteColorPicker.css} (100%) rename apps/client/src/menus/custom-items/{NoteColorPickerMenuItem.tsx => NoteColorPicker.tsx} (99%) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css b/apps/client/src/menus/custom-items/NoteColorPicker.css similarity index 100% rename from apps/client/src/menus/custom-items/NoteColorPickerMenuItem.css rename to apps/client/src/menus/custom-items/NoteColorPicker.css diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPicker.tsx similarity index 99% rename from apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx rename to apps/client/src/menus/custom-items/NoteColorPicker.tsx index c74f83919..0c0941adc 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPicker.tsx @@ -1,4 +1,4 @@ -import "./NoteColorPickerMenuItem.css"; +import "./NoteColorPicker.css"; import { t } from "../../services/i18n"; import { useCallback, useEffect, useRef, useState} from "preact/hooks"; import {ComponentChildren} from "preact"; diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts index b02eaf970..35480cfc6 100644 --- a/apps/client/src/menus/tree_context_menu.ts +++ b/apps/client/src/menus/tree_context_menu.ts @@ -1,4 +1,4 @@ -import NoteColorPickerMenuItem from "./custom-items/NoteColorPickerMenuItem.jsx"; +import NoteColorPicker from "./custom-items/NoteColorPicker.jsx"; import treeService from "../services/tree.js"; import froca from "../services/froca.js"; import clipboard from "../services/clipboard.js"; @@ -264,7 +264,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener { if (notOptionsOrHelp && selectedNotes.length === 1) { - return NoteColorPickerMenuItem({note}); + return NoteColorPicker({note}); } else { return null; } diff --git a/apps/client/src/widgets/collections/board/context_menu.ts b/apps/client/src/widgets/collections/board/context_menu.ts index 551e96a49..c834b4c8d 100644 --- a/apps/client/src/widgets/collections/board/context_menu.ts +++ b/apps/client/src/widgets/collections/board/context_menu.ts @@ -1,5 +1,5 @@ import FNote from "../../../entities/fnote"; -import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem"; +import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker"; import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu"; import link_context_menu from "../../../menus/link_context_menu"; import attributes from "../../../services/attributes"; @@ -78,7 +78,7 @@ export function openNoteContextMenu(api: Api, event: ContextMenuEvent, note: FNo { kind: "separator" }, { kind: "custom", - componentFn: () => NoteColorPickerMenuItem({note}) + componentFn: () => NoteColorPicker({note}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, note.noteId), diff --git a/apps/client/src/widgets/collections/calendar/context_menu.ts b/apps/client/src/widgets/collections/calendar/context_menu.ts index 5120fcc97..0195d131c 100644 --- a/apps/client/src/widgets/collections/calendar/context_menu.ts +++ b/apps/client/src/widgets/collections/calendar/context_menu.ts @@ -1,4 +1,4 @@ -import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem"; +import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker"; import FNote from "../../../entities/fnote"; import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu"; import link_context_menu from "../../../menus/link_context_menu"; @@ -40,7 +40,7 @@ export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, par { kind: "separator" }, { kind: "custom", - componentFn: () => NoteColorPickerMenuItem({note: noteId}) + componentFn: () => NoteColorPicker({note: noteId}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, noteId), diff --git a/apps/client/src/widgets/collections/geomap/context_menu.ts b/apps/client/src/widgets/collections/geomap/context_menu.ts index c4cf6ebf4..f4161fad9 100644 --- a/apps/client/src/widgets/collections/geomap/context_menu.ts +++ b/apps/client/src/widgets/collections/geomap/context_menu.ts @@ -2,7 +2,7 @@ import type { LatLng, LeafletMouseEvent } from "leaflet"; import appContext, { type CommandMappings } from "../../../components/app_context.js"; import contextMenu, { type MenuItem } from "../../../menus/context_menu.js"; import linkContextMenu from "../../../menus/link_context_menu.js"; -import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem.jsx"; +import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker.jsx"; import { t } from "../../../services/i18n.js"; import { createNewNote } from "./api.js"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; @@ -23,7 +23,7 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent, is { kind: "separator"}, { kind: "custom", - componentFn: () => NoteColorPickerMenuItem({note: noteId}) + componentFn: () => NoteColorPicker({note: noteId}) } ]; } diff --git a/apps/client/src/widgets/collections/table/context_menu.ts b/apps/client/src/widgets/collections/table/context_menu.ts index 1218dbf6c..80b1c93c2 100644 --- a/apps/client/src/widgets/collections/table/context_menu.ts +++ b/apps/client/src/widgets/collections/table/context_menu.ts @@ -7,7 +7,7 @@ import link_context_menu from "../../../menus/link_context_menu.js"; import froca from "../../../services/froca.js"; import branches from "../../../services/branches.js"; import Component from "../../../components/component.js"; -import NoteColorPickerMenuItem from "../../../menus/custom-items/NoteColorPickerMenuItem.jsx"; +import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker.jsx"; import { RefObject } from "preact"; export function useContextMenu(parentNote: FNote, parentComponent: Component | null | undefined, tabulator: RefObject): Partial { @@ -224,7 +224,7 @@ export function showRowContextMenu(parentComponent: Component, e: MouseEvent, ro { kind: "separator"}, { kind: "custom", - componentFn: () => NoteColorPickerMenuItem({note: rowData.noteId}) + componentFn: () => NoteColorPicker({note: rowData.noteId}) } ], selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, rowData.noteId), From e4c928ae877b88e1e2d5e42e92402197130d04ff Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 19:11:51 +0200 Subject: [PATCH 29/37] client/note color picker: refactor --- .../menus/custom-items/NoteColorPicker.tsx | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPicker.tsx b/apps/client/src/menus/custom-items/NoteColorPicker.tsx index 0c0941adc..16a17655f 100644 --- a/apps/client/src/menus/custom-items/NoteColorPicker.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPicker.tsx @@ -8,17 +8,17 @@ import Debouncer from "../../utils/debouncer"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; -const COLORS = [ +const COLOR_PALETTE = [ "#e64d4d", "#e6994d", "#e5e64d", "#99e64d", "#4de64d", "#4de699", "#4de5e6", "#4d99e6", "#4d4de6", "#994de6", "#e64db3" ]; -export interface NoteColorPickerMenuItemProps { +export interface NoteColorPickerProps { /** The target Note instance or its ID string. */ note: FNote | string | null; } -export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemProps) { +export default function NoteColorPicker(props: NoteColorPickerProps) { if (!props.note) return null; const [note, setNote] = useState(null); @@ -43,14 +43,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr useEffect(() => { const colorLabel = note?.getLabel("color")?.value ?? null; if (colorLabel) { - let color: ColorInstance | null = null; - - try { - color = new Color(colorLabel); - } catch(ex) { - console.error(ex); - } - + let color = tryParseColor(colorLabel); if (color) { setCurrentColor(color.hex().toLowerCase()); } @@ -58,7 +51,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr }, [note]); useEffect(() => { - setIsCustomColor(currentColor !== null && COLORS.indexOf(currentColor) === -1); + setIsCustomColor(currentColor !== null && COLOR_PALETTE.indexOf(currentColor) === -1); }, [currentColor]) const onColorCellClicked = useCallback((color: string | null) => { @@ -84,7 +77,7 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr onSelect={onColorCellClicked} /> - {COLORS.map((color) => ( + {COLOR_PALETTE.map((color) => ( + return
        } -function ensureContrast(color: string | null) { - if (color === null) return "inherit"; +function getForegroundColor(backgroundColor: string | null) { + if (backgroundColor === null) return "inherit"; - const colorHsl = Color(color).hsl(); - let l = colorHsl.lightness(); - - if (l >= 40) { - l = 0; + const colorHsl = tryParseColor(backgroundColor)?.hsl(); + if (colorHsl) { + let l = colorHsl.lightness(); + return colorHsl.saturationl(0).lightness(l >= 50 ? 0 : 100).hex(); } else { - l = 100 + return "inherit"; + } +} + +function tryParseColor(colorStr: string): ColorInstance | null { + try { + return new Color(colorStr); + } catch(ex) { + console.error(ex); } - return colorHsl.saturationl(0).lightness(l).hex(); + return null; } \ No newline at end of file From 926f0f85f3846f52fdb0646c5117fb1dbf3f35e9 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 23:10:36 +0200 Subject: [PATCH 30/37] client/note color picker: refactor --- apps/client/package.json | 1 + .../client/src/menus/custom-items/NoteColorPicker.tsx | 11 +++++++++-- pnpm-lock.yaml | 11 +++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index cd1317e2b..8bdbb9485 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -36,6 +36,7 @@ "autocomplete.js": "0.38.1", "bootstrap": "5.3.8", "boxicons": "2.1.4", + "clsx": "2.1.1", "color": "5.0.3", "dayjs": "1.11.19", "dayjs-plugin-utc": "0.1.2", diff --git a/apps/client/src/menus/custom-items/NoteColorPicker.tsx b/apps/client/src/menus/custom-items/NoteColorPicker.tsx index 16a17655f..05041d898 100644 --- a/apps/client/src/menus/custom-items/NoteColorPicker.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPicker.tsx @@ -3,6 +3,7 @@ import { t } from "../../services/i18n"; import { useCallback, useEffect, useRef, useState} from "preact/hooks"; import {ComponentChildren} from "preact"; import attributes from "../../services/attributes"; +import clsx from "clsx"; import Color, { ColorInstance } from "color"; import Debouncer from "../../utils/debouncer"; import FNote from "../../entities/fnote"; @@ -105,7 +106,11 @@ interface ColorCellProps { } function ColorCell(props: ColorCellProps) { - return
        props.onSelect?.(props.color)}> @@ -151,7 +156,9 @@ function CustomColorCell(props: ColorCellProps) { return
        =18.0.0'} + deprecated: Please upgrade your lockfile to use the latest 3.x version of @smithy/core for various fixes, see https://github.com/smithy-lang/smithy-typescript/blob/main/packages/core/CHANGELOG.md '@smithy/core@3.18.4': resolution: {integrity: sha512-o5tMqPZILBvvROfC8vC+dSVnWJl9a0u9ax1i1+Bq8515eYjUJqqk5XjjEsDLoeL5dSqGSh6WGdVx1eJ1E/Nwhw==} engines: {node: '>=18.0.0'} + deprecated: Please upgrade your lockfile to use the latest 3.x version of @smithy/core for various fixes, see https://github.com/smithy-lang/smithy-typescript/blob/main/packages/core/CHANGELOG.md '@smithy/credential-provider-imds@4.0.6': resolution: {integrity: sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==} @@ -15692,6 +15697,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -15756,8 +15763,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.2.0 '@ckeditor/ckeditor5-watchdog': 47.2.0 es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': dependencies: @@ -15952,8 +15957,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.2.0 ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-multi-root@47.2.0': dependencies: From a5c5486474e093608debf6dfb804e99bcc2998b9 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 23:34:37 +0200 Subject: [PATCH 31/37] client/note color picker: tweak style --- apps/client/src/stylesheets/style.css | 4 ++++ apps/client/src/stylesheets/theme-next/base.css | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 4ce8b1cd7..e127db1aa 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -494,6 +494,10 @@ body #context-menu-container .dropdown-item > span { width: 100%; } +.dropdown-menu .note-color-picker { + padding: 10px 12px 8px 12px; +} + .cm-editor { height: 100%; outline: none !important; diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index 5976f1dfb..ba83af30a 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -347,6 +347,12 @@ li.dropdown-item a.dropdown-item-button:focus-visible { outline: 2px solid var(--input-focus-outline-color) !important; } +:root .dropdown-menu .note-color-picker { + padding: 4px 10px; + --note-color-picker-clear-color-cell-background: var(--main-text-color); + --note-color-picker-clear-color-cell-selection-outline-color: var(--main-text-color); +} + /* * TOASTS */ From 1b2d922c3f7483a621d2170854344a06e251e9eb Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 23:37:03 +0200 Subject: [PATCH 32/37] client/note color picker: tweak style --- apps/client/src/stylesheets/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index e127db1aa..60ad28237 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -495,7 +495,7 @@ body #context-menu-container .dropdown-item > span { } .dropdown-menu .note-color-picker { - padding: 10px 12px 8px 12px; + padding: 4px 12px 8px 12px; } .cm-editor { From d42f911df9c740e99ace6231a6f204e671b59090 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Thu, 20 Nov 2025 23:43:08 +0200 Subject: [PATCH 33/37] client/note color picker: dismiss the menu when a color is clicked --- apps/client/src/menus/custom-items/NoteColorPicker.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPicker.tsx b/apps/client/src/menus/custom-items/NoteColorPicker.tsx index 05041d898..06ae6dc96 100644 --- a/apps/client/src/menus/custom-items/NoteColorPicker.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPicker.tsx @@ -67,8 +67,7 @@ export default function NoteColorPicker(props: NoteColorPickerProps) { } }, [note, currentColor]); - return
        {e.stopPropagation()}}> + return
        Date: Thu, 20 Nov 2025 23:55:26 +0200 Subject: [PATCH 34/37] client/tree context menu: relocate the note color picker --- apps/client/src/menus/tree_context_menu.ts | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts index 35480cfc6..19e6f4e16 100644 --- a/apps/client/src/menus/tree_context_menu.ts +++ b/apps/client/src/menus/tree_context_menu.ts @@ -242,6 +242,19 @@ export default class TreeContextMenu implements SelectMenuItemEventListener { + if (notOptionsOrHelp && selectedNotes.length === 1) { + return NoteColorPicker({note}); + } else { + return null; + } + } + }, + { kind: "separator" }, { title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptionsOrHelp }, @@ -256,20 +269,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener { - if (notOptionsOrHelp && selectedNotes.length === 1) { - return NoteColorPicker({note}); - } else { - return null; - } - } - }, + } ]; return items.filter((row) => row !== null) as MenuItem[]; } From e9796c9a35c41f302bbbcc860ece2550f67c10b9 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 21 Nov 2025 01:29:54 +0200 Subject: [PATCH 35/37] client/note color picker: fix the custom color picker on Safari --- apps/client/src/menus/custom-items/NoteColorPicker.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPicker.tsx b/apps/client/src/menus/custom-items/NoteColorPicker.tsx index 06ae6dc96..9a65073a7 100644 --- a/apps/client/src/menus/custom-items/NoteColorPicker.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPicker.tsx @@ -122,6 +122,7 @@ function CustomColorCell(props: ColorCellProps) { const colorInput = useRef(null); const colorInputDebouncer = useRef | null>(null); const callbackRef = useRef(props.onSelect); + const isSafari = useRef(/^((?!chrome|android).)*safari/i.test(navigator.userAgent)); useEffect(() => { colorInputDebouncer.current = new Debouncer(500, (color) => { @@ -152,7 +153,12 @@ function CustomColorCell(props: ColorCellProps) { colorInput.current?.click(); }, [pickedColor]); - return
        + return
        { + // The color picker dropdown will close on Safari if the parent context menu is + // dismissed, so stop the click propagation to prevent dismissing the menu. + isSafari.current && e.stopPropagation(); + }}> Date: Fri, 21 Nov 2025 01:30:45 +0200 Subject: [PATCH 36/37] client/note color picker: decrease the debouncer interval --- apps/client/src/menus/custom-items/NoteColorPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPicker.tsx b/apps/client/src/menus/custom-items/NoteColorPicker.tsx index 9a65073a7..775769261 100644 --- a/apps/client/src/menus/custom-items/NoteColorPicker.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPicker.tsx @@ -125,7 +125,7 @@ function CustomColorCell(props: ColorCellProps) { const isSafari = useRef(/^((?!chrome|android).)*safari/i.test(navigator.userAgent)); useEffect(() => { - colorInputDebouncer.current = new Debouncer(500, (color) => { + colorInputDebouncer.current = new Debouncer(250, (color) => { callbackRef.current?.(color); setPickedColor(color); }); From d87e8b729f4c276ee403189d3b002db8c59a76e4 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Fri, 21 Nov 2025 02:12:52 +0200 Subject: [PATCH 37/37] client/note color picker/clear color cell: fix icon alignment --- .../menus/custom-items/NoteColorPicker.css | 47 +++++++++---------- .../menus/custom-items/NoteColorPicker.tsx | 8 +++- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPicker.css b/apps/client/src/menus/custom-items/NoteColorPicker.css index 93a56ec0c..f4d6f642f 100644 --- a/apps/client/src/menus/custom-items/NoteColorPicker.css +++ b/apps/client/src/menus/custom-items/NoteColorPicker.css @@ -32,20 +32,6 @@ outline-offset: 2px; } -.note-color-picker .color-cell-reset::before, -.note-color-picker .custom-color-cell::before { - position: absolute; - display: flex; - top: 0; - left: 0; - right: 0; - bottom: 0; - font-size: calc(var(--color-picker-cell-size) * 1.3); - justify-content: center; - align-items: center; - font-family: boxicons; -} - /* * RESET COLOR CELL */ @@ -55,18 +41,37 @@ --outline-color: var(--note-color-picker-clear-color-cell-selection-outline-color); position: relative; + display: flex; + justify-content: center; + align-items: center; } -.note-color-picker .color-cell-reset::before { - content: "\ec8d"; - mix-blend-mode: normal; - color: var(--note-color-picker-clear-color-cell-color); +.note-color-picker .color-cell-reset svg { + width: var(--color-picker-cell-size); + height: var(--color-picker-cell-size); + fill: var(--note-color-picker-clear-color-cell-color); } /* * CUSTOM COLOR CELL */ + .note-color-picker .custom-color-cell::before { + position: absolute; + content: "\ed35"; + display: flex; + top: 0; + left: 0; + right: 0; + bottom: 0; + font-size: calc(var(--color-picker-cell-size) * 1.3); + justify-content: center; + align-items: center; + font-family: boxicons; + font-size: 16px; + color: var(--foreground); +} + .note-color-picker .custom-color-cell { position: relative; display: flex; @@ -78,10 +83,4 @@ background-image: url(./custom-color.png); background-size: cover; --foreground: transparent; -} - -.note-color-picker .custom-color-cell::before { - content: "\ed35"; - color: var(--foreground); - font-size: 16px; } \ No newline at end of file diff --git a/apps/client/src/menus/custom-items/NoteColorPicker.tsx b/apps/client/src/menus/custom-items/NoteColorPicker.tsx index 775769261..08bc7c84a 100644 --- a/apps/client/src/menus/custom-items/NoteColorPicker.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPicker.tsx @@ -74,7 +74,13 @@ export default function NoteColorPicker(props: NoteColorPickerProps) { color={null} isSelected={(currentColor === null)} isDisabled={(note === null)} - onSelect={onColorCellClicked} /> + onSelect={onColorCellClicked}> + + {/* https://pictogrammers.com/library/mdi/icon/close/ */} + + + + {COLOR_PALETTE.map((color) => (