This commit is contained in:
Adorian Doran 2025-11-29 18:50:24 +02:00
commit 1346ffb77e
13 changed files with 126 additions and 46 deletions

View File

@ -487,7 +487,7 @@ type EventMappings = {
relationMapResetPanZoom: { ntxId: string | null | undefined }; relationMapResetPanZoom: { ntxId: string | null | undefined };
relationMapResetZoomIn: { ntxId: string | null | undefined }; relationMapResetZoomIn: { ntxId: string | null | undefined };
relationMapResetZoomOut: { ntxId: string | null | undefined }; relationMapResetZoomOut: { ntxId: string | null | undefined };
activeNoteChanged: {}; activeNoteChanged: {ntxId: string | null | undefined};
showAddLinkDialog: AddLinkOpts; showAddLinkDialog: AddLinkOpts;
showIncludeDialog: IncludeNoteOpts; showIncludeDialog: IncludeNoteOpts;
openBulkActionsDialog: { openBulkActionsDialog: {

View File

@ -165,7 +165,7 @@ export default class TabManager extends Component {
const activeNoteContext = this.getActiveContext(); const activeNoteContext = this.getActiveContext();
this.updateDocumentTitle(activeNoteContext); this.updateDocumentTitle(activeNoteContext);
this.triggerEvent("activeNoteChanged", {}); // trigger this even in on popstate event this.triggerEvent("activeNoteChanged", {ntxId:activeNoteContext?.ntxId}); // trigger this even in on popstate event
} }
calculateHash(): string { calculateHash(): string {

View File

@ -32,6 +32,7 @@ import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
const MOBILE_CSS = ` const MOBILE_CSS = `
<style> <style>
span.keyboard-shortcut,
kbd { kbd {
display: none; display: none;
} }

View File

@ -1,7 +1,9 @@
import clsx from "clsx";
import {readCssVar} from "../utils/css-var"; import {readCssVar} from "../utils/css-var";
import Color, { ColorInstance } from "color"; import Color, { ColorInstance } from "color";
const registeredClasses = new Set<string>(); const registeredClasses = new Set<string>();
const colorsWithHue = new Set<string>();
// Read the color lightness limits defined in the theme as CSS variables // Read the color lightness limits defined in the theme as CSS variables
@ -26,19 +28,23 @@ function createClassForColor(colorString: string | null) {
if (!registeredClasses.has(className)) { if (!registeredClasses.has(className)) {
const adjustedColor = adjustColorLightness(color, lightThemeColorMaxLightness!, const adjustedColor = adjustColorLightness(color, lightThemeColorMaxLightness!,
darkThemeColorMinLightness!); darkThemeColorMinLightness!);
const hue = getHue(color);
$("head").append(`<style> $("head").append(`<style>
.${className}, span.fancytree-active.${className} { .${className}, span.fancytree-active.${className} {
--light-theme-custom-color: ${adjustedColor.lightThemeColor}; --light-theme-custom-color: ${adjustedColor.lightThemeColor};
--dark-theme-custom-color: ${adjustedColor.darkThemeColor}; --dark-theme-custom-color: ${adjustedColor.darkThemeColor};
--custom-color-hue: ${getHue(color) ?? 'unset'}; --custom-color-hue: ${hue ?? 'unset'};
} }
</style>`); </style>`);
registeredClasses.add(className); registeredClasses.add(className);
if (hue) {
colorsWithHue.add(className);
}
} }
return `color ${className}`; return clsx(className, colorsWithHue.has(className) && "with-hue");
} }
function parseColor(color: string) { function parseColor(color: string) {

View File

@ -98,6 +98,7 @@
--menu-item-delimiter-color: #ffffff1c; --menu-item-delimiter-color: #ffffff1c;
--menu-item-group-header-color: #ffffff91; --menu-item-group-header-color: #ffffff91;
--menu-section-background-color: #fefefe08; --menu-section-background-color: #fefefe08;
--menu-submenu-mobile-background-color: rgba(0, 0, 0, 0.15);
--modal-backdrop-color: #000; --modal-backdrop-color: #000;
--modal-shadow-color: rgba(0, 0, 0, .5); --modal-shadow-color: rgba(0, 0, 0, .5);
@ -300,7 +301,7 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
border-color: var(--muted-text-color) !important; border-color: var(--muted-text-color) !important;
} }
.quick-edit-dialog-wrapper.color { .quick-edit-dialog-wrapper.with-hue {
--modal-background-color: hsl(var(--custom-color-hue), 8.8%, 11.2%); --modal-background-color: hsl(var(--custom-color-hue), 8.8%, 11.2%);
--modal-border-color: hsl(var(--custom-color-hue), 9.4%, 25.1%); --modal-border-color: hsl(var(--custom-color-hue), 9.4%, 25.1%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 13.2%, 20.8%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 13.2%, 20.8%);

View File

@ -276,7 +276,7 @@
--custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1); --custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1);
} }
.quick-edit-dialog-wrapper.color { .quick-edit-dialog-wrapper.with-hue {
--modal-background-color: hsl(var(--custom-color-hue), 56%, 96%); --modal-background-color: hsl(var(--custom-color-hue), 56%, 96%);
--modal-border-color: hsl(var(--custom-color-hue), 33%, 41%); --modal-border-color: hsl(var(--custom-color-hue), 33%, 41%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%); --promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);

View File

@ -62,6 +62,7 @@
--menu-padding-size: 8px; --menu-padding-size: 8px;
--menu-item-icon-vert-offset: -2px; --menu-item-icon-vert-offset: -2px;
--menu-submenu-mobile-background-color: rgba(255, 255, 255, 0.15);
--more-accented-background-color: var(--card-background-hover-color); --more-accented-background-color: var(--card-background-hover-color);
@ -384,7 +385,8 @@ body.mobile .dropdown-menu {
} }
.dropdown-menu { .dropdown-menu {
--menu-background-color: rgba(0, 0, 0, 0.15); --menu-background-color: --menu-submenu-mobile-background-color;
--bs-dropdown-divider-margin-y: 0.25rem;
border-radius: 0; border-radius: 0;
max-height: 0; max-height: 0;
transition: max-height 100ms ease-in; transition: max-height 100ms ease-in;
@ -392,22 +394,7 @@ body.mobile .dropdown-menu {
&.show { &.show {
max-height: 1000px; max-height: 1000px;
} padding: 0.5rem 0.75rem !important;
.dropdown-item {
background: transparent;
}
}
.dropdown-divider {
visibility: visible;
margin: 0;
height: 3px;
border-top: unset;
background-color: rgba(0, 0, 0, 0.2);
&:after {
content: unset;
} }
} }

View File

@ -105,9 +105,11 @@ export default function NoteDetail() {
}); });
// Automatically focus the editor. // Automatically focus the editor.
useTriliumEvent("activeNoteChanged", () => { useTriliumEvent("activeNoteChanged", ({ ntxId: eventNtxId }) => {
// Restore focus to the editor when switching tabs, but only if the note tree is not already focused. if (eventNtxId != ntxId) return;
if (!document.activeElement?.classList.contains("fancytree-title")) { // Restore focus to the editor when switching tabs,
// but only if the note tree and the note panel (e.g., note title or note detail) are not focused.
if (!document.activeElement?.classList.contains("fancytree-title") && !parentComponent.$widget[0].closest(".note-split")?.contains(document.activeElement)) {
parentComponent.triggerCommand("focusOnDetail", { ntxId }); parentComponent.triggerCommand("focusOnDetail", { ntxId });
} }
}); });

