UI improvements (#7655)
Some checks are pending
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / main (push) Waiting to run

This commit is contained in:
Adorian Doran 2025-11-09 02:41:20 +02:00 committed by GitHub
commit 78e2814068
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 826 additions and 296 deletions

View File

@ -499,6 +499,10 @@ type EventMappings = {
noteIds: string[]; noteIds: string[];
}; };
refreshData: { ntxId: string | null | undefined }; refreshData: { ntxId: string | null | undefined };
contentSafeMarginChanged: {
top: number;
noteContext: NoteContext;
}
}; };
export type EventListener<T extends EventNames> = { export type EventListener<T extends EventNames> = {

View File

@ -1,47 +1,49 @@
import FlexContainer from "../widgets/containers/flex_container.js";
import TabRowWidget from "../widgets/tab_row.js";
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteDetailWidget from "../widgets/note_detail.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import NoteIconWidget from "../widgets/note_icon.jsx";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
import SpacerWidget from "../widgets/spacer.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import FindWidget from "../widgets/find.js";
import TocWidget from "../widgets/toc.js";
import HighlightsListWidget from "../widgets/highlights_list.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import ScrollPadding from "../widgets/scroll_padding.js";
import options from "../services/options.js";
import utils from "../services/utils.js";
import type { AppContext } from "../components/app_context.js";
import type { WidgetsByParent } from "../services/bundle.js";
import { applyModals } from "./layout_commons.js"; import { applyModals } from "./layout_commons.js";
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx"; import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import SearchResult from "../widgets/search_result.jsx"; import ApiLog from "../widgets/api_log.jsx";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import ContentHeader from "../widgets/containers/content-header.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import FindWidget from "../widgets/find.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import GlobalMenu from "../widgets/buttons/global_menu.jsx"; import GlobalMenu from "../widgets/buttons/global_menu.jsx";
import HighlightsListWidget from "../widgets/highlights_list.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import NoteIconWidget from "../widgets/note_icon.jsx";
import NoteList from "../widgets/collections/NoteList.jsx";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import options from "../services/options.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import ScrollPadding from "../widgets/scroll_padding.js";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
import SpacerWidget from "../widgets/spacer.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import SqlResults from "../widgets/sql_result.js"; import SqlResults from "../widgets/sql_result.js";
import SqlTableSchemas from "../widgets/sql_table_schemas.js"; import SqlTableSchemas from "../widgets/sql_table_schemas.js";
import TabRowWidget from "../widgets/tab_row.js";
import TitleBarButtons from "../widgets/title_bar_buttons.jsx"; import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js"; import TocWidget from "../widgets/toc.js";
import ApiLog from "../widgets/api_log.jsx"; import type { AppContext } from "../components/app_context.js";
import CloseZenModeButton from "../widgets/close_zen_button.jsx"; import type { WidgetsByParent } from "../services/bundle.js";
import SharedInfo from "../widgets/shared_info.jsx"; import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import NoteList from "../widgets/collections/NoteList.jsx"; import utils from "../services/utils.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
export default class DesktopLayout { export default class DesktopLayout {
@ -129,12 +131,15 @@ export default class DesktopLayout {
.child(<CreatePaneButton />) .child(<CreatePaneButton />)
) )
.child(<Ribbon />) .child(<Ribbon />)
.child(<SharedInfo />)
.child(new WatchedFileUpdateStatusWidget()) .child(new WatchedFileUpdateStatusWidget())
.child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />) .child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
.child( .child(
new ScrollingContainer() new ScrollingContainer()
.filling() .filling()
.child(new ContentHeader()
.child(<ReadOnlyNoteInfoBar />)
.child(<SharedInfo />)
)
.child(new PromotedAttributesWidget()) .child(new PromotedAttributesWidget())
.child(<SqlTableSchemas />) .child(<SqlTableSchemas />)
.child(new NoteDetailWidget()) .child(new NoteDetailWidget())

View File

@ -1,32 +1,34 @@
import { applyModals } from "./layout_commons.js";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import FlexContainer from "../widgets/containers/flex_container.js"; import FlexContainer from "../widgets/containers/flex_container.js";
import NoteTitleWidget from "../widgets/note_title.js"; import FloatingButtons from "../widgets/FloatingButtons.jsx";
import NoteDetailWidget from "../widgets/note_detail.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js"; import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js"; import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import type AppContext from "../components/app_context.js";
import TabRowWidget from "../widgets/tab_row.js";
import MobileEditorToolbar from "../widgets/type_widgets/ckeditor/mobile_editor_toolbar.js";
import { applyModals } from "./layout_commons.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js"; import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import MobileEditorToolbar from "../widgets/type_widgets/ckeditor/mobile_editor_toolbar.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import NoteList from "../widgets/collections/NoteList.jsx"; import NoteList from "../widgets/collections/NoteList.jsx";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx"; import NoteTitleWidget from "../widgets/note_title.js";
import ContentHeader from "../widgets/containers/content-header.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import RootContainer from "../widgets/containers/root_container.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx"; import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
import SearchResult from "../widgets/search_result.jsx"; import SearchResult from "../widgets/search_result.jsx";
import SharedInfoWidget from "../widgets/shared_info.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import TabRowWidget from "../widgets/tab_row.js";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import type AppContext from "../components/app_context.js";
const MOBILE_CSS = ` const MOBILE_CSS = `
<style> <style>
@ -149,13 +151,16 @@ export default class MobileLayout {
.child(<NoteTitleWidget />) .child(<NoteTitleWidget />)
.child(<MobileDetailMenu />) .child(<MobileDetailMenu />)
) )
.child(<SharedInfoWidget />)
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />) .child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
.child(new PromotedAttributesWidget()) .child(new PromotedAttributesWidget())
.child( .child(
new ScrollingContainer() new ScrollingContainer()
.filling() .filling()
.contentSized() .contentSized()
.child(new ContentHeader()
.child(<ReadOnlyNoteInfoBar />)
.child(<SharedInfoWidget />)
)
.child(new NoteDetailWidget()) .child(new NoteDetailWidget())
.child(<NoteList media="screen" />) .child(<NoteList media="screen" />)
.child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />) .child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />)

View File

@ -1809,12 +1809,15 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
} }
.note-split { .note-split {
/* Limits the maximum width of the note */
--max-content-width: var(--preferred-max-content-width);
margin-inline-start: auto; margin-inline-start: auto;
margin-inline-end: auto; margin-inline-end: auto;
} }
.note-split.full-content-width { .note-split.full-content-width {
max-width: 999999px; --max-content-width: unset;
} }
button.close:hover { button.close:hover {
@ -2034,13 +2037,16 @@ body.zen #right-pane,
body.zen #mobile-sidebar-wrapper, body.zen #mobile-sidebar-wrapper,
body.zen .tab-row-container, body.zen .tab-row-container,
body.zen .tab-row-widget, body.zen .tab-row-widget,
body.zen .shared-info-widget,
body.zen .ribbon-container:not(:has(.classic-toolbar-widget)), body.zen .ribbon-container:not(:has(.classic-toolbar-widget)),
body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row, body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row,
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)), body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
body.zen .note-icon-widget, body.zen .note-icon-widget,
body.zen .title-row .icon-action, body.zen .title-row .icon-action,
body.zen .promoted-attributes-widget,
body.zen .floating-buttons-children > *:not(.bx-edit-alt), body.zen .floating-buttons-children > *:not(.bx-edit-alt),
body.zen .action-button { body.zen .action-button,
body.zen .note-list-widget:not(.full-height) {
display: none !important; display: none !important;
} }
@ -2084,12 +2090,116 @@ body.zen .note-title-widget,
body.zen .note-title-widget input { body.zen .note-title-widget input {
font-size: 1rem !important; font-size: 1rem !important;
background: transparent !important; background: transparent !important;
pointer-events: none;
} }
body.zen #detail-container { body.zen #detail-container {
width: 100%; width: 100%;
} }
body.zen .note-split:not(.full-content-width) .scrolling-container {
display: flex;
flex-direction: column;
scroll-behavior: unset !important;
}
body.zen .note-split:not(.full-content-width) .note-detail {
margin: auto;
padding-bottom: 25vh;
max-width: var(--max-content-width);
width: 100%;
}
body.zen .note-split:not(.full-content-width) .scroll-padding-widget {
display: none;
}
body.zen .note-split.type-text {
position: relative;
font-size: 1.15em;
}
body.zen:not(.backdrop-effects-disabled) .note-split.type-text .title-row {
--start-color: var(--main-background-color);
position: absolute;
width: 100%;
background: linear-gradient(var(--start-color) 30%, transparent 100%);
z-index: 1000;
}
@supports (background: color-mix(in srgb, white, transparent)) {
body.zen .note-split.type-text .title-row {
--start-color: color-mix(in srgb, var(--main-background-color), transparent 10%);
}
}
body.zen .note-split.type-text .scrolling-container {
--padding-bottom: 130px; /* Should be enough to avoid caret being hidden by the formatting toolbar */
/* (Usually) keeps the caret above the fixed toolbar */
scroll-padding-bottom: var(--padding-bottom);
}
body.zen:not(.backdrop-effects-disabled) .note-split.type-text .scrolling-container {
--padding-top: 50px; /* Should be enough to cover the title row */
padding-top: var(--padding-top);
scroll-padding-top: var(--padding-top);
}
/* Fixed formatting toolbar */
body.zen .note-split .ribbon-container {
position: fixed;
left: 0;
bottom: 20px;
width: 100%;
z-index: 1000;
opacity: 0; /* Hidden unless the current note split is focused */
pointer-events: none;
transition: opacity 100ms linear;
}
body.zen .note-split:focus-within .ribbon-container {
opacity: 1; /* Show when the note split is focused */
}
body.zen .note-split .ribbon-container .ribbon-body {
border: 0;
}
body.zen .note-split .ribbon-container .classic-toolbar-widget {
margin: auto;
width: fit-content;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
border-radius: 8px;
border: 1px solid var(--main-border-color);
padding: 4px;
background: var(--menu-background-color);
}
body.zen .note-split:focus-within .ribbon-container .classic-toolbar-widget {
pointer-events: all;
}
@media (max-width: 1300px) {
body.zen .note-split .ribbon-container .classic-toolbar-widget {
/* Set the toolbar to full with */
width: 100%;
}
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
/* Force toolbar items overflow dropdowns open upwards */
top: auto;
bottom: 100%;
}
}
/* Content renderer */ /* Content renderer */
footer.file-footer, footer.file-footer,