View File

@ -29,7 +29,11 @@ export default class LeftPaneContainer extends FlexContainer<Component> {
if (visible) { if (visible) {
this.triggerEvent("focusTree", {}); this.triggerEvent("focusTree", {});
} else { } else {
this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId }); const ntxId = appContext.tabManager.getActiveContext()?.ntxId;
const noteContainer = document.querySelector(`.note-split[data-ntx-id="${ntxId}"]`);
if (!noteContainer?.contains(document.activeElement)) {
this.triggerEvent("focusOnDetail", { ntxId });
}
} }
options.save("leftPaneVisible", this.currentLeftPaneVisible.toString()); options.save("leftPaneVisible", this.currentLeftPaneVisible.toString());

View File

@ -14,11 +14,15 @@ body.desktop .modal.popup-editor-dialog .modal-dialog {
} }
body.mobile .modal.popup-editor-dialog .modal-dialog { body.mobile .modal.popup-editor-dialog .modal-dialog {
max-width: 90vw; max-width: min(var(--preferred-max-content-width), 95vw);
max-height: var(--tn-modal-max-height); max-height: var(--tn-modal-max-height);
height: 100%; height: 100%;
} }
.modal.popup-editor-dialog .modal-content {
transition: background-color 250ms ease-in;
}
.modal.popup-editor-dialog .modal-header .modal-title { .modal.popup-editor-dialog .modal-header .modal-title {
font-size: 1.1em; font-size: 1.1em;
} }
@ -58,12 +62,16 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
font-size: 1em; font-size: 1em;
} }
.modal.popup-editor-dialog .classic-toolbar-outer-container.visible {
background-color: transparent;
}
.modal.popup-editor-dialog .classic-toolbar-widget { .modal.popup-editor-dialog .classic-toolbar-widget {
position: sticky; position: sticky;
top: 0; top: 0;
inset-inline-start: 0; inset-inline-start: 0;
inset-inline-end: 0; inset-inline-end: 0;
background: var(--modal-background-color); background: transparent;
z-index: 998; z-index: 998;
align-items: flex-start; align-items: flex-start;
} }

View File