View File

@ -15,7 +15,7 @@
--native-titlebar-background: #00000000; --native-titlebar-background: #00000000;
--window-background-color-bgfx: transparent; /* When background effects enabled */ --window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: #272727; --main-background-color: #242424;
--main-text-color: #ccc; --main-text-color: #ccc;
--main-border-color: #454545; --main-border-color: #454545;
--subtle-border-color: #313131; --subtle-border-color: #313131;
@ -166,6 +166,9 @@
--protected-session-active-icon-color: #8edd8e; --protected-session-active-icon-color: #8edd8e;
--sync-status-error-pulse-color: #f47871; --sync-status-error-pulse-color: #f47871;
--center-pane-vert-layout-background-color-bgfx: #0c0c0c69;
--center-pane-horiz-layout-background-color-bgfx: #1e1e1ec7;
--right-pane-heading-color: gray; --right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color); --root-background: var(--left-pane-background-color);
@ -192,9 +195,9 @@
--badge-background-color: #ffffff1a; --badge-background-color: #ffffff1a;
--badge-text-color: var(--muted-text-color); --badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color); --promoted-attribute-card-background-color: #ffffff21;
--promoted-attribute-card-shadow-color: #000000b3; --promoted-attribute-card-shadow: none;
--floating-button-shadow-color: #00000080; --floating-button-shadow-color: #00000080;
--floating-button-background-color: #494949d2; --floating-button-background-color: #494949d2;
--floating-button-color: var(--button-text-color); --floating-button-color: var(--button-text-color);
@ -208,6 +211,8 @@
--floating-button-hide-button-background: #00000029; --floating-button-hide-button-background: #00000029;
--floating-button-hide-button-color: #ffffff63; --floating-button-hide-button-color: #ffffff63;
--right-pane-background-color: var(--main-background-color);
--right-pane-background-color-bgfx: #0c0c0c24; /* Only for the vertical layout */
--right-pane-item-hover-background: #ffffff26; --right-pane-item-hover-background: #ffffff26;
--right-pane-item-hover-color: white; --right-pane-item-hover-color: white;
@ -227,8 +232,8 @@
--card-background-color: #ffffff12; --card-background-color: #ffffff12;
--card-background-hover-color: #3c3c3c; --card-background-hover-color: #3c3c3c;
--card-background-press-color: #464646; --card-background-press-color: #464646;
--card-border-color: #222222; --card-border-color: transparent;
--card-box-shadow: 0 0 12px rgba(0, 0, 0, 0.15); --card-box-shadow: none;
--calendar-color: var(--menu-text-color); --calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color); --calendar-weekday-labels-color: var(--muted-text-color);
@ -294,4 +299,10 @@ body ::-webkit-calendar-picker-indicator {
body .todo-list input[type="checkbox"]:not(:checked):before { body .todo-list input[type="checkbox"]:not(:checked):before {
border-color: var(--muted-text-color) !important; border-color: var(--muted-text-color) !important;
}
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 8.8%, 11.2%);
--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%);
} }

View File

@ -159,6 +159,9 @@
--protected-session-active-icon-color: #16b516; --protected-session-active-icon-color: #16b516;
--sync-status-error-pulse-color: #ff5528; --sync-status-error-pulse-color: #ff5528;
--center-pane-vert-layout-background-color-bgfx: #ffffff75;
--center-pane-horiz-layout-background-color-bgfx: #ffffffd6;
--right-pane-heading-color: gray; --right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color); --root-background: var(--left-pane-background-color);
@ -180,13 +183,13 @@
--inactive-tab-hover-background-color: #00000016; --inactive-tab-hover-background-color: #00000016;
--inactive-tab-text-color: #4e4e4e; --inactive-tab-text-color: #4e4e4e;
--alert-bar-background: #32637b29; --alert-bar-background: #f9cf2b29;
--badge-background-color: #00000011; --badge-background-color: #00000011;
--badge-text-color: var(--muted-text-color); --badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color); --promoted-attribute-card-background-color: #00000014;
--promoted-attribute-card-shadow-color: #00000033; --promoted-attribute-card-shadow: none;
--floating-button-shadow-color: #00000042; --floating-button-shadow-color: #00000042;
--floating-button-background-color: #eaeaeacc; --floating-button-background-color: #eaeaeacc;
@ -207,6 +210,8 @@
--new-tab-button-hover-background: white; --new-tab-button-hover-background: white;
--new-tab-button-hover-color: black; --new-tab-button-hover-color: black;
--right-pane-background-color: var(--main-background-color);
--right-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx); /* Only for the vertical layout */
--right-pane-item-hover-background: #ececec; --right-pane-item-hover-background: #ececec;
--right-pane-item-hover-color: inherit; --right-pane-item-hover-color: inherit;
@ -223,12 +228,12 @@
--code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2); --code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2);
--card-background-color: var(--accented-background-color); --card-background-color: #0000000d;
--card-background-hover-color: #f9f9f9; --card-background-hover-color: #f9f9f9;
--card-background-press-color: #efefef; --card-background-press-color: #efefef;
--card-border-color: #eaeaea; --card-border-color: transparent;
--card-shadow-color: rgba(0, 0, 0, 0.1); --card-shadow-color: rgba(0, 0, 0, 0.1);
--card-box-shadow: 0 0 12px var(--card-shadow-color); --card-box-shadow: none;
--calendar-color: var(--menu-text-color); --calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color); --calendar-weekday-labels-color: var(--muted-text-color);
@ -270,4 +275,10 @@
* The --custom-color-hue variable contains the hue of the user-selected note color. * The --custom-color-hue variable contains the hue of the user-selected note color.
* This value is unset for gray tones. */ * This value is unset for gray tones. */
--custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1); --custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1);
}
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 56%, 96%);
--modal-border-color: hsl(var(--custom-color-hue), 33%, 41%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);
} }

View File