@ -10,16 +10,16 @@ import FNote from "../../entities/fnote";
import search from "../../services/search"; import search from "../../services/search";
import { TypeWidgetProps } from "./type_widget"; import { TypeWidgetProps } from "./type_widget";
export default function Empty({ }: TypeWidgetProps) { export default function Empty({ ntxId }: TypeWidgetProps) {
return ( return (
<> <>
<WorkspaceSwitcher /> <WorkspaceSwitcher />
<NoteSearch /> <NoteSearch ntxId={ntxId ?? null} />
</> </>
) )
} }
function NoteSearch() { function NoteSearch({ ntxId }: { ntxId: string | null }) {
const resultsContainerRef = useRef<HTMLDivElement>(null); const resultsContainerRef = useRef<HTMLDivElement>(null);
const autocompleteRef = useRef<HTMLInputElement>(null); const autocompleteRef = useRef<HTMLInputElement>(null);
@ -45,10 +45,9 @@ function NoteSearch() {
if (!suggestion?.notePath) { if (!suggestion?.notePath) {
return false; return false;
} }
const activeNoteContext = appContext.tabManager.getNoteContextById(ntxId) ?? appContext.tabManager.getActiveContext();
const activeContext = appContext.tabManager.getActiveContext(); if (activeNoteContext) {
if (activeContext) { activeNoteContext.setNote(suggestion.notePath);
activeContext.setNote(suggestion.notePath);
} }
}} }}
/> />

View File

@ -98,6 +98,14 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
editorApi: editorApiRef.current, editorApi: editorApiRef.current,
}); });
}, },
insertDateTimeToTextCommand() {
if (!editorApiRef.current) return;
const date = new Date();
const customDateTimeFormat = options.get("customDateTimeFormat");
const dateString = utils.formatDateTime(date, customDateTimeFormat);
addTextToEditor(dateString);
},
// Include note functionality note // Include note functionality note
addIncludeNoteToTextCommand() { addIncludeNoteToTextCommand() {
if (!editorApiRef.current) return; if (!editorApiRef.current) return;
@ -197,14 +205,6 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
}); });
} }
useTriliumEvent("insertDateTimeToText", ({ ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId) return;
const date = new Date();
const customDateTimeFormat = options.get("customDateTimeFormat");
const dateString = utils.formatDateTime(date, customDateTimeFormat);
addTextToEditor(dateString);
});
useTriliumEvent("addTextToActiveEditor", ({ text }) => { useTriliumEvent("addTextToActiveEditor", ({ text }) => {
if (!noteContext?.isActive()) return; if (!noteContext?.isActive()) return;
addTextToEditor(text); addTextToEditor(text);

View File

@ -0,0 +1,72 @@
import { test, expect } from "@playwright/test";
import App from "../support/app";
const TEXT_NOTE_TITLE = "Text notes";
const CODE_NOTE_TITLE = "Code notes";
test("Open the note in the correct split pane", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.closeAllTabs();
// Open the first split.
await app.goToNoteInNewTab(TEXT_NOTE_TITLE);
const split1 = app.currentNoteSplit;
// Create a new split.
const splitButton = split1.locator("button.bx-dock-right");
await expect(splitButton).toBeVisible();
await splitButton.click();
// Search for "Code notes" in the empty area of the second split.
const split2 = app.currentNoteSplit.nth(1);;
await expect(split2).toBeVisible();
const autocomplete = split2.locator(".note-autocomplete");
await autocomplete.fill(CODE_NOTE_TITLE);
const resultsSelector = split2.locator(".note-detail-empty-results");
await expect(resultsSelector).toContainText(CODE_NOTE_TITLE);
//Focus on the first split.
const noteContent = split1.locator(".note-detail-editable-text-editor");
await expect(noteContent.locator("p")).toBeVisible();
await noteContent.focus();
// Click the search result in the second split.
await resultsSelector.locator(".aa-suggestion", { hasText: CODE_NOTE_TITLE })
.nth(1).click();
await expect(split2).toContainText(CODE_NOTE_TITLE);
});
test("Can directly focus the autocomplete input within the split", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await app.closeAllTabs();
// Open the first split.
await app.goToNoteInNewTab(TEXT_NOTE_TITLE);
const split1 = app.currentNoteSplit;
// Create a new split.
const splitButton = split1.locator("button.bx-dock-right");
await expect(splitButton).toBeVisible();
await splitButton.click();
// Search for "Code notes" in the empty area of the second split.
const split2 = app.currentNoteSplit.nth(1);;
await expect(split2).toBeVisible();
// Focus the first split.
const noteContent = split1.locator(".note-detail-editable-text-editor");
await expect(noteContent.locator("p")).toBeVisible();
await noteContent.focus();
await noteContent.click();
// click the autocomplete input box of the second split
const autocomplete = split2.locator(".note-autocomplete");
await autocomplete.focus();
await autocomplete.click();
await page.waitForTimeout(100);
await expect(autocomplete).toBeFocused();
});