@ -82,6 +82,7 @@
/* Theme capabilities */ /* Theme capabilities */
--tab-note-icons: true; --tab-note-icons: true;
--allow-background-effects: true;
/* To ensure that a tree item's custom color remains sufficiently contrasted and readable, /* To ensure that a tree item's custom color remains sufficiently contrasted and readable,
* the color is adjusted based on the current color scheme (light or dark). The lightness * the color is adjusted based on the current color scheme (light or dark). The lightness
@ -131,7 +132,8 @@ body.mobile .dropdown-menu .dropdown-menu {
body.desktop .dropdown-menu::before, body.desktop .dropdown-menu::before,
:root .ck.ck-dropdown__panel::before, :root .ck.ck-dropdown__panel::before,
:root .excalidraw .popover::before { :root .excalidraw .popover::before,
body.zen .note-split .ribbon-container .classic-toolbar-widget::before {
content: ""; content: "";
backdrop-filter: var(--dropdown-backdrop-filter); backdrop-filter: var(--dropdown-backdrop-filter);
border-radius: var(--dropdown-border-radius); border-radius: var(--dropdown-border-radius);

View File

@ -148,7 +148,7 @@ div.note-detail-empty {
--options-card-min-width: 500px; --options-card-min-width: 500px;
--options-card-max-width: 900px; --options-card-max-width: 900px;
--options-card-padding: 17px; --options-card-padding: 17px;
--options-title-font-size: 1rem; --options-title-font-size: .75rem;
--options-title-offset: 13px; --options-title-offset: 13px;
} }
/* Create a gap at the top of the option pages */ /* Create a gap at the top of the option pages */
@ -173,16 +173,19 @@ div.note-detail-empty {
} }
.options-section:not(.tn-no-card) { .options-section:not(.tn-no-card) {
margin: auto; margin-bottom: calc(var(--options-title-offset) + 26px) !important;
border-radius: 12px;
border: 1px solid var(--card-border-color) !important;
box-shadow: var(--card-box-shadow); box-shadow: var(--card-box-shadow);
border: 1px solid var(--card-border-color) !important;
border-radius: 8px;
background: var(--card-background-color); background: var(--card-background-color);
padding: var(--options-card-padding); padding: var(--options-card-padding);
margin-bottom: calc(var(--options-title-offset) + 26px) !important;
} }
body.desktop .option-section:not(.tn-no-card) { body.prefers-centered-content .options-section:not(.tn-no-card) {
margin-inline: auto;
}
body.desktop .options-section:not(.tn-no-card) {
min-width: var(--options-card-min-width); min-width: var(--options-card-min-width);
max-width: var(--options-card-max-width); max-width: var(--options-card-max-width);
} }
@ -193,9 +196,16 @@ body.desktop .option-section:not(.tn-no-card) {
padding-bottom: var(--default-padding); padding-bottom: var(--default-padding);
} }
.options-section:not(.tn-no-card) h4,
.options-section:not(.tn-no-card) h5 {
text-transform: uppercase;
letter-spacing: .4pt;
}
.options-section:not(.tn-no-card) h4 { .options-section:not(.tn-no-card) h4 {
font-size: var(--options-title-font-size); font-size: var(--options-title-font-size);
font-weight: bold; font-weight: 600;
color: var(--launcher-pane-text-color); color: var(--launcher-pane-text-color);
margin-top: calc(-1 * var(--options-card-padding) - var(--options-title-font-size) - var(--options-title-offset)) !important; margin-top: calc(-1 * var(--options-card-padding) - var(--options-title-font-size) - var(--options-title-offset)) !important;
margin-bottom: calc(var(--options-title-offset) + var(--options-card-padding)) !important; margin-bottom: calc(var(--options-title-offset) + var(--options-card-padding)) !important;

View File

@ -34,6 +34,7 @@
div.promoted-attributes-container { div.promoted-attributes-container {
margin-top: 8px; margin-top: 8px;
margin-bottom: 8px; margin-bottom: 8px;
margin-inline-start: 12px;
} }
/* /*

View File

@ -8,7 +8,7 @@
} }
:root { :root {
--dropdown-backdrop-filter: blur(10px) saturate(6); --dropdown-backdrop-filter: blur(20px) saturate(6);
--dropdown-border-radius: 10px; --dropdown-border-radius: 10px;
} }
@ -35,30 +35,52 @@ body.mobile {
} }
/* #region Mica */ /* #region Mica */
body.background-effects.platform-win32 { body.background-effects.platform-win32 {
/* Quirk: --background-material is read before "theme-supports-background-effects" class
* is applied. Apply the matterial even if the theme doesn't support it. */
--background-material: tabbed; --background-material: tabbed;
}
body.background-effects.theme-supports-background-effects.platform-win32 {
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx); --launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx); --launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx); --launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
--tab-background-color: var(--window-background-color-bgfx); --tab-background-color: var(--window-background-color-bgfx);
--new-tab-button-background: var(--window-background-color-bgfx); --new-tab-button-background: var(--window-background-color-bgfx);
--active-tab-background-color: var(--launcher-pane-horiz-background-color); --active-tab-background-color: var(--launcher-pane-horiz-background-color);
--root-background: transparent;
} }
body.background-effects.platform-win32.layout-vertical { body.background-effects.platform-win32.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--background-material: mica; --background-material: mica;
} }
body.background-effects.platform-win32, body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical {
body.background-effects.platform-win32 #root-widget { --left-pane-background-color: var(--window-background-color-bgfx);
--center-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx);
--right-pane-background-color: var(--right-pane-background-color-bgfx);
}
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal {
--center-pane-background-color-bgfx: var(--center-pane-horiz-layout-background-color-bgfx);
}
body.background-effects.theme-supports-background-effects.platform-win32,
body.background-effects.theme-supports-background-effects.platform-win32 #root-widget {
background: var(--window-background-color-bgfx) !important; background: var(--window-background-color-bgfx) !important;
} }
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container, body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal #horizontal-main-container,
body.background-effects.platform-win32.layout-vertical #vertical-main-container { body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical #vertical-main-container {
background-color: var(--root-background); background-color: var(--root-background);
} }
/* Note split with background effects */
body.background-effects.theme-supports-background-effects.platform-win32 #center-pane .note-split.bgfx {
--note-split-background-color: var(--center-pane-background-color-bgfx);
}
/* #endregion */ /* #endregion */
/* Matches when the left pane is collapsed */ /* Matches when the left pane is collapsed */
@ -72,9 +94,21 @@ body.layout-vertical #horizontal-main-container.left-pane-hidden #launcher-pane.
border-inline-end: 2px solid var(--left-pane-collapsed-border-color); border-inline-end: 2px solid var(--left-pane-collapsed-border-color);
} }
body.background-effects.zen #root-widget { /*
--main-background-color: transparent; * Zen mode
--root-background: transparent; */
@keyframes zen-formatting-toolbar-entrance {
from {
transform: translateY(200%);
} to {
transform: translateY(0);
}
}
body.zen .note-split .ribbon-container .classic-toolbar-widget {
position: relative;
animation: zen-formatting-toolbar-entrance 300ms ease-out;
} }
/* /*
@ -1171,23 +1205,18 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
* CENTER PANE * CENTER PANE
*/ */
#center-pane { /* The first visible note split */
background: var(--main-background-color); .vertical-layout #center-pane .note-split:not(.visible ~ .visible) {
}
.vertical-layout #center-pane {
border-radius: var(--center-pane-border-radius) 0 0 0; border-radius: var(--center-pane-border-radius) 0 0 0;
} }
.note-split { #center-pane .note-split {
padding-top: 2px; padding-top: 2px;
animation: note-entrance 100ms linear; background-color: var(--note-split-background-color, var(--main-background-color));
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
} }
.split-note-container-widget > .gutter { body:not(.background-effects) #center-pane .note-split {
background: var(--root-background) !important; animation: note-entrance 100ms linear;
transition: background 150ms ease-out;
} }
/* /*
@ -1200,9 +1229,9 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
@keyframes note-entrance { @keyframes note-entrance {
from { from {
opacity: 0; filter: opacity(0);
} to { } to {
opacity: 1; filter: opacity(1);
} }
} }
@ -1328,8 +1357,7 @@ div.promoted-attribute-cell {
--pa-card-padding-inline-end: 2px; --pa-card-padding-inline-end: 2px;
--input-background-color: transparent; --input-background-color: transparent;
box-shadow: 1px 1px 2px var(--promoted-attribute-card-shadow-color); box-shadow: var(--promoted-attribute-card-shadow);
display: inline-flex; display: inline-flex;
margin: 0; margin: 0;
border-radius: 8px; border-radius: 8px;
@ -1716,7 +1744,7 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
*/ */
#right-pane { #right-pane {
background: var(--main-background-color); background: var(--right-pane-background-color);
} }
#right-pane div.card-header { #right-pane div.card-header {

View File

@ -520,9 +520,7 @@
"max_content_width": { "max_content_width": {
"max_width_unit": "بكسل", "max_width_unit": "بكسل",
"title": "عرض المحتوى", "title": "عرض المحتوى",
"reload_button": "اعادة تحميل الواجهة", "max_width_label": "اقصى عرض للمحتوى"
"max_width_label": "اقصى عرض للمحتوى",
"reload_description": "تغييرات من خيارات المظهر"
}, },
"native_title_bar": { "native_title_bar": {
"enabled": "مفعل", "enabled": "مفعل",

View File

@ -1107,9 +1107,6 @@
"title": "内容宽度", "title": "内容宽度",
"default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。", "default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。",
"max_width_label": "内容最大宽度(像素)", "max_width_label": "内容最大宽度(像素)",
"apply_changes_description": "要应用内容宽度更改,请点击",
"reload_button": "重载前端",
"reload_description": "来自外观选项的更改",
"max_width_unit": "像素" "max_width_unit": "像素"
}, },
"native_title_bar": { "native_title_bar": {

View File

@ -1104,9 +1104,6 @@
"title": "Inhaltsbreite", "title": "Inhaltsbreite",
"default_description": "Trilium begrenzt standardmäßig die maximale Inhaltsbreite, um die Lesbarkeit für maximierte Bildschirme auf Breitbildschirmen zu verbessern.", "default_description": "Trilium begrenzt standardmäßig die maximale Inhaltsbreite, um die Lesbarkeit für maximierte Bildschirme auf Breitbildschirmen zu verbessern.",
"max_width_label": "Maximale Inhaltsbreite in Pixel", "max_width_label": "Maximale Inhaltsbreite in Pixel",
"apply_changes_description": "Um Änderungen an der Inhaltsbreite anzuwenden, klicke auf",
"reload_button": "Frontend neu laden",
"reload_description": "Änderungen an den Darstellungsoptionen",
"max_width_unit": "Pixel" "max_width_unit": "Pixel"
}, },
"native_title_bar": { "native_title_bar": {

View File

@ -1111,9 +1111,7 @@
"default_description": "Trilium by default limits max content width to improve readability for maximized screens on wide screens.", "default_description": "Trilium by default limits max content width to improve readability for maximized screens on wide screens.",
"max_width_label": "Max content width", "max_width_label": "Max content width",
"max_width_unit": "pixels", "max_width_unit": "pixels",
"apply_changes_description": "To apply content width changes, click on", "centerContent": "Keep content centered"
"reload_button": "reload frontend",
"reload_description": "changes from appearance options"
}, },
"native_title_bar": { "native_title_bar": {
"title": "Native Title Bar (requires app restart)", "title": "Native Title Bar (requires app restart)",
@ -1639,6 +1637,12 @@
"shared_locally": "This note is shared locally on {{- link}}.", "shared_locally": "This note is shared locally on {{- link}}.",
"help_link": "For help visit <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>." "help_link": "For help visit <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
}, },
"read-only-info": {
"read-only-note": "Currently viewing a read-only note.",
"auto-read-only-note": "This note is shown in a read-only mode for faster loading.",
"auto-read-only-learn-more": "Learn more",
"edit-note": "Edit note"
},
"note_types": { "note_types": {
"text": "Text", "text": "Text",
"code": "Code", "code": "Code",

View File

@ -1107,10 +1107,7 @@
"title": "Ancho del contenido", "title": "Ancho del contenido",
"default_description": "Trilium limita de forma predeterminada el ancho máximo del contenido para mejorar la legibilidad de ventanas maximizadas en pantallas anchas.", "default_description": "Trilium limita de forma predeterminada el ancho máximo del contenido para mejorar la legibilidad de ventanas maximizadas en pantallas anchas.",
"max_width_label": "Ancho máximo del contenido en píxeles", "max_width_label": "Ancho máximo del contenido en píxeles",
"max_width_unit": "píxeles", "max_width_unit": "píxeles"
"apply_changes_description": "Para aplicar cambios en el ancho del contenido, haga clic en",
"reload_button": "recargar la interfaz",
"reload_description": "cambios desde las opciones de apariencia"
}, },
"native_title_bar": { "native_title_bar": {
"title": "Barra de título nativa (requiere reiniciar la aplicación)", "title": "Barra de título nativa (requiere reiniciar la aplicación)",

View File

@ -1106,9 +1106,6 @@
"title": "Largeur du contenu", "title": "Largeur du contenu",
"default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur des écrans larges.", "default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur des écrans larges.",
"max_width_label": "Largeur maximale du contenu en pixels", "max_width_label": "Largeur maximale du contenu en pixels",
"apply_changes_description": "Pour appliquer les modifications de largeur du contenu, cliquez sur",
"reload_button": "recharger l'interface",
"reload_description": "changements par rapport aux options d'apparence",
"max_width_unit": "Pixels" "max_width_unit": "Pixels"
}, },
"native_title_bar": { "native_title_bar": {

View File

@ -1570,10 +1570,7 @@
"title": "Larghezza del contenuto", "title": "Larghezza del contenuto",
"default_description": "Per impostazione predefinita, Trilium limita la larghezza massima del contenuto per migliorare la leggibilità sugli schermi più grandi.", "default_description": "Per impostazione predefinita, Trilium limita la larghezza massima del contenuto per migliorare la leggibilità sugli schermi più grandi.",
"max_width_label": "Larghezza massima del contenuto", "max_width_label": "Larghezza massima del contenuto",
"max_width_unit": "pixel", "max_width_unit": "pixel"
"apply_changes_description": "Per applicare le modifiche alla larghezza del contenuto, fare clic su",
"reload_button": "ricarica frontend",
"reload_description": "modifiche dalle opzioni di aspetto"
}, },
"native_title_bar": { "native_title_bar": {
"title": "Barra del titolo nativa (richiede il riavvio dell'app)", "title": "Barra del titolo nativa (richiede il riavvio dell'app)",

View File

@ -833,13 +833,10 @@
"theme_defined": "テーマが定義されました" "theme_defined": "テーマが定義されました"
}, },
"max_content_width": { "max_content_width": {
"reload_button": "フロントエンドをリロード",
"title": "コンテンツ幅", "title": "コンテンツ幅",
"default_description": "Triliumは、ワイドスクリーンで最大化された画面での可読性を向上させるために、デフォルトでコンテンツの最大幅を制限しています。", "default_description": "Triliumは、ワイドスクリーンで最大化された画面での可読性を向上させるために、デフォルトでコンテンツの最大幅を制限しています。",
"max_width_label": "最大コンテンツ幅", "max_width_label": "最大コンテンツ幅",
"max_width_unit": "ピクセル", "max_width_unit": "ピクセル"
"apply_changes_description": "コンテンツ幅の変更を適用するには、クリックしてください",
"reload_description": "外観設定から変更"
}, },
"theme": { "theme": {
"title": "アプリのテーマ", "title": "アプリのテーマ",

View File

@ -1464,10 +1464,7 @@
"title": "Szerokość zawartości", "title": "Szerokość zawartości",
"default_description": "Trilium domyślnie ogranicza maksymalną szerokość zawartości, aby poprawić czytelność na zmaksymalizowanych ekranach o dużej szerokości.", "default_description": "Trilium domyślnie ogranicza maksymalną szerokość zawartości, aby poprawić czytelność na zmaksymalizowanych ekranach o dużej szerokości.",
"max_width_label": "Maksymalna szerokość zawartości", "max_width_label": "Maksymalna szerokość zawartości",
"max_width_unit": "piksele", "max_width_unit": "piksele"
"apply_changes_description": "Aby zastosować zmiany szerokości zawartości, kliknij na",
"reload_button": "przeładuj frontend",
"reload_description": "zmiany z opcji wyglądu"
}, },
"native_title_bar": { "native_title_bar": {
"title": "Natywny pasek tytułu (wymaga ponownego uruchomienia aplikacji)", "title": "Natywny pasek tytułu (wymaga ponownego uruchomienia aplikacji)",

View File

@ -1082,10 +1082,7 @@
"title": "Largura do Conteúdo", "title": "Largura do Conteúdo",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em ecrãs largos.", "default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em ecrãs largos.",
"max_width_label": "Largura máxima do conteúdo", "max_width_label": "Largura máxima do conteúdo",
"max_width_unit": "pixels", "max_width_unit": "pixels"
"apply_changes_description": "Para aplicar as alterações de largura do conteúdo, clique em",
"reload_button": "recarregar frontend",
"reload_description": "alterações de opções de aparência"
}, },
"native_title_bar": { "native_title_bar": {
"title": "Barra de Título Nativa (requer recarregar a app)", "title": "Barra de Título Nativa (requer recarregar a app)",

View File

@ -1304,9 +1304,6 @@
"title": "Largura do Conteúdo", "title": "Largura do Conteúdo",
"max_width_label": "Largura máxima do conteúdo", "max_width_label": "Largura máxima do conteúdo",
"max_width_unit": "pixels", "max_width_unit": "pixels",
"apply_changes_description": "Para aplicar as alterações de largura do conteúdo, clique em",
"reload_button": "recarregar frontend",
"reload_description": "alterações de opções de aparência",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em telas wide." "default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em telas wide."
}, },
"native_title_bar": { "native_title_bar": {

View File

@ -796,12 +796,9 @@
"modal_body_text": "Din cauza limitărilor la nivel de navigator, nu este posibilă citirea clipboard-ului din JavaScript. Inserați Markdown-ul pentru a-l importa în caseta de mai jos și dați clic pe butonul Import" "modal_body_text": "Din cauza limitărilor la nivel de navigator, nu este posibilă citirea clipboard-ului din JavaScript. Inserați Markdown-ul pentru a-l importa în caseta de mai jos și dați clic pe butonul Import"
}, },
"max_content_width": { "max_content_width": {
"apply_changes_description": "Pentru a aplica schimbările de lățime a conținutului, dați click pe",
"default_description": "În mod implicit Trilium limitează lățimea conținutului pentru a îmbunătăți lizibilitatea pentru ferestrele maximizate pe ecrane late.", "default_description": "În mod implicit Trilium limitează lățimea conținutului pentru a îmbunătăți lizibilitatea pentru ferestrele maximizate pe ecrane late.",
"max_width_label": "Lungimea maximă a conținutului", "max_width_label": "Lungimea maximă a conținutului",
"max_width_unit": "pixeli", "max_width_unit": "pixeli",
"reload_button": "reîncarcă interfața",
"reload_description": "schimbări din opțiunile de afișare",
"title": "Lățime conținut" "title": "Lățime conținut"
}, },
"mobile_detail_menu": { "mobile_detail_menu": {

View File

@ -1203,11 +1203,8 @@
"max_content_width": { "max_content_width": {
"max_width_unit": "пикселей", "max_width_unit": "пикселей",
"title": "Ширина контентной области", "title": "Ширина контентной области",
"reload_button": "перезагрузить интерфейс",
"default_description": "Trilium по умолчанию ограничивает максимальную ширину контента, чтобы улучшить читаемость на широких экранах.", "default_description": "Trilium по умолчанию ограничивает максимальную ширину контента, чтобы улучшить читаемость на широких экранах.",
"max_width_label": "Максимальная ширина контентной области", "max_width_label": "Максимальная ширина контентной области"
"apply_changes_description": "Чтобы применить изменения, нажмите на",
"reload_description": "изменения в параметрах внешнего вида"
}, },
"native_title_bar": { "native_title_bar": {
"enabled": "включено", "enabled": "включено",

View File

@ -1104,9 +1104,6 @@
"title": "內容寬度", "title": "內容寬度",
"default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。", "default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。",
"max_width_label": "內容最大寬度(像素)", "max_width_label": "內容最大寬度(像素)",
"apply_changes_description": "要套用內容寬度更改,請點擊",
"reload_button": "重新載入前端",
"reload_description": "來自外觀選項的更改",
"max_width_unit": "像素" "max_width_unit": "像素"
}, },
"native_title_bar": { "native_title_bar": {

View File

@ -1204,10 +1204,7 @@
"title": "Ширина вмісту", "title": "Ширина вмісту",
"default_description": "Trilium за замовчуванням обмежує максимальну ширину вмісту, щоб поліпшити читабельність на широкоформатних екранах у режимі максимального розширення.", "default_description": "Trilium за замовчуванням обмежує максимальну ширину вмісту, щоб поліпшити читабельність на широкоформатних екранах у режимі максимального розширення.",
"max_width_label": "Максимальна ширина вмісту", "max_width_label": "Максимальна ширина вмісту",
"max_width_unit": "пікселів", "max_width_unit": "пікселів"
"apply_changes_description": "Щоб застосувати зміни ширини вмісту, натисніть на",
"reload_button": "перезавантажити інтерфейс",
"reload_description": "зміни в параметрах зовнішнього вигляду"
}, },
"native_title_bar": { "native_title_bar": {
"title": "Нативний рядок заголовка (потрібен перезапуск)", "title": "Нативний рядок заголовка (потрібен перезапуск)",

View File

@ -23,6 +23,24 @@ export class CssVarReader {
return (!isNaN(number.valueOf()) ? number.valueOf() : defaultValue) return (!isNaN(number.valueOf()) ? number.valueOf() : defaultValue)
} }
asBoolean(defaultValue?: boolean) {
let value = this.value.toLocaleLowerCase().trim();
let result: boolean | undefined;
switch (value) {
case "true":
case "1":
result = true;
break;
case "false":
case "0":
result = false;
break;
}
return (result !== undefined) ? result : defaultValue;
}
asEnum<T>(enumType: T, defaultValue?: T[keyof T]): T[keyof T] | undefined { asEnum<T>(enumType: T, defaultValue?: T[keyof T]): T[keyof T] | undefined {
let result: T[keyof T] | undefined; let result: T[keyof T] | undefined;

View File

@ -6,7 +6,7 @@
.floating-buttons-children, .floating-buttons-children,
.show-floating-buttons { .show-floating-buttons {
position: absolute; position: absolute;
top: var(--floating-buttons-vert-offset, 10px); top: var(--floating-buttons-vert-offset, 14px);
inset-inline-end: var(--floating-buttons-horiz-offset, 10px); inset-inline-end: var(--floating-buttons-horiz-offset, 10px);
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -1,6 +1,6 @@
import { t } from "i18next"; import { t } from "i18next";
import "./FloatingButtons.css"; import "./FloatingButtons.css";
import { useNoteContext, useNoteLabel, useNoteLabelBoolean } from "./react/hooks"; import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useTriliumEvent } from "./react/hooks";
import { useContext, useEffect, useMemo, useState } from "preact/hooks"; import { useContext, useEffect, useMemo, useState } from "preact/hooks";
import { ParentComponent } from "./react/react_utils"; import { ParentComponent } from "./react/react_utils";
import { EventData, EventNames } from "../components/app_context"; import { EventData, EventNames } from "../components/app_context";
@ -20,6 +20,7 @@ interface FloatingButtonsProps {
* properly handle rounded corners, as defined by the --border-radius CSS variable. * properly handle rounded corners, as defined by the --border-radius CSS variable.
*/ */
export default function FloatingButtons({ items }: FloatingButtonsProps) { export default function FloatingButtons({ items }: FloatingButtonsProps) {
const [ top, setTop ] = useState(0);
const { note, noteContext } = useNoteContext(); const { note, noteContext } = useNoteContext();
const parentComponent = useContext(ParentComponent); const parentComponent = useContext(ParentComponent);
const [ viewType ] = useNoteLabel(note, "viewType"); const [ viewType ] = useNoteLabel(note, "viewType");
@ -47,8 +48,14 @@ export default function FloatingButtons({ items }: FloatingButtonsProps) {
const [ visible, setVisible ] = useState(true); const [ visible, setVisible ] = useState(true);
useEffect(() => setVisible(true), [ note ]); useEffect(() => setVisible(true), [ note ]);
useTriliumEvent("contentSafeMarginChanged", (e) => {
if (e.noteContext === noteContext) {
setTop(e.top);
}
});
return ( return (
<div className="floating-buttons no-print"> <div className="floating-buttons no-print" style={{top}}>
<div className={`floating-buttons-children ${!visible ? "temporarily-hidden" : ""}`}> <div className={`floating-buttons-children ${!visible ? "temporarily-hidden" : ""}`}>
{context && items.map((Component) => ( {context && items.map((Component) => (
<Component {...context} /> <Component {...context} />

View File

@ -4,7 +4,7 @@ import Component from "../components/component";
import NoteContext from "../components/note_context"; import NoteContext from "../components/note_context";
import FNote from "../entities/fnote"; import FNote from "../entities/fnote";
import ActionButton, { ActionButtonProps } from "./react/ActionButton"; import ActionButton, { ActionButtonProps } from "./react/ActionButton";
import { useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks"; import { useIsNoteReadOnly, useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils"; import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
import server from "../services/server"; import server from "../services/server";
@ -13,8 +13,6 @@ import toast from "../services/toast";
import { t } from "../services/i18n"; import { t } from "../services/i18n";
import { copyImageReferenceToClipboard } from "../services/image"; import { copyImageReferenceToClipboard } from "../services/image";
import tree from "../services/tree"; import tree from "../services/tree";
import protected_session_holder from "../services/protected_session_holder";
import options from "../services/options";
import { getHelpUrlForNote } from "../services/in_app_help"; import { getHelpUrlForNote } from "../services/in_app_help";
import froca from "../services/froca"; import froca from "../services/froca";
import NoteLink from "./react/NoteLink"; import NoteLink from "./react/NoteLink";
@ -101,48 +99,26 @@ function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingBut
/> />
} }
function EditButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) { function EditButton({ note, noteContext }: FloatingButtonContext) {
const [ animationClass, setAnimationClass ] = useState(""); const [animationClass, setAnimationClass] = useState("");
const [ isEnabled, setIsEnabled ] = useState(false); const {isReadOnly, enableEditing} = useIsNoteReadOnly(note, noteContext);
const isReadOnlyInfoBarDismissed = false; // TODO
useEffect(() => { useEffect(() => {
noteContext.isReadOnly().then(isReadOnly => { if (isReadOnly) {
setIsEnabled(
isDefaultViewMode
&& (!note.isProtected || protected_session_holder.isProtectedSessionAvailable())
&& !options.is("databaseReadonly")
&& isReadOnly
);
});
}, [ note ]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsEnabled(false);
}
});
// make the edit button stand out on the first display, otherwise
// it's difficult to notice that the note is readonly
useEffect(() => {
if (isEnabled) {
setAnimationClass("bx-tada bx-lg"); setAnimationClass("bx-tada bx-lg");
setTimeout(() => { setTimeout(() => {
setAnimationClass(""); setAnimationClass("");
}, 1700); }, 1700);
} }
}, [ isEnabled ]); }, [ isReadOnly ]);
return isEnabled && <FloatingButton return !!isReadOnly && isReadOnlyInfoBarDismissed && <FloatingButton
text={t("edit_button.edit_this_note")} text={t("edit_button.edit_this_note")}
icon="bx bx-pencil" icon="bx bx-pencil"
className={animationClass} className={animationClass}
onClick={() => { onClick={() => enableEditing()}
if (noteContext.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext });
}
}}
/> />
} }

View File

@ -0,0 +1,19 @@
body.zen div.read-only-note-info-bar-widget {
width: fit-content;
max-width: var(--max-content-width);
border-radius: 8px;
border: unset;
margin: 0 auto;
}
.read-only-note-info-bar-widget-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
}
:root div.read-only-note-info-bar-widget button {
white-space: nowrap;
padding: 2px 8px;
}

View File

@ -0,0 +1,36 @@
import "./ReadOnlyNoteInfoBar.css";
import { t } from "../services/i18n";
import { useIsNoteReadOnly, useNoteContext, useTriliumEvent } from "./react/hooks"
import Button from "./react/Button";
import InfoBar from "./react/InfoBar";
export default function ReadOnlyNoteInfoBar(props: {}) {
const {note, noteContext} = useNoteContext();
const {isReadOnly, enableEditing} = useIsNoteReadOnly(note, noteContext);
const isExplicitReadOnly = note?.isLabelTruthy("readOnly");
return <InfoBar className="read-only-note-info-bar-widget"
type={(isExplicitReadOnly ? "subtle" : "prominent")}
style={{display: (!isReadOnly) ? "none" : undefined}}>
<div class="read-only-note-info-bar-widget-content">
{(isExplicitReadOnly) ? (
<div>{t("read-only-info.read-only-note")}</div>
) : (
<div>
{t("read-only-info.auto-read-only-note")}
&nbsp;
<a class="tn-link"
href="https://docs.triliumnotes.org/user-guide/concepts/notes/read-only-notes#automatic-read-only-mode">
{t("read-only-info.auto-read-only-learn-more")}
</a>
</div>
)}
<Button text={t("read-only-info.edit-note")}
icon="bx-pencil" onClick={() => enableEditing()} />
</div>
</InfoBar>
}

View File

@ -1,9 +1,16 @@
.note-list-widget { .note-list-widget {
min-height: 0; min-height: 0;
max-width: var(--max-content-width); /* Inherited from .note-split */
overflow: auto; overflow: auto;
contain: none !important; contain: none !important;
} }
body.prefers-centered-content .note-list-widget {
/* Horizontally center the widget in its parent when the "Keep content centered" option is on */
margin-inline: auto;
}
.note-list-widget .note-list { .note-list-widget .note-list {
padding: 10px; padding: 10px;
} }

View File

@ -0,0 +1,63 @@
import { EventData } from "../../components/app_context";
import BasicWidget from "../basic_widget";
import Container from "./container";
import NoteContext from "../../components/note_context";
export default class ContentHeader extends Container<BasicWidget> {
noteContext?: NoteContext;
thisElement?: HTMLElement;
parentElement?: HTMLElement;
resizeObserver: ResizeObserver;
currentHeight: number = 0;
currentSafeMargin: number = NaN;
constructor() {
super();
this.css("contain", "unset");
this.resizeObserver = new ResizeObserver(this.onResize.bind(this));
}
setNoteContextEvent({ noteContext }: EventData<"setNoteContext">) {
this.noteContext = noteContext;
this.init();
}
init() {
this.parentElement = this.parent!.$widget.get(0);
if (!this.parentElement) {
console.warn("No parent set for <ContentHeader>.");
return;
}
this.thisElement = this.$widget.get(0)!;
this.resizeObserver.observe(this.thisElement);
this.parentElement.addEventListener("scroll", this.updateSafeMargin.bind(this));
}
updateSafeMargin() {
const newSafeMargin = Math.max(this.currentHeight - this.parentElement!.scrollTop, 0);
if (newSafeMargin !== this.currentSafeMargin) {
this.currentSafeMargin = newSafeMargin;
this.triggerEvent("contentSafeMarginChanged", {
top: newSafeMargin,
noteContext: this.noteContext!
});
}
}
onResize(entries: ResizeObserverEntry[]) {
for (const entry of entries) {
if (entry.target === this.thisElement) {
this.currentHeight = entry.contentRect.height;
this.updateSafeMargin();
}
}
}
}

View File

@ -1,9 +1,10 @@
import { EventData } from "../../components/app_context.js"; import { EventData } from "../../components/app_context.js";
import { LOCALES } from "@triliumnext/commons";
import { readCssVar } from "../../utils/css-var.js";
import FlexContainer from "./flex_container.js"; import FlexContainer from "./flex_container.js";
import options from "../../services/options.js"; import options from "../../services/options.js";
import type BasicWidget from "../basic_widget.js"; import type BasicWidget from "../basic_widget.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import { LOCALES } from "@triliumnext/commons";
/** /**
* The root container is the top-most widget/container, from which the entire layout derives. * The root container is the top-most widget/container, from which the entire layout derives.
@ -30,9 +31,11 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
window.visualViewport?.addEventListener("resize", () => this.#onMobileResize()); window.visualViewport?.addEventListener("resize", () => this.#onMobileResize());
} }
this.#setMotion(options.is("motionEnabled")); this.#setMaxContentWidth();
this.#setShadows(options.is("shadowsEnabled")); this.#setMotion();
this.#setBackdropEffects(options.is("backdropEffectsEnabled")); this.#setShadows();
this.#setBackdropEffects();
this.#setThemeCapabilities();
this.#setLocaleAndDirection(options.get("locale")); this.#setLocaleAndDirection(options.get("locale"));
return super.render(); return super.render();
@ -40,15 +43,21 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.isOptionReloaded("motionEnabled")) { if (loadResults.isOptionReloaded("motionEnabled")) {
this.#setMotion(options.is("motionEnabled")); this.#setMotion();
} }
if (loadResults.isOptionReloaded("shadowsEnabled")) { if (loadResults.isOptionReloaded("shadowsEnabled")) {
this.#setShadows(options.is("shadowsEnabled")); this.#setShadows();
} }
if (loadResults.isOptionReloaded("backdropEffectsEnabled")) { if (loadResults.isOptionReloaded("backdropEffectsEnabled")) {
this.#setBackdropEffects(options.is("backdropEffectsEnabled")); this.#setBackdropEffects();
}
if (loadResults.isOptionReloaded("maxContentWidth")
|| loadResults.isOptionReloaded("centerContent")) {
this.#setMaxContentWidth();
} }
} }
@ -58,19 +67,38 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened); this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
} }
#setMotion(enabled: boolean) { #setMaxContentWidth() {
const width = Math.max(options.getInt("maxContentWidth") || 0, 640);
document.body.style.setProperty("--preferred-max-content-width", `${width}px`);
document.body.classList.toggle("prefers-centered-content", options.is("centerContent"));
}
#setMotion() {
const enabled = options.is("motionEnabled");
document.body.classList.toggle("motion-disabled", !enabled); document.body.classList.toggle("motion-disabled", !enabled);
jQuery.fx.off = !enabled; jQuery.fx.off = !enabled;
} }
#setShadows(enabled: boolean) { #setShadows() {
const enabled = options.is("shadowsEnabled");
document.body.classList.toggle("shadows-disabled", !enabled); document.body.classList.toggle("shadows-disabled", !enabled);
} }
#setBackdropEffects(enabled: boolean) { #setBackdropEffects() {
const enabled = options.is("backdropEffectsEnabled");
document.body.classList.toggle("backdrop-effects-disabled", !enabled); document.body.classList.toggle("backdrop-effects-disabled", !enabled);
} }
#setThemeCapabilities() {
// Supports background effects
const useBgfx = readCssVar(document.documentElement, "allow-background-effects")
.asBoolean(false);
document.body.classList.toggle("theme-supports-background-effects", useBgfx);
}
#setLocaleAndDirection(locale: string) { #setLocaleAndDirection(locale: string) {
const correspondingLocale = LOCALES.find(l => l.id === locale); const correspondingLocale = LOCALES.find(l => l.id === locale);
document.body.lang = locale; document.body.lang = locale;

View File

@ -57,17 +57,19 @@ const TPL = /*html*/`\
} }
</style> </style>
<div class="modal-dialog modal-lg" role="document"> <div class="quick-edit-dialog-wrapper">
<div class="modal-content"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-header"> <div class="modal-content">
<div class="modal-title"> <div class="modal-header">
<!-- This is where the first child will be injected --> <div class="modal-title">
<!-- This is where the first child will be injected -->
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body"> <div class="modal-body">
<!-- This is where all but the first child will be injected. --> <!-- This is where all but the first child will be injected. -->
</div>
</div> </div>
</div> </div>
</div> </div>
@ -79,6 +81,7 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
private noteContext: NoteContext; private noteContext: NoteContext;
private $modalHeader!: JQuery<HTMLElement>; private $modalHeader!: JQuery<HTMLElement>;
private $modalBody!: JQuery<HTMLElement>; private $modalBody!: JQuery<HTMLElement>;
private $wrapper!: JQuery<HTMLDivElement>;
constructor() { constructor() {
super(); super();
@ -93,6 +96,7 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
const $newWidget = $(TPL); const $newWidget = $(TPL);
this.$modalHeader = $newWidget.find(".modal-title"); this.$modalHeader = $newWidget.find(".modal-title");
this.$modalBody = $newWidget.find(".modal-body"); this.$modalBody = $newWidget.find(".modal-body");
this.$wrapper = $newWidget.find(".quick-edit-dialog-wrapper");
const children = this.$widget.children(); const children = this.$widget.children();
this.$modalHeader.append(children[0]); this.$modalHeader.append(children[0]);
@ -112,6 +116,21 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
} }
}); });
const colorClass = this.noteContext.note?.getColorClass();
const wrapperElement = this.$wrapper.get(0)!;
if (colorClass) {
wrapperElement.className = "quick-edit-dialog-wrapper " + colorClass;
} else {
wrapperElement.className = "quick-edit-dialog-wrapper";
}
const customHue = getComputedStyle(wrapperElement).getPropertyValue("--custom-color-hue");
if (customHue) {
/* Apply the tinted-dialog class only if the custom color CSS class specifies a hue */
wrapperElement.classList.add("tinted-quick-edit-dialog");
}
const activeEl = document.activeElement; const activeEl = document.activeElement;
if (activeEl && "blur" in activeEl) { if (activeEl && "blur" in activeEl) {
(activeEl as HTMLElement).blur(); (activeEl as HTMLElement).blur();

View File

@ -39,10 +39,16 @@ const TPL = /*html*/`
<div class="note-detail"> <div class="note-detail">
<style> <style>
.note-detail { .note-detail {
max-width: var(--max-content-width); /* Inherited from .note-split */
font-family: var(--detail-font-family); font-family: var(--detail-font-family);
font-size: var(--detail-font-size); font-size: var(--detail-font-size);
} }
body.prefers-centered-content .note-detail {
/* Horizontally center the widget in its parent when the "Keep content centered" option is on */
margin-inline: auto;
}
.note-detail.full-height { .note-detail.full-height {
height: 100%; height: 100%;
} }

View File

@ -52,6 +52,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
const note = this.noteContext?.note; const note = this.noteContext?.note;
if (!note) { if (!note) {
this.$widget.addClass("bgfx");
return; return;
} }
@ -61,7 +62,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
this.$widget.addClass(utils.getNoteTypeClass(note.type)); this.$widget.addClass(utils.getNoteTypeClass(note.type));
this.$widget.addClass(utils.getMimeTypeClass(note.mime)); this.$widget.addClass(utils.getMimeTypeClass(note.mime));
this.$widget.toggleClass(["bgfx", "options"], note.isOptions());
this.$widget.toggleClass("protected", note.isProtected); this.$widget.toggleClass("protected", note.isProtected);
const noteLanguage = note?.getLabelValue("language"); const noteLanguage = note?.getLabelValue("language");
@ -70,7 +71,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
} }
#isFullWidthNote(note: FNote) { #isFullWidthNote(note: FNote) {
if (["image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) { if (["code", "image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) {
return true; return true;
} }

View File

@ -0,0 +1,21 @@
.info-bar {
--link-color: var(--main-text-color);
margin-top: 4px;
contain: unset !important;
padding: 8px 20px;
color: var(--read-only-note-info-bar-color);
font-size: .9em;
}
.info-bar-prominent {
background: var(--alert-bar-background, var(--accented-background-color));
}
.info-bar-subtle {
color: var(--muted-text-color);
border-bottom: 1px solid var(--main-border-color);
margin-block: 0;
margin-inline: 10px;
padding-inline: 12px;
}

View File

@ -0,0 +1,19 @@
import "./InfoBar.css";
import { ComponentChildren, CSSProperties } from "preact";
export type InfoBarParams = {
type: "prominent" | "subtle",
className: string;
style: CSSProperties
children: ComponentChildren;
};
export default function InfoBar(props: InfoBarParams) {
return <div className={`info-bar ${props.className} info-bar-${props.type}`} style={props.style}>
{props?.children}
</div>
}
InfoBar.defaultProps = {
type: "prominent"
} as InfoBarParams

View File

@ -1,25 +1,26 @@
import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; import { CSSProperties } from "preact/compat";
import { CommandListenerData, EventData, EventNames } from "../../components/app_context"; import { DragData } from "../note_tree";
import { ParentComponent } from "./react_utils";
import SpacedUpdate from "../../services/spaced_update";
import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons"; import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons";
import options, { type OptionValue } from "../../services/options"; import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils"; import { ParentComponent } from "./react_utils";
import NoteContext from "../../components/note_context";
import BasicWidget, { ReactWrappedWidget } from "../basic_widget";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import FBlob from "../../entities/fblob";
import NoteContextAwareWidget from "../note_context_aware_widget";
import { RefObject, VNode } from "preact"; import { RefObject, VNode } from "preact";
import { Tooltip } from "bootstrap"; import { Tooltip } from "bootstrap";
import { CSSProperties } from "preact/compat"; import { ViewMode } from "../../services/link";
import appContext, { CommandListenerData, EventData, EventNames } from "../../components/app_context";
import attributes from "../../services/attributes";
import BasicWidget, { ReactWrappedWidget } from "../basic_widget";
import Component from "../../components/component";
import FBlob from "../../entities/fblob";
import FNote from "../../entities/fnote";
import keyboard_actions from "../../services/keyboard_actions"; import keyboard_actions from "../../services/keyboard_actions";
import Mark from "mark.js"; import Mark from "mark.js";
import { DragData } from "../note_tree"; import NoteContext from "../../components/note_context";
import Component from "../../components/component"; import NoteContextAwareWidget from "../note_context_aware_widget";
import options, { type OptionValue } from "../../services/options";
import protected_session_holder from "../../services/protected_session_holder";
import SpacedUpdate from "../../services/spaced_update";
import toast, { ToastOptions } from "../../services/toast"; import toast, { ToastOptions } from "../../services/toast";
import { ViewMode } from "../../services/link"; import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils";
export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) { export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
const parentComponent = useContext(ParentComponent); const parentComponent = useContext(ParentComponent);
@ -701,3 +702,51 @@ export function useResizeObserver(ref: RefObject<HTMLElement>, callback: () => v
return () => observer.disconnect(); return () => observer.disconnect();
}, [ callback, ref ]); }, [ callback, ref ]);
} }
/**
* Indicates that the current note is in read-only mode, while an editing mode is available,
* and provides a way to switch to editing mode.
*/
export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) {
const [isReadOnly, setIsReadOnly] = useState<boolean | undefined>(undefined);
const enableEditing = useCallback(() => {
if (noteContext?.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", {noteContext});
}
}, [noteContext]);
useEffect(() => {
if (note && noteContext) {
isNoteReadOnly(note, noteContext).then((readOnly) => {
setIsReadOnly(readOnly);
});
}
}, [note, noteContext]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsReadOnly(false);
}
});
return {isReadOnly, enableEditing};
}
async function isNoteReadOnly(note: FNote, noteContext: NoteContext) {
if (note.isProtected && !protected_session_holder.isProtectedSessionAvailable()) {
return false;
}
if (options.is("databaseReadonly")) {
return false;
}
if (noteContext.viewScope?.viewMode !== "default" || !await noteContext.isReadOnly()) {
return false;
}
return true;
}

View File

@ -1,19 +1,20 @@
import { ConvertToAttachmentResponse } from "@triliumnext/commons"; import { ConvertToAttachmentResponse } from "@triliumnext/commons";
import appContext, { CommandNames } from "../../components/app_context";
import FNote from "../../entities/fnote"
import dialog from "../../services/dialog";
import { t } from "../../services/i18n"
import server from "../../services/server";
import toast from "../../services/toast";
import ws from "../../services/ws";
import ActionButton from "../react/ActionButton"
import Dropdown from "../react/Dropdown";
import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/utils"; import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/utils";
import { ParentComponent } from "../react/react_utils"; import { ParentComponent } from "../react/react_utils";
import { t } from "../../services/i18n"
import { useContext } from "preact/hooks"; import { useContext } from "preact/hooks";
import NoteContext from "../../components/note_context"; import { useIsNoteReadOnly } from "../react/hooks";
import ActionButton from "../react/ActionButton"
import appContext, { CommandNames } from "../../components/app_context";
import branches from "../../services/branches"; import branches from "../../services/branches";
import dialog from "../../services/dialog";
import Dropdown from "../react/Dropdown";
import FNote from "../../entities/fnote"
import NoteContext from "../../components/note_context";
import server from "../../services/server";
import toast from "../../services/toast";
import ws from "../../services/ws";
interface NoteActionsProps { interface NoteActionsProps {
note?: FNote; note?: FNote;
@ -52,6 +53,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
const isMac = getIsMac(); const isMac = getIsMac();
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type); const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type);
const isSearchOrBook = ["search", "book"].includes(note.type); const isSearchOrBook = ["search", "book"].includes(note.type);
const {isReadOnly, enableEditing} = useIsNoteReadOnly(note, noteContext);
return ( return (
<Dropdown <Dropdown
@ -59,8 +61,14 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
className="note-actions" className="note-actions"
hideToggleArrow hideToggleArrow
noSelectButtonStyle noSelectButtonStyle
iconAction iconAction>
>
{isReadOnly && <>
<CommandItem icon="bx bx-pencil" text={t("read-only-info.edit-note")}
command={() => enableEditing()} />
<FormDropdownDivider />
</>}
{canBeConvertedToAttachment && <ConvertToAttachment note={note} /> } {canBeConvertedToAttachment && <ConvertToAttachment note={note} /> }
{note.type === "render" && <CommandItem command="renderActiveNote" icon="bx bx-extension" text={t("note_actions.re_render_note")} />} {note.type === "render" && <CommandItem command="renderActiveNote" icon="bx bx-extension" text={t("note_actions.re_render_note")} />}
<CommandItem command="findInText" icon="bx bx-search" disabled={!isSearchable} text={t("note_actions.search_in_note")} /> <CommandItem command="findInText" icon="bx bx-search" disabled={!isSearchable} text={t("note_actions.search_in_note")} />

View File

@ -0,0 +1,7 @@
.shared-info-widget {
display: flex;
}
.shared-info-widget button {
margin-inline-start: 8px;
}

View File

@ -1,11 +1,12 @@
import { useEffect, useState } from "preact/hooks"; import "./shared_info.css";
import { t } from "../services/i18n"; import { t } from "../services/i18n";
import Alert from "./react/Alert"; import { useEffect, useState } from "preact/hooks";
import { useNoteContext, useTriliumEvent, useTriliumOption } from "./react/hooks"; import { useNoteContext, useTriliumEvent, useTriliumOption } from "./react/hooks";
import FNote from "../entities/fnote";
import attributes from "../services/attributes"; import attributes from "../services/attributes";
import RawHtml from "./react/RawHtml"; import FNote from "../entities/fnote";
import HelpButton from "./react/HelpButton"; import HelpButton from "./react/HelpButton";
import InfoBar from "./react/InfoBar";
import RawHtml from "./react/RawHtml";
export default function SharedInfo() { export default function SharedInfo() {
const { note } = useNoteContext(); const { note } = useNoteContext();
@ -35,7 +36,7 @@ export default function SharedInfo() {
link = `${location.protocol}//${host}${location.pathname}share/${shareId}`; link = `${location.protocol}//${host}${location.pathname}share/${shareId}`;
} }
setLink(`<a href="${link}" class="external">${link}</a>`); setLink(`<a href="${link}" class="external tn-link">${link}</a>`);
} }
useEffect(refresh, [ note ]); useEffect(refresh, [ note ]);
@ -48,20 +49,14 @@ export default function SharedInfo() {
}); });
return ( return (
<Alert className="shared-info-widget" type="warning" style={{ <InfoBar className="shared-info-widget" type="subtle" style={{display: (!link) ? "none" : undefined}}>
contain: "none",
margin: "10px",
padding: "10px",
fontWeight: "bold",
display: !link ? "none" : undefined
}}>
{link && ( {link && (
<RawHtml html={syncServerHost <RawHtml html={syncServerHost
? t("shared_info.shared_publicly", { link }) ? t("shared_info.shared_publicly", { link })
: t("shared_info.shared_locally", { link })} /> : t("shared_info.shared_locally", { link })} />
)} )}
<HelpButton helpPage="R9pX4DGra2Vt" style={{ width: "24px", height: "24px" }} /> <HelpButton helpPage="R9pX4DGra2Vt" style={{ width: "24px", height: "24px" }} />
</Alert> </InfoBar>
) )
} }

View File

@ -284,7 +284,8 @@ function SmoothScrollEnabledOption() {
} }
function MaxContentWidth() { function MaxContentWidth() {
const [ maxContentWidth, setMaxContentWidth ] = useTriliumOption("maxContentWidth"); const [maxContentWidth, setMaxContentWidth] = useTriliumOption("maxContentWidth");
const [centerContent, setCenterContent] = useTriliumOptionBool("centerContent");
return ( return (
<OptionsSection title={t("max_content_width.title")}> <OptionsSection title={t("max_content_width.title")}>
@ -300,9 +301,9 @@ function MaxContentWidth() {
</FormGroup> </FormGroup>
</Column> </Column>
<p> <FormCheckbox label={t("max_content_width.centerContent")}
{t("max_content_width.apply_changes_description")} <Button text={t("max_content_width.reload_button")} size="micro" onClick={reloadFrontendApp} /> currentValue={centerContent}
</p> onChange={setCenterContent} />
</OptionsSection> </OptionsSection>
) )
} }

View File

@ -97,7 +97,7 @@ function TokenList({ tokens }: { tokens: EtapiToken[] }) {
return ( return (
tokens.length ? ( tokens.length ? (
<div style={{ overflow: "auto", height: "500px"}}> <div style={{ overflow: "auto"}}>
<table className="table table-stripped"> <table className="table table-stripped">
<thead> <thead>
<tr> <tr>

View File

@ -331,6 +331,8 @@
"calendar-title": "Calendar", "calendar-title": "Calendar",
"recent-changes-title": "Recent Changes", "recent-changes-title": "Recent Changes",
"bookmarks-title": "Bookmarks", "bookmarks-title": "Bookmarks",
"command-palette": "Open Command Palette",
"zen-mode": "Zen Mode",
"open-today-journal-note-title": "Open Today's Journal Note", "open-today-journal-note-title": "Open Today's Journal Note",
"quick-search-title": "Quick Search", "quick-search-title": "Quick Search",
"protected-session-title": "Protected Session", "protected-session-title": "Protected Session",

View File

@ -28,12 +28,6 @@
<%- include("./partials/windowGlobal.ejs", locals) %> <%- include("./partials/windowGlobal.ejs", locals) %>
<style>
.note-split {
max-width: <%= maxContentWidth %>px;
}
</style>
<!-- Required for match the PWA's top bar color with the theme --> <!-- Required for match the PWA's top bar color with the theme -->
<!-- This works even when the user directly changes --root-background in CSS --> <!-- This works even when the user directly changes --root-background in CSS -->
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div> <div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>

View File

@ -68,6 +68,7 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
"smoothScrollEnabled", "smoothScrollEnabled",
"backdropEffectsEnabled", "backdropEffectsEnabled",
"maxContentWidth", "maxContentWidth",
"centerContent",
"compressImages", "compressImages",
"downloadImagesAutomatically", "downloadImagesAutomatically",
"minTocHeadings", "minTocHeadings",

View File

@ -57,7 +57,6 @@ function index(req: Request, res: Response) {
isDev, isDev,
isMainWindow: view === "mobile" ? true : !req.query.extraWindow, isMainWindow: view === "mobile" ? true : !req.query.extraWindow,
isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(),
maxContentWidth: Math.max(640, parseInt(options.maxContentWidth)),
triliumVersion: packageJson.version, triliumVersion: packageJson.version,
assetPath, assetPath,
appPath, appPath,

View File

@ -44,13 +44,42 @@ export default function buildLaunchBarConfig() {
}; };
const desktopAvailableLaunchers: HiddenSubtreeItem[] = [ const desktopAvailableLaunchers: HiddenSubtreeItem[] = [
{ id: "_lbBackInHistory", ...sharedLaunchers.backInHistory }, {
{ id: "_lbForwardInHistory", ...sharedLaunchers.forwardInHistory }, id: "_lbBackInHistory",
{ id: "_lbBackendLog", title: t("hidden-subtree.backend-log-title"), type: "launcher", targetNoteId: "_backendLog", icon: "bx bx-terminal" }, ...sharedLaunchers.backInHistory
},
{
id: "_lbForwardInHistory",
...sharedLaunchers.forwardInHistory
},
{
id: "_commandPalette",
title: t("hidden-subtree.command-palette"),
type: "launcher",
command: "commandPalette",
icon: "bx bx-chevron-right-square"
},
{
id: "_lbBackendLog",
title: t("hidden-subtree.backend-log-title"),
type: "launcher",
targetNoteId: "_backendLog",
icon: "bx bx-detail"
},
{
id: "_zenMode",
title: t("hidden-subtree.zen-mode"),
type: "launcher",
command: "toggleZenMode",
icon: "bx bxs-yin-yang"
}
]; ];
const desktopVisibleLaunchers: HiddenSubtreeItem[] = [ const desktopVisibleLaunchers: HiddenSubtreeItem[] = [
{ id: "_lbNewNote", ...sharedLaunchers.newNote }, {
id: "_lbNewNote",
...sharedLaunchers.newNote
},
{ {
id: "_lbSearch", id: "_lbSearch",
title: t("hidden-subtree.search-notes-title"), title: t("hidden-subtree.search-notes-title"),
@ -67,7 +96,12 @@ export default function buildLaunchBarConfig() {
icon: "bx bx-send", icon: "bx bx-send",
attributes: [{ type: "label", name: "desktopOnly" }] attributes: [{ type: "label", name: "desktopOnly" }]
}, },
{ id: "_lbNoteMap", title: t("hidden-subtree.note-map-title"), type: "launcher", targetNoteId: "_globalNoteMap", icon: "bx bxs-network-chart" }, { id: "_lbNoteMap",
title: t("hidden-subtree.note-map-title"),
type: "launcher",
targetNoteId: "_globalNoteMap",
icon: "bx bxs-network-chart"
},
{ {
id: "_lbLlmChat", id: "_lbLlmChat",
title: t("hidden-subtree.llm-chat-title"), title: t("hidden-subtree.llm-chat-title"),
@ -78,12 +112,41 @@ export default function buildLaunchBarConfig() {
{ type: "label", name: "desktopOnly" } { type: "label", name: "desktopOnly" }
] ]
}, },
{ id: "_lbCalendar", ...sharedLaunchers.calendar }, {
{ id: "_lbRecentChanges", ...sharedLaunchers.recentChanges }, id: "_lbCalendar",
{ id: "_lbSpacer1", title: t("hidden-subtree.spacer-title"), type: "launcher", builtinWidget: "spacer", baseSize: "50", growthFactor: "0" }, ...sharedLaunchers.calendar
{ id: "_lbBookmarks", title: t("hidden-subtree.bookmarks-title"), type: "launcher", builtinWidget: "bookmarks", icon: "bx bx-bookmark" }, },
{ id: "_lbToday", ...sharedLaunchers.openToday }, {
{ id: "_lbSpacer2", title: t("hidden-subtree.spacer-title"), type: "launcher", builtinWidget: "spacer", baseSize: "0", growthFactor: "1" }, id: "_lbRecentChanges",
...sharedLaunchers.recentChanges
},
{
id: "_lbSpacer1",
title: t("hidden-subtree.spacer-title"),
type: "launcher",
builtinWidget: "spacer",
baseSize: "50",
growthFactor: "0"
},
{
id: "_lbBookmarks",
title: t("hidden-subtree.bookmarks-title"),
type: "launcher",
builtinWidget: "bookmarks",
icon: "bx bx-bookmark"
},
{
id: "_lbToday",
...sharedLaunchers.openToday
},
{
id: "_lbSpacer2",
title: t("hidden-subtree.spacer-title"),
type: "launcher",
builtinWidget: "spacer",
baseSize: "0",
growthFactor: "1"
},
{ {
id: "_lbQuickSearch", id: "_lbQuickSearch",
title: t("hidden-subtree.quick-search-title"), title: t("hidden-subtree.quick-search-title"),
@ -92,9 +155,26 @@ export default function buildLaunchBarConfig() {
icon: "bx bx-rectangle", icon: "bx bx-rectangle",
attributes: [{ type: "label", name: "docName", value: "launchbar_quick_search" }] attributes: [{ type: "label", name: "docName", value: "launchbar_quick_search" }]
}, },
{ id: "_lbProtectedSession", title: t("hidden-subtree.protected-session-title"), type: "launcher", builtinWidget: "protectedSession", icon: "bx bx bx-shield-quarter" }, {
{ id: "_lbSyncStatus", title: t("hidden-subtree.sync-status-title"), type: "launcher", builtinWidget: "syncStatus", icon: "bx bx-wifi" }, id: "_lbProtectedSession",
{ id: "_lbSettings", title: t("hidden-subtree.settings-title"), type: "launcher", command: "showOptions", icon: "bx bx-cog" } title: t("hidden-subtree.protected-session-title"),
type: "launcher", builtinWidget: "protectedSession",
icon: "bx bx bx-shield-quarter"
},
{
id: "_lbSyncStatus",
title: t("hidden-subtree.sync-status-title"),
type: "launcher",
builtinWidget: "syncStatus",
icon: "bx bx-wifi"
},
{
id: "_lbSettings",
title: t("hidden-subtree.settings-title"),
type: "launcher",
command: "showOptions",
icon: "bx bx-cog"
}
]; ];
const mobileAvailableLaunchers: HiddenSubtreeItem[] = [ const mobileAvailableLaunchers: HiddenSubtreeItem[] = [
@ -103,11 +183,29 @@ export default function buildLaunchBarConfig() {
]; ];
const mobileVisibleLaunchers: HiddenSubtreeItem[] = [ const mobileVisibleLaunchers: HiddenSubtreeItem[] = [
{ id: "_lbMobileBackInHistory", ...sharedLaunchers.backInHistory }, {
{ id: "_lbMobileForwardInHistory", ...sharedLaunchers.forwardInHistory }, id: "_lbMobileBackInHistory",
{ id: "_lbMobileJumpTo", title: t("hidden-subtree.jump-to-note-title"), type: "launcher", command: "jumpToNote", icon: "bx bx-plus-circle" }, ...sharedLaunchers.backInHistory
{ id: "_lbMobileCalendar", ...sharedLaunchers.calendar }, },
{ id: "_lbMobileRecentChanges", ...sharedLaunchers.recentChanges } {
id: "_lbMobileForwardInHistory",
...sharedLaunchers.forwardInHistory
},
{
id: "_lbMobileJumpTo",
title: t("hidden-subtree.jump-to-note-title"),
type: "launcher",
command: "jumpToNote",
icon: "bx bx-plus-circle"
},
{
id: "_lbMobileCalendar",
...sharedLaunchers.calendar
},
{
id: "_lbMobileRecentChanges",
...sharedLaunchers.recentChanges
}
]; ];
return { return {
@ -116,4 +214,4 @@ export default function buildLaunchBarConfig() {
mobileAvailableLaunchers, mobileAvailableLaunchers,
mobileVisibleLaunchers mobileVisibleLaunchers
}; };
} }

View File

@ -111,12 +111,13 @@ const defaultOptions: DefaultOption[] = [
{ name: "debugModeEnabled", value: "false", isSynced: false }, { name: "debugModeEnabled", value: "false", isSynced: false },
{ name: "headingStyle", value: "underline", isSynced: true }, { name: "headingStyle", value: "underline", isSynced: true },
{ name: "autoCollapseNoteTree", value: "true", isSynced: true }, { name: "autoCollapseNoteTree", value: "true", isSynced: true },
{ name: "autoReadonlySizeText", value: "10000", isSynced: false }, { name: "autoReadonlySizeText", value: "32000", isSynced: false },
{ name: "autoReadonlySizeCode", value: "30000", isSynced: false }, { name: "autoReadonlySizeCode", value: "64000", isSynced: false },
{ name: "dailyBackupEnabled", value: "true", isSynced: false }, { name: "dailyBackupEnabled", value: "true", isSynced: false },
{ name: "weeklyBackupEnabled", value: "true", isSynced: false }, { name: "weeklyBackupEnabled", value: "true", isSynced: false },
{ name: "monthlyBackupEnabled", value: "true", isSynced: false }, { name: "monthlyBackupEnabled", value: "true", isSynced: false },
{ name: "maxContentWidth", value: "1200", isSynced: false }, { name: "maxContentWidth", value: "1200", isSynced: false },
{ name: "centerContent", value: "false", isSynced: false },
{ name: "compressImages", value: "true", isSynced: true }, { name: "compressImages", value: "true", isSynced: true },
{ name: "downloadImagesAutomatically", value: "true", isSynced: true }, { name: "downloadImagesAutomatically", value: "true", isSynced: true },
{ name: "minTocHeadings", value: "5", isSynced: true }, { name: "minTocHeadings", value: "5", isSynced: true },

View File

@ -6,7 +6,9 @@ enum Command {
createNoteIntoInbox, createNoteIntoInbox,
showRecentChanges, showRecentChanges,
showOptions, showOptions,
createAiChat createAiChat,
commandPalette,
toggleZenMode
} }
export interface HiddenSubtreeAttribute { export interface HiddenSubtreeAttribute {
@ -41,7 +43,9 @@ export interface HiddenSubtreeItem {
| "protectedSession" | "protectedSession"
| "calendar" | "calendar"
| "quickSearch" | "quickSearch"
| "aiChatLauncher"; | "aiChatLauncher"
| "commandPalette"
| "toggleZenMode";
command?: keyof typeof Command; command?: keyof typeof Command;
/** /**
* If set to true, then branches will be enforced to be in the correct place. * If set to true, then branches will be enforced to be in the correct place.

View File

@ -82,6 +82,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
autoReadonlySizeText: number; autoReadonlySizeText: number;
autoReadonlySizeCode: number; autoReadonlySizeCode: number;
maxContentWidth: number; maxContentWidth: number;
centerContent: boolean;
minTocHeadings: number; minTocHeadings: number;
eraseUnusedAttachmentsAfterSeconds: number; eraseUnusedAttachmentsAfterSeconds: number;
eraseUnusedAttachmentsAfterTimeScale: number; eraseUnusedAttachmentsAfterTimeScale: number;