mirror of
https://github.com/zadam/trilium.git
synced 2025-12-12 18:34:24 +01:00
New layout improvements (#8012)
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 / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
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 / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
This commit is contained in:
commit
84bde62e05
@ -140,7 +140,7 @@ export default class DesktopLayout {
|
|||||||
.class("breadcrumb-row")
|
.class("breadcrumb-row")
|
||||||
.cssBlock(".breadcrumb-row > * { margin: 5px; }")
|
.cssBlock(".breadcrumb-row > * { margin: 5px; }")
|
||||||
.child(<Breadcrumb />)
|
.child(<Breadcrumb />)
|
||||||
.child(<BreadcrumbBadges />)
|
.optChild(isNewLayout, <BreadcrumbBadges />)
|
||||||
.child(<SpacerWidget baseSize={0} growthFactor={1} />)
|
.child(<SpacerWidget baseSize={0} growthFactor={1} />)
|
||||||
.child(<MovePaneButton direction="left" />)
|
.child(<MovePaneButton direction="left" />)
|
||||||
.child(<MovePaneButton direction="right" />)
|
.child(<MovePaneButton direction="right" />)
|
||||||
|
|||||||
@ -52,5 +52,5 @@ export function applyModals(rootContainer: RootContainer) {
|
|||||||
.child(<IncorrectCpuArchDialog />)
|
.child(<IncorrectCpuArchDialog />)
|
||||||
.child(<PopupEditorDialog />)
|
.child(<PopupEditorDialog />)
|
||||||
.child(<CallToActionDialog />)
|
.child(<CallToActionDialog />)
|
||||||
.child(<ToastContainer />)
|
.child(<ToastContainer />);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1321,6 +1321,11 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu.dropstart > .dropdown-menu {
|
||||||
|
inset-inline-start: auto;
|
||||||
|
inset-inline-end: calc(100% - 2px);
|
||||||
|
}
|
||||||
|
|
||||||
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||||
inset-inline-start: calc(-100% + 10px);
|
inset-inline-start: calc(-100% + 10px);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,13 +89,13 @@
|
|||||||
* 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
|
||||||
* component of the color represented in the CIELAB color space, will be
|
* component of the color represented in the CIELAB color space, will be
|
||||||
* constrained to a certain percentage defined below.
|
* constrained to a certain percentage defined below.
|
||||||
*
|
*
|
||||||
* Note: the tree background may vary when background effects are enabled, so it is recommended
|
* Note: the tree background may vary when background effects are enabled, so it is recommended
|
||||||
* to maintain a higher contrast margin than on the usual note tree solid background. */
|
* to maintain a higher contrast margin than on the usual note tree solid background. */
|
||||||
|
|
||||||
/* The maximum perceptual lightness for the custom color in the light theme (%): */
|
/* The maximum perceptual lightness for the custom color in the light theme (%): */
|
||||||
--tree-item-light-theme-max-color-lightness: 60;
|
--tree-item-light-theme-max-color-lightness: 60;
|
||||||
|
|
||||||
/* The minimum perceptual lightness for the custom color in the dark theme (%): */
|
/* The minimum perceptual lightness for the custom color in the dark theme (%): */
|
||||||
--tree-item-dark-theme-min-color-lightness: 65;
|
--tree-item-dark-theme-min-color-lightness: 65;
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ body.desktop .dropdown-submenu .dropdown-menu {
|
|||||||
--menu-item-start-padding: 8px;
|
--menu-item-start-padding: 8px;
|
||||||
--menu-item-end-padding: 22px;
|
--menu-item-end-padding: 22px;
|
||||||
--menu-item-vertical-padding: 2px;
|
--menu-item-vertical-padding: 2px;
|
||||||
|
|
||||||
padding-top: var(--menu-item-vertical-padding) !important;
|
padding-top: var(--menu-item-vertical-padding) !important;
|
||||||
padding-bottom: var(--menu-item-vertical-padding) !important;
|
padding-bottom: var(--menu-item-vertical-padding) !important;
|
||||||
padding-inline-start: var(--menu-item-start-padding) !important;
|
padding-inline-start: var(--menu-item-start-padding) !important;
|
||||||
@ -176,6 +176,11 @@ body.desktop .dropdown-submenu .dropdown-menu {
|
|||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu:has(> .dropdown-submenu.dropstart) > .dropdown-item {
|
||||||
|
padding-inline-end: var(--menu-item-start-padding) !important;
|
||||||
|
padding-inline-start: var(--menu-item-end-padding) !important;
|
||||||
|
}
|
||||||
|
|
||||||
:root .dropdown-item:focus-visible {
|
:root .dropdown-item:focus-visible {
|
||||||
outline: 2px solid var(--input-focus-outline-color) !important;
|
outline: 2px solid var(--input-focus-outline-color) !important;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
@ -249,7 +254,7 @@ html body .dropdown-item[disabled] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Menu item arrow */
|
/* Menu item arrow */
|
||||||
.dropdown-menu .dropdown-toggle::after {
|
.dropdown-submenu:not(.dropstart) .dropdown-toggle::after {
|
||||||
content: "\ed3b" !important;
|
content: "\ed3b" !important;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
@ -265,6 +270,22 @@ html body .dropdown-item[disabled] {
|
|||||||
color: var(--menu-item-arrow-color) !important;
|
color: var(--menu-item-arrow-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu.dropstart .dropdown-toggle::before {
|
||||||
|
content: "\ea4d" !important;
|
||||||
|
position: absolute;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
top: 0;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
margin: unset !important;
|
||||||
|
border: unset !important;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-family: boxicons;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: var(--menu-item-arrow-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
body[dir=rtl] .dropdown-menu:not([data-popper-placement="bottom-start"]) .dropdown-toggle::after {
|
body[dir=rtl] .dropdown-menu:not([data-popper-placement="bottom-start"]) .dropdown-toggle::after {
|
||||||
content: "\ea4d" !important;
|
content: "\ea4d" !important;
|
||||||
}
|
}
|
||||||
@ -339,7 +360,7 @@ body.mobile .dropdown-menu {
|
|||||||
font-size: 1em !important;
|
font-size: 1em !important;
|
||||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.dropdown-toggle::after {
|
.dropdown-toggle::after {
|
||||||
top: 0.5em;
|
top: 0.5em;
|
||||||
right: var(--dropdown-menu-padding-horizontal);
|
right: var(--dropdown-menu-padding-horizontal);
|
||||||
@ -356,7 +377,7 @@ body.mobile .dropdown-menu {
|
|||||||
padding: var(--dropdown-menu-padding-vertical) var(--dropdown-menu-padding-horizontal) !important;
|
padding: var(--dropdown-menu-padding-vertical) var(--dropdown-menu-padding-horizontal) !important;
|
||||||
background: var(--card-background-color);
|
background: var(--card-background-color);
|
||||||
border-bottom: 1px solid var(--menu-item-delimiter-color) !important;
|
border-bottom: 1px solid var(--menu-item-delimiter-color) !important;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item:first-of-type,
|
.dropdown-item:first-of-type,
|
||||||
@ -367,9 +388,9 @@ body.mobile .dropdown-menu {
|
|||||||
border-top-right-radius: 6px;
|
border-top-right-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item:last-of-type,
|
.dropdown-item:last-of-type,
|
||||||
.dropdown-item:has(+ .dropdown-divider),
|
.dropdown-item:has(+ .dropdown-divider),
|
||||||
.dropdown-custom-item:last-of-type,
|
.dropdown-custom-item:last-of-type,
|
||||||
.dropdown-custom-item:has(+ .dropdown-divider) {
|
.dropdown-custom-item:has(+ .dropdown-divider) {
|
||||||
border-bottom-left-radius: 6px;
|
border-bottom-left-radius: 6px;
|
||||||
border-bottom-right-radius: 6px;
|
border-bottom-right-radius: 6px;
|
||||||
@ -392,10 +413,10 @@ body.mobile .dropdown-menu {
|
|||||||
--menu-background-color: --menu-submenu-mobile-background-color;
|
--menu-background-color: --menu-submenu-mobile-background-color;
|
||||||
--bs-dropdown-divider-margin-y: 0.25rem;
|
--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;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
|
|
||||||
&.show {
|
&.show {
|
||||||
max-height: 1000px;
|
max-height: 1000px;
|
||||||
padding: 0.5rem 0.75rem !important;
|
padding: 0.5rem 0.75rem !important;
|
||||||
@ -405,7 +426,7 @@ body.mobile .dropdown-menu {
|
|||||||
&.submenu-open {
|
&.submenu-open {
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
padding-bottom: var(--dropdown-menu-padding-vertical);
|
padding-bottom: var(--dropdown-menu-padding-vertical);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -743,4 +764,4 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
|
|||||||
.note-detail-empty .aa-suggestions div.aa-cursor {
|
.note-detail-empty .aa-suggestions div.aa-cursor {
|
||||||
background: var(--hover-item-background-color);
|
background: var(--hover-item-background-color);
|
||||||
color: var(--hover-item-text-color);
|
color: var(--hover-item-text-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -689,6 +689,7 @@
|
|||||||
"export_note": "Export note",
|
"export_note": "Export note",
|
||||||
"delete_note": "Delete note",
|
"delete_note": "Delete note",
|
||||||
"print_note": "Print note",
|
"print_note": "Print note",
|
||||||
|
"view_revisions": "Note revisions...",
|
||||||
"save_revision": "Save revision",
|
"save_revision": "Save revision",
|
||||||
"convert_into_attachment_failed": "Converting note '{{title}}' failed.",
|
"convert_into_attachment_failed": "Converting note '{{title}}' failed.",
|
||||||
"convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.",
|
"convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.",
|
||||||
@ -1750,8 +1751,8 @@
|
|||||||
},
|
},
|
||||||
"note_title": {
|
"note_title": {
|
||||||
"placeholder": "type note's title here...",
|
"placeholder": "type note's title here...",
|
||||||
"created_on": "Created on {{date}}",
|
"created_on": "Created on <Value />",
|
||||||
"last_modified": "Last modified on {{date}}"
|
"last_modified": "Last modified on <Value />"
|
||||||
},
|
},
|
||||||
"search_result": {
|
"search_result": {
|
||||||
"no_notes_found": "No notes have been found for given search parameters.",
|
"no_notes_found": "No notes have been found for given search parameters.",
|
||||||
@ -2132,8 +2133,18 @@
|
|||||||
},
|
},
|
||||||
"breadcrumb_badges": {
|
"breadcrumb_badges": {
|
||||||
"read_only_explicit": "Read-only",
|
"read_only_explicit": "Read-only",
|
||||||
|
"read_only_explicit_description": "This note has been manually set to read-only.\nClick to edit it temporarily.",
|
||||||
"read_only_auto": "Auto read-only",
|
"read_only_auto": "Auto read-only",
|
||||||
|
"read_only_auto_description": "This note was set automatically to read-only mode for performance reasons. This automatic limit is adjustable from settings.\n\nClick to edit it temporarily.",
|
||||||
|
"read_only_temporarily_disabled": "Temporarily editable",
|
||||||
|
"read_only_temporarily_disabled_description": "This note is currently editable, but it is normally read-only. The note will go back to being read-only as soon as you navigate to another note.\n\nClick to re-enable read-only mode.",
|
||||||
"shared_publicly": "Shared publicly",
|
"shared_publicly": "Shared publicly",
|
||||||
"shared_locally": "Shared locally"
|
"shared_publicly_description": "This note has been published online at {{- link}}, and is publicly accessible.\n\nClick to navigate to the shared note or right click for more options.",
|
||||||
|
"shared_locally": "Shared locally",
|
||||||
|
"shared_locally_description": "This note is shared on the local network only at {{- link}}.\n\nClick to navigate to the shared note or right click for more options.",
|
||||||
|
"backlinks_one": "{{count}} backlink",
|
||||||
|
"backlinks_other": "{{count}} backlinks",
|
||||||
|
"backlinks_description_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.",
|
||||||
|
"backlinks_description_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,49 @@
|
|||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
container-type: inline-size;
|
||||||
|
|
||||||
|
@container (max-width: 700px) {
|
||||||
|
.breadcrumb-badges {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
>* {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 2px;
|
||||||
|
|
||||||
|
>* {
|
||||||
|
text-overflow: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (max-width: 500px) {
|
||||||
|
.breadcrumb {
|
||||||
|
.btn.icon-action {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-action {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.experimental-feature-new-layout .breadcrumb-row {
|
body.experimental-feature-new-layout .breadcrumb-row {
|
||||||
@ -53,11 +96,23 @@ body.experimental-feature-new-layout .breadcrumb-row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item span,
|
.dropdown-item span,
|
||||||
.dropdown-item strong {
|
.dropdown-item strong,
|
||||||
|
.breadcrumb-last-item {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumb-last-item {
|
||||||
|
text-decoration: none;
|
||||||
|
color: unset;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 0 10px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import "./Breadcrumb.css";
|
import "./Breadcrumb.css";
|
||||||
|
|
||||||
import { useMemo } from "preact/hooks";
|
import { useMemo, useState } from "preact/hooks";
|
||||||
import { Fragment } from "preact/jsx-runtime";
|
import { Fragment } from "preact/jsx-runtime";
|
||||||
|
|
||||||
import NoteContext from "../components/note_context";
|
import NoteContext from "../components/note_context";
|
||||||
@ -12,6 +12,8 @@ import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "./
|
|||||||
import Icon from "./react/Icon";
|
import Icon from "./react/Icon";
|
||||||
import NoteLink from "./react/NoteLink";
|
import NoteLink from "./react/NoteLink";
|
||||||
import link_context_menu from "../menus/link_context_menu";
|
import link_context_menu from "../menus/link_context_menu";
|
||||||
|
import { TitleEditor } from "./collections/board";
|
||||||
|
import server from "../services/server";
|
||||||
|
|
||||||
const COLLAPSE_THRESHOLD = 5;
|
const COLLAPSE_THRESHOLD = 5;
|
||||||
const INITIAL_ITEMS = 2;
|
const INITIAL_ITEMS = 2;
|
||||||
@ -27,10 +29,7 @@ export default function Breadcrumb() {
|
|||||||
<>
|
<>
|
||||||
{notePath.slice(0, INITIAL_ITEMS).map((item, index) => (
|
{notePath.slice(0, INITIAL_ITEMS).map((item, index) => (
|
||||||
<Fragment key={item}>
|
<Fragment key={item}>
|
||||||
{index === 0
|
<BreadcrumbItem index={index} notePath={item} notePathLength={notePath.length} noteContext={noteContext} />
|
||||||
? <BreadcrumbRoot noteContext={noteContext} />
|
|
||||||
: <BreadcrumbItem notePath={item} />
|
|
||||||
}
|
|
||||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />
|
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
@ -38,7 +37,7 @@ export default function Breadcrumb() {
|
|||||||
{notePath.slice(-FINAL_ITEMS).map((item, index) => (
|
{notePath.slice(-FINAL_ITEMS).map((item, index) => (
|
||||||
<Fragment key={item}>
|
<Fragment key={item}>
|
||||||
<BreadcrumbSeparator notePath={notePath[notePath.length - FINAL_ITEMS - (1 - index)]} activeNotePath={item} noteContext={noteContext} />
|
<BreadcrumbSeparator notePath={notePath[notePath.length - FINAL_ITEMS - (1 - index)]} activeNotePath={item} noteContext={noteContext} />
|
||||||
<BreadcrumbItem notePath={item} />
|
<BreadcrumbItem index={notePath.length - FINAL_ITEMS + index} notePath={item} notePathLength={notePath.length} noteContext={noteContext} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@ -47,7 +46,7 @@ export default function Breadcrumb() {
|
|||||||
<Fragment key={item}>
|
<Fragment key={item}>
|
||||||
{index === 0
|
{index === 0
|
||||||
? <BreadcrumbRoot noteContext={noteContext} />
|
? <BreadcrumbRoot noteContext={noteContext} />
|
||||||
: <BreadcrumbItem notePath={item} />
|
: <BreadcrumbItem index={index} notePath={item} notePathLength={notePath.length} noteContext={noteContext} />
|
||||||
}
|
}
|
||||||
{(index < notePath.length - 1 || note?.hasChildren()) &&
|
{(index < notePath.length - 1 || note?.hasChildren()) &&
|
||||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />}
|
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />}
|
||||||
@ -76,15 +75,56 @@ function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbItem({ notePath }: { notePath: string }) {
|
function BreadcrumbLink({ notePath }: { notePath: string }) {
|
||||||
return (
|
return (
|
||||||
<NoteLink
|
<NoteLink
|
||||||
notePath={notePath}
|
notePath={notePath}
|
||||||
noPreview
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BreadcrumbLastItem({ notePath }: { notePath: string }) {
|
||||||
|
const noteId = notePath.split("/").at(-1);
|
||||||
|
const [ note ] = useState(() => froca.getNoteFromCache(noteId!));
|
||||||
|
const [ isEditing, setIsEditing ] = useState(false);
|
||||||
|
const title = useNoteProperty(note, "title");
|
||||||
|
|
||||||
|
if (!note) return null;
|
||||||
|
|
||||||
|
if (!isEditing) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="breadcrumb-last-item tn-link"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsEditing(true);
|
||||||
|
}}
|
||||||
|
>{title}</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TitleEditor
|
||||||
|
currentValue={title}
|
||||||
|
save={(newTitle) => { return server.put(`notes/${noteId}/title`, { title: newTitle.trim() }); }}
|
||||||
|
dismiss={() => setIsEditing(false)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbItem({ index, notePath, noteContext, notePathLength }: { index: number, notePathLength: number, notePath: string, noteContext: NoteContext | undefined }) {
|
||||||
|
if (index === 0) {
|
||||||
|
return <BreadcrumbRoot noteContext={noteContext} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === notePathLength - 1) {
|
||||||
|
return <BreadcrumbLastItem notePath={notePath} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BreadcrumbLink notePath={notePath} />;
|
||||||
|
}
|
||||||
|
|
||||||
function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) {
|
function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) {
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
|||||||
@ -1,23 +1,71 @@
|
|||||||
.component.breadcrumb-badges {
|
.component.breadcrumb-badges {
|
||||||
|
contain: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
contain: none;
|
min-width: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
--badge-radius: 12px;
|
||||||
|
|
||||||
.breadcrumb-badge {
|
.breadcrumb-badge {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 12px;
|
border-radius: var(--badge-radius);
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
background-color: var(--badge-background-color);
|
background-color: var(--color, transparent);
|
||||||
color: var(--badge-text-color);
|
color: white;
|
||||||
|
min-width: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
&.clickable {
|
&.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--badge-background-hover-color);
|
background-color: color-mix(in srgb, var(--color, --badge-background-color) 80%, black);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.temporarily-editable-badge { --color: #4fa52b; }
|
||||||
|
&.read-only-badge { --color: #e33f3b; }
|
||||||
|
&.share-badge { --color: #3b82f6; }
|
||||||
|
&.backlinks-badge { color: var(--badge-text-color); }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-radius: var(--badge-radius);
|
||||||
|
|
||||||
|
&.dropdown-backlinks-badge .dropdown-menu {
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-badge {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,22 @@
|
|||||||
import "./BreadcrumbBadges.css";
|
import "./BreadcrumbBadges.css";
|
||||||
|
|
||||||
import { ComponentChildren } from "preact";
|
import clsx from "clsx";
|
||||||
import { useIsNoteReadOnly, useNoteContext } from "./react/hooks";
|
import { ComponentChildren, MouseEventHandler } from "preact";
|
||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
|
||||||
|
import { t } from "../services/i18n";
|
||||||
|
import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions";
|
||||||
|
import Dropdown, { DropdownProps } from "./react/Dropdown";
|
||||||
|
import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks";
|
||||||
import Icon from "./react/Icon";
|
import Icon from "./react/Icon";
|
||||||
import { useShareInfo } from "./shared_info";
|
import { useShareInfo } from "./shared_info";
|
||||||
import clsx from "clsx";
|
|
||||||
import { t } from "../services/i18n";
|
|
||||||
|
|
||||||
export default function BreadcrumbBadges() {
|
export default function BreadcrumbBadges() {
|
||||||
return (
|
return (
|
||||||
<div className="breadcrumb-badges">
|
<div className="breadcrumb-badges">
|
||||||
<ReadOnlyBadge />
|
<ReadOnlyBadge />
|
||||||
<ShareBadge />
|
<ShareBadge />
|
||||||
|
<BacklinksBadge />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -20,37 +25,113 @@ function ReadOnlyBadge() {
|
|||||||
const { note, noteContext } = useNoteContext();
|
const { note, noteContext } = useNoteContext();
|
||||||
const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext);
|
const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext);
|
||||||
const isExplicitReadOnly = note?.isLabelTruthy("readOnly");
|
const isExplicitReadOnly = note?.isLabelTruthy("readOnly");
|
||||||
|
const isTemporarilyEditable = noteContext?.viewScope?.readOnlyTemporarilyDisabled;
|
||||||
|
|
||||||
return (isReadOnly &&
|
if (isTemporarilyEditable) {
|
||||||
<Badge
|
return <Badge
|
||||||
icon="bx bx-lock"
|
icon="bx bx-lock-open-alt"
|
||||||
onClick={() => enableEditing()}>
|
text={t("breadcrumb_badges.read_only_temporarily_disabled")}
|
||||||
{isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit") : t("breadcrumb_badges.read_only_auto")}
|
tooltip={t("breadcrumb_badges.read_only_temporarily_disabled_description")}
|
||||||
</Badge>
|
className="temporarily-editable-badge"
|
||||||
);
|
onClick={() => enableEditing(false)}
|
||||||
|
/>;
|
||||||
|
} else if (isReadOnly) {
|
||||||
|
return <Badge
|
||||||
|
icon="bx bx-lock-alt"
|
||||||
|
text={isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit") : t("breadcrumb_badges.read_only_auto")}
|
||||||
|
tooltip={isExplicitReadOnly ? t("breadcrumb_badges.read_only_explicit_description") : t("breadcrumb_badges.read_only_auto_description")}
|
||||||
|
className="read-only-badge"
|
||||||
|
onClick={() => enableEditing()}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ShareBadge() {
|
function ShareBadge() {
|
||||||
const { note } = useNoteContext();
|
const { note } = useNoteContext();
|
||||||
const { isSharedExternally, link } = useShareInfo(note);
|
const { isSharedExternally, link, linkHref } = useShareInfo(note);
|
||||||
|
|
||||||
return (link &&
|
return (link &&
|
||||||
<Badge
|
<Badge
|
||||||
icon={isSharedExternally ? "bx bx-world" : "bx bx-link"}
|
icon={isSharedExternally ? "bx bx-world" : "bx bx-link"}
|
||||||
>
|
text={isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")}
|
||||||
{isSharedExternally ? t("breadcrumb_badges.shared_publicly") : t("breadcrumb_badges.shared_locally")}
|
tooltip={isSharedExternally ?
|
||||||
</Badge>
|
t("breadcrumb_badges.shared_publicly_description", { link }) :
|
||||||
|
t("breadcrumb_badges.shared_locally_description", { link })
|
||||||
|
}
|
||||||
|
className="share-badge"
|
||||||
|
href={linkHref}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Badge({ icon, children, onClick }: { icon: string, children: ComponentChildren, onClick?: () => void }) {
|
function BacklinksBadge() {
|
||||||
|
const { note, viewScope } = useNoteContext();
|
||||||
|
const count = useBacklinkCount(note, viewScope?.viewMode === "default");
|
||||||
|
return (note && count > 0 &&
|
||||||
|
<BadgeWithDropdown
|
||||||
|
className="backlinks-badge backlinks-widget"
|
||||||
|
icon="bx bx-revision"
|
||||||
|
text={t("breadcrumb_badges.backlinks", { count })}
|
||||||
|
tooltip={t("breadcrumb_badges.backlinks_description", { count })}
|
||||||
|
dropdownOptions={{
|
||||||
|
dropdownContainerClassName: "backlinks-items"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BacklinksList note={note} />
|
||||||
|
</BadgeWithDropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BadgeProps {
|
||||||
|
text: string;
|
||||||
|
icon?: string;
|
||||||
|
className: string;
|
||||||
|
tooltip?: string;
|
||||||
|
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||||
|
href?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Badge({ icon, className, text, tooltip, onClick, href }: BadgeProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
useStaticTooltip(containerRef, {
|
||||||
|
placement: "bottom",
|
||||||
|
fallbackPlacements: [ "bottom" ],
|
||||||
|
animation: false,
|
||||||
|
html: true,
|
||||||
|
title: tooltip
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = <>
|
||||||
|
{icon && <><Icon icon={icon} /> </>}
|
||||||
|
<span class="text">{text}</span>
|
||||||
|
</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx("breadcrumb-badge", { "clickable": !!onClick })}
|
ref={containerRef}
|
||||||
|
className={clsx("breadcrumb-badge", className, { "clickable": !!onClick })}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Icon icon={icon} />
|
{href ? <a href={href}>{content}</a> : <span>{content}</span>}
|
||||||
{children}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BadgeWithDropdown({ children, tooltip, className, dropdownOptions, ...props }: BadgeProps & {
|
||||||
|
children: ComponentChildren,
|
||||||
|
dropdownOptions?: Partial<DropdownProps>
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
className={`dropdown-${className}`}
|
||||||
|
text={<Badge className={className} {...props} />}
|
||||||
|
noDropdownListStyle
|
||||||
|
noSelectButtonStyle
|
||||||
|
hideToggleArrow
|
||||||
|
title={tooltip}
|
||||||
|
titlePosition="bottom"
|
||||||
|
dropdownOptions={{ popperConfig: { placement: "bottom", strategy: "fixed" } }}
|
||||||
|
{...dropdownOptions}
|
||||||
|
>{children}</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ 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 { useIsNoteReadOnly, 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 { useCallback, useEffect, useLayoutEffect, 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";
|
||||||
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
|
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
|
||||||
@ -20,6 +20,7 @@ import RawHtml from "./react/RawHtml";
|
|||||||
import { ViewTypeOptions } from "./collections/interface";
|
import { ViewTypeOptions } from "./collections/interface";
|
||||||
import attributes from "../services/attributes";
|
import attributes from "../services/attributes";
|
||||||
import LoadResults from "../services/load_results";
|
import LoadResults from "../services/load_results";
|
||||||
|
import { isExperimentalFeatureEnabled } from "../services/experimental_features";
|
||||||
|
|
||||||
export interface FloatingButtonContext {
|
export interface FloatingButtonContext {
|
||||||
parentComponent: Component;
|
parentComponent: Component;
|
||||||
@ -76,6 +77,8 @@ export const POPUP_HIDDEN_FLOATING_BUTTONS: FloatingButtonsList = [
|
|||||||
ToggleReadOnlyButton
|
ToggleReadOnlyButton
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||||
|
|
||||||
function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||||
const isEnabled = (note.noteId === "_backendLog" || note.type === "render") && isDefaultViewMode;
|
const isEnabled = (note.noteId === "_backendLog" || note.type === "render") && isDefaultViewMode;
|
||||||
return isEnabled && <FloatingButton
|
return isEnabled && <FloatingButton
|
||||||
@ -308,22 +311,9 @@ function InAppHelpButton({ note }: FloatingButtonContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
|
function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
|
||||||
let [ backlinkCount, setBacklinkCount ] = useState(0);
|
const [ popupOpen, setPopupOpen ] = useState(false);
|
||||||
let [ popupOpen, setPopupOpen ] = useState(false);
|
|
||||||
const backlinksContainerRef = useRef<HTMLDivElement>(null);
|
const backlinksContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const backlinkCount = useBacklinkCount(note, isDefaultViewMode);
|
||||||
function refresh() {
|
|
||||||
if (!isDefaultViewMode) return;
|
|
||||||
|
|
||||||
server.get<BacklinkCountResponse>(`note-map/${note.noteId}/backlink-count`).then(resp => {
|
|
||||||
setBacklinkCount(resp.count);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => refresh(), [ note ]);
|
|
||||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
|
||||||
if (needsRefresh(note, loadResults)) refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine the max height of the container.
|
// Determine the max height of the container.
|
||||||
const { windowHeight } = useWindowSize();
|
const { windowHeight } = useWindowSize();
|
||||||
@ -336,7 +326,7 @@ function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
|
|||||||
}
|
}
|
||||||
}, [ popupOpen, windowHeight ]);
|
}, [ popupOpen, windowHeight ]);
|
||||||
|
|
||||||
const isEnabled = isDefaultViewMode && backlinkCount > 0;
|
const isEnabled = !isNewLayout && isDefaultViewMode && backlinkCount > 0;
|
||||||
return (isEnabled &&
|
return (isEnabled &&
|
||||||
<div className="backlinks-widget has-overflow">
|
<div className="backlinks-widget has-overflow">
|
||||||
<div
|
<div
|
||||||
@ -355,15 +345,34 @@ function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BacklinksList({ note }: { note: FNote }) {
|
export function useBacklinkCount(note: FNote | null | undefined, isDefaultViewMode: boolean) {
|
||||||
|
const [ backlinkCount, setBacklinkCount ] = useState(0);
|
||||||
|
|
||||||
|
const refresh = useCallback(() => {
|
||||||
|
if (!note || !isDefaultViewMode) return;
|
||||||
|
|
||||||
|
server.get<BacklinkCountResponse>(`note-map/${note.noteId}/backlink-count`).then(resp => {
|
||||||
|
setBacklinkCount(resp.count);
|
||||||
|
});
|
||||||
|
}, [ isDefaultViewMode, note ]);
|
||||||
|
|
||||||
|
useEffect(() => refresh(), [ refresh ]);
|
||||||
|
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||||
|
if (note && needsRefresh(note, loadResults)) refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
return backlinkCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BacklinksList({ note }: { note: FNote }) {
|
||||||
const [ backlinks, setBacklinks ] = useState<BacklinksResponse>([]);
|
const [ backlinks, setBacklinks ] = useState<BacklinksResponse>([]);
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
server.get<BacklinksResponse>(`note-map/${note.noteId}/backlinks`).then(async (backlinks) => {
|
server.get<BacklinksResponse>(`note-map/${note.noteId}/backlinks`).then(async (backlinks) => {
|
||||||
// prefetch all
|
// prefetch all
|
||||||
const noteIds = backlinks
|
const noteIds = backlinks
|
||||||
.filter(bl => "noteId" in bl)
|
.filter(bl => "noteId" in bl)
|
||||||
.map((bl) => bl.noteId);
|
.map((bl) => bl.noteId);
|
||||||
await froca.getNotes(noteIds);
|
await froca.getNotes(noteIds);
|
||||||
setBacklinks(backlinks);
|
setBacklinks(backlinks);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,23 +1,60 @@
|
|||||||
import { t } from "../services/i18n";
|
import { type ComponentChild } from "preact";
|
||||||
|
|
||||||
import { formatDateTime } from "../utils/formatters";
|
import { formatDateTime } from "../utils/formatters";
|
||||||
import { useNoteContext } from "./react/hooks";
|
import { useNoteContext, useStaticTooltip } from "./react/hooks";
|
||||||
import { joinElements } from "./react/react_utils";
|
import { joinElements } from "./react/react_utils";
|
||||||
import { useNoteMetadata } from "./ribbon/NoteInfoTab";
|
import { useNoteMetadata } from "./ribbon/NoteInfoTab";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
import { useRef } from "preact/hooks";
|
||||||
|
|
||||||
export default function NoteTitleDetails() {
|
export default function NoteTitleDetails() {
|
||||||
const { note } = useNoteContext();
|
const { note, noteContext } = useNoteContext();
|
||||||
const { metadata } = useNoteMetadata(note);
|
const { metadata } = useNoteMetadata(note);
|
||||||
|
const isHiddenNote = note?.noteId.startsWith("_");
|
||||||
|
const isDefaultView = noteContext?.viewScope?.viewMode === "default";
|
||||||
|
|
||||||
|
const items: ComponentChild[] = [
|
||||||
|
(isDefaultView && !isHiddenNote && metadata?.dateCreated &&
|
||||||
|
<TextWithValue
|
||||||
|
i18nKey="note_title.created_on"
|
||||||
|
value={formatDateTime(metadata.dateCreated, "medium", "none")}
|
||||||
|
valueTooltip={formatDateTime(metadata.dateCreated, "full", "long")}
|
||||||
|
/>),
|
||||||
|
(isDefaultView && !isHiddenNote && metadata?.dateModified &&
|
||||||
|
<TextWithValue
|
||||||
|
i18nKey="note_title.last_modified"
|
||||||
|
value={formatDateTime(metadata.dateModified, "medium", "none")}
|
||||||
|
valueTooltip={formatDateTime(metadata.dateModified, "full", "long")}
|
||||||
|
/>)
|
||||||
|
].filter(item => !!item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="title-details">
|
<div className="title-details">
|
||||||
{joinElements([
|
{joinElements(items, " • ")}
|
||||||
metadata?.dateCreated && <li>
|
|
||||||
{t("note_title.created_on", { date: formatDateTime(metadata.dateCreated, "medium", "none")} )}
|
|
||||||
</li>,
|
|
||||||
metadata?.dateModified && <li>
|
|
||||||
{t("note_title.last_modified", { date: formatDateTime(metadata.dateModified, "medium", "none")} )}
|
|
||||||
</li>
|
|
||||||
], " • ")}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TextWithValue({ i18nKey, value, valueTooltip }: {
|
||||||
|
i18nKey: string;
|
||||||
|
value: string;
|
||||||
|
valueTooltip: string;
|
||||||
|
}) {
|
||||||
|
const listItemRef = useRef<HTMLLIElement>(null);
|
||||||
|
useStaticTooltip(listItemRef, {
|
||||||
|
selector: "span.value",
|
||||||
|
title: valueTooltip,
|
||||||
|
popperConfig: { placement: "bottom" }
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li ref={listItemRef}>
|
||||||
|
<Trans
|
||||||
|
i18nKey={i18nKey}
|
||||||
|
components={{
|
||||||
|
Value: <span className="value">{value}</span> as React.ReactElement
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import shortcutService from "../../services/shortcuts.js";
|
|||||||
import appContext from "../../components/app_context.js";
|
import appContext from "../../components/app_context.js";
|
||||||
import type { Attribute } from "../../services/attribute_parser.js";
|
import type { Attribute } from "../../services/attribute_parser.js";
|
||||||
import { focusSavedElement, saveFocusedElement } from "../../services/focus.js";
|
import { focusSavedElement, saveFocusedElement } from "../../services/focus.js";
|
||||||
|
import { isExperimentalFeatureEnabled } from "../../services/experimental_features.js";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="attr-detail tn-tool-dialog">
|
<div class="attr-detail tn-tool-dialog">
|
||||||
@ -309,6 +310,8 @@ interface SearchRelatedResponse {
|
|||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||||
|
|
||||||
export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||||
private $title!: JQuery<HTMLElement>;
|
private $title!: JQuery<HTMLElement>;
|
||||||
private $inputName!: JQuery<HTMLElement>;
|
private $inputName!: JQuery<HTMLElement>;
|
||||||
@ -579,6 +582,13 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
|||||||
.css("top", y - offset.top + 70)
|
.css("top", y - offset.top + 70)
|
||||||
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
||||||
|
|
||||||
|
if (isNewLayout) {
|
||||||
|
this.$widget
|
||||||
|
.css("top", "unset")
|
||||||
|
.css("bottom", 70)
|
||||||
|
.css("max-height", "80vh");
|
||||||
|
}
|
||||||
|
|
||||||
if (focus === "name") {
|
if (focus === "name") {
|
||||||
this.$inputName.trigger("focus").trigger("select");
|
this.$inputName.trigger("focus").trigger("select");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -243,7 +243,7 @@ function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMo
|
|||||||
export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: {
|
export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: {
|
||||||
currentValue?: string;
|
currentValue?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
save: (newValue: string) => void;
|
save: (newValue: string) => void | Promise<void>;
|
||||||
dismiss: () => void;
|
dismiss: () => void;
|
||||||
isNewItem?: boolean;
|
isNewItem?: boolean;
|
||||||
mode?: "normal" | "multiline" | "relation";
|
mode?: "normal" | "multiline" | "relation";
|
||||||
|
|||||||
@ -31,6 +31,7 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-block: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal.popup-editor-dialog .modal-header .note-title-widget {
|
.modal.popup-editor-dialog .modal-header .note-title-widget {
|
||||||
|
|||||||
@ -1,26 +1,32 @@
|
|||||||
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
||||||
import Modal from "../react/Modal";
|
|
||||||
import "./PopupEditor.css";
|
import "./PopupEditor.css";
|
||||||
import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks";
|
|
||||||
import NoteTitleWidget from "../note_title";
|
|
||||||
import NoteIcon from "../note_icon";
|
|
||||||
import NoteContext from "../../components/note_context";
|
|
||||||
import { NoteContextContext, ParentComponent } from "../react/react_utils";
|
|
||||||
import NoteDetail from "../NoteDetail";
|
|
||||||
import { ComponentChildren } from "preact";
|
import { ComponentChildren } from "preact";
|
||||||
|
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import appContext from "../../components/app_context";
|
||||||
|
import NoteContext from "../../components/note_context";
|
||||||
|
import froca from "../../services/froca";
|
||||||
|
import { t } from "../../services/i18n";
|
||||||
|
import tree from "../../services/tree";
|
||||||
|
import utils from "../../services/utils";
|
||||||
import NoteList from "../collections/NoteList";
|
import NoteList from "../collections/NoteList";
|
||||||
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
|
|
||||||
import FormattingToolbar from "../ribbon/FormattingToolbar";
|
|
||||||
import PromotedAttributes from "../PromotedAttributes";
|
|
||||||
import FloatingButtons from "../FloatingButtons";
|
import FloatingButtons from "../FloatingButtons";
|
||||||
import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
|
import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
|
||||||
import utils from "../../services/utils";
|
import NoteIcon from "../note_icon";
|
||||||
import tree from "../../services/tree";
|
import NoteTitleWidget from "../note_title";
|
||||||
import froca from "../../services/froca";
|
import NoteDetail from "../NoteDetail";
|
||||||
|
import PromotedAttributes from "../PromotedAttributes";
|
||||||
|
import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks";
|
||||||
|
import Modal from "../react/Modal";
|
||||||
|
import { NoteContextContext, ParentComponent } from "../react/react_utils";
|
||||||
import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar";
|
import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar";
|
||||||
|
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
|
||||||
|
import FormattingToolbar from "../ribbon/FormattingToolbar";
|
||||||
import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar";
|
import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar";
|
||||||
import { t } from "../../services/i18n";
|
import BreadcrumbBadges from "../BreadcrumbBadges";
|
||||||
import appContext from "../../components/app_context";
|
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||||
|
|
||||||
|
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||||
|
|
||||||
export default function PopupEditor() {
|
export default function PopupEditor() {
|
||||||
const [ shown, setShown ] = useState(false);
|
const [ shown, setShown ] = useState(false);
|
||||||
@ -61,7 +67,10 @@ export default function PopupEditor() {
|
|||||||
<NoteContextContext.Provider value={noteContext}>
|
<NoteContextContext.Provider value={noteContext}>
|
||||||
<DialogWrapper>
|
<DialogWrapper>
|
||||||
<Modal
|
<Modal
|
||||||
title={<TitleRow />}
|
title={<>
|
||||||
|
<TitleRow />
|
||||||
|
{isNewLayout && <BreadcrumbBadges />}
|
||||||
|
</>}
|
||||||
customTitleBarButtons={[{
|
customTitleBarButtons={[{
|
||||||
iconClassName: "bx-expand-alt",
|
iconClassName: "bx-expand-alt",
|
||||||
title: t("popup-editor.maximize"),
|
title: t("popup-editor.maximize"),
|
||||||
@ -75,19 +84,17 @@ export default function PopupEditor() {
|
|||||||
className="popup-editor-dialog"
|
className="popup-editor-dialog"
|
||||||
size="lg"
|
size="lg"
|
||||||
show={shown}
|
show={shown}
|
||||||
onShown={() => {
|
onShown={() => parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId })}
|
||||||
parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId });
|
|
||||||
}}
|
|
||||||
onHidden={() => setShown(false)}
|
onHidden={() => setShown(false)}
|
||||||
keepInDom // needed for faster loading
|
keepInDom // needed for faster loading
|
||||||
noFocus // automatic focus breaks block popup
|
noFocus // automatic focus breaks block popup
|
||||||
>
|
>
|
||||||
<ReadOnlyNoteInfoBar />
|
{!isNewLayout && <ReadOnlyNoteInfoBar />}
|
||||||
<PromotedAttributes />
|
<PromotedAttributes />
|
||||||
|
|
||||||
{isMobile
|
{isMobile
|
||||||
? <MobileEditorToolbar inPopupEditor />
|
? <MobileEditorToolbar inPopupEditor />
|
||||||
: <StandaloneRibbonAdapter component={FormattingToolbar} />}
|
: <StandaloneRibbonAdapter component={FormattingToolbar} />}
|
||||||
|
|
||||||
<FloatingButtons items={items} />
|
<FloatingButtons items={items} />
|
||||||
<NoteDetail />
|
<NoteDetail />
|
||||||
@ -95,7 +102,7 @@ export default function PopupEditor() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
</DialogWrapper>
|
</DialogWrapper>
|
||||||
</NoteContextContext.Provider>
|
</NoteContextContext.Provider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DialogWrapper({ children }: { children: ComponentChildren }) {
|
export function DialogWrapper({ children }: { children: ComponentChildren }) {
|
||||||
@ -107,7 +114,7 @@ export function DialogWrapper({ children }: { children: ComponentChildren }) {
|
|||||||
<div ref={wrapperRef} class={`quick-edit-dialog-wrapper ${note?.getColorClass() ?? ""}`}>
|
<div ref={wrapperRef} class={`quick-edit-dialog-wrapper ${note?.getColorClass() ?? ""}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TitleRow() {
|
export function TitleRow() {
|
||||||
@ -116,5 +123,5 @@ export function TitleRow() {
|
|||||||
<NoteIcon />
|
<NoteIcon />
|
||||||
<NoteTitleWidget />
|
<NoteTitleWidget />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,30 +29,73 @@ body.desktop .note-title-widget input.note-title {
|
|||||||
font-size: 180%;
|
font-size: 180%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.experimental-feature-new-layout .title-row,
|
body.experimental-feature-new-layout {
|
||||||
body.experimental-feature-new-layout .title-details {
|
.title-row,
|
||||||
max-width: var(--max-content-width);
|
.title-details {
|
||||||
}
|
max-width: var(--max-content-width);
|
||||||
|
padding: 0;
|
||||||
|
padding-inline-start: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
body.experimental-feature-new-layout .title-row {
|
.title-row {
|
||||||
margin-top: 2em;
|
margin-left: 12px;
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.experimental-feature-new-layout .title-details {
|
.note-icon-widget {
|
||||||
margin-top: 0;
|
padding: 0;
|
||||||
contain: none;
|
width: 41px;
|
||||||
padding: 0;
|
}
|
||||||
padding-inline-start: 24px;
|
}
|
||||||
opacity: 0.85;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25em;
|
|
||||||
margin: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.experimental-feature-new-layout.prefers-centered-content .title-row,
|
.note-split.type-code:not(.mime-text-x-sqlite) .title-row,
|
||||||
body.experimental-feature-new-layout.prefers-centered-content .title-details {
|
.note-split.type-code:not(.mime-text-x-sqlite) .title-details {
|
||||||
margin-inline: auto;
|
background-color: var(--main-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-details {
|
||||||
|
margin-top: 0;
|
||||||
|
contain: none;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
margin: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
span.value {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-split.view-mode-default {
|
||||||
|
.title-row {
|
||||||
|
padding-top: 2em;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-details {
|
||||||
|
padding-bottom: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) {
|
||||||
|
.title-row,
|
||||||
|
.title-details {
|
||||||
|
width: 100%;
|
||||||
|
max-width: unset;
|
||||||
|
padding-inline-start: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-row {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-details {
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
opacity: 0.65;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.prefers-centered-content .title-row,
|
||||||
|
&.prefers-centered-content .title-details {
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,6 +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.addClass(`view-mode-${this.noteContext?.viewScope?.viewMode ?? "default"}`);
|
||||||
this.$widget.toggleClass(["bgfx", "options"], note.isOptions());
|
this.$widget.toggleClass(["bgfx", "options"], note.isOptions());
|
||||||
this.$widget.toggleClass("protected", note.isProtected);
|
this.$widget.toggleClass("protected", note.isProtected);
|
||||||
|
|
||||||
|
|||||||
@ -117,8 +117,8 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
id={id ?? ariaId}
|
id={id ?? ariaId}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onMouseOver={() => showTooltip()}
|
onMouseEnter={showTooltip}
|
||||||
onMouseLeave={() => hideTooltip()}
|
onMouseLeave={hideTooltip}
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
|
|||||||
@ -161,11 +161,16 @@ export function FormDropdownDivider() {
|
|||||||
return <div className="dropdown-divider" />;
|
return <div className="dropdown-divider" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormDropdownSubmenu({ icon, title, children }: { icon: string, title: ComponentChildren, children: ComponentChildren }) {
|
export function FormDropdownSubmenu({ icon, title, children, dropStart }: {
|
||||||
|
icon: string,
|
||||||
|
title: ComponentChildren,
|
||||||
|
children: ComponentChildren,
|
||||||
|
dropStart?: boolean
|
||||||
|
}) {
|
||||||
const [ openOnMobile, setOpenOnMobile ] = useState(false);
|
const [ openOnMobile, setOpenOnMobile ] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={`dropdown-item dropdown-submenu ${openOnMobile ? "submenu-open" : ""}`}>
|
<li className={clsx("dropdown-item dropdown-submenu", { "submenu-open": openOnMobile, "dropstart": dropStart })}>
|
||||||
<span
|
<span
|
||||||
className="dropdown-toggle"
|
className="dropdown-toggle"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -184,5 +189,5 @@ export function FormDropdownSubmenu({ icon, title, children }: { icon: string, t
|
|||||||
{children}
|
{children}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -201,7 +201,7 @@ export function useTriliumOptionBool(name: OptionNames, needsRefresh?: boolean):
|
|||||||
return [
|
return [
|
||||||
(value === "true"),
|
(value === "true"),
|
||||||
(newValue) => setValue(newValue ? "true" : "false")
|
(newValue) => setValue(newValue ? "true" : "false")
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,17 +217,18 @@ export function useTriliumOptionInt(name: OptionNames): [number, (newValue: numb
|
|||||||
return [
|
return [
|
||||||
(parseInt(value, 10)),
|
(parseInt(value, 10)),
|
||||||
(newValue) => setValue(newValue)
|
(newValue) => setValue(newValue)
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to {@link useTriliumOption}, but the object value is parsed to and from a JSON instead of a string.
|
* Similar to {@link useTriliumOption}, but the object value is parsed to and from a JSON instead of a string.
|
||||||
*
|
*
|
||||||
* @param name the name of the option to listen for.
|
* @param name the name of the option to listen for.
|
||||||
|
* @param needsRefresh whether to reload the frontend whenever the value is changed.
|
||||||
* @returns an array where the first value is the current option value and the second value is the setter.
|
* @returns an array where the first value is the current option value and the second value is the setter.
|
||||||
*/
|
*/
|
||||||
export function useTriliumOptionJson<T>(name: OptionNames): [ T, (newValue: T) => Promise<void> ] {
|
export function useTriliumOptionJson<T>(name: OptionNames, needsRefresh?: boolean): [ T, (newValue: T) => Promise<void> ] {
|
||||||
const [ value, setValue ] = useTriliumOption(name);
|
const [ value, setValue ] = useTriliumOption(name, needsRefresh);
|
||||||
useDebugValue(name);
|
useDebugValue(name);
|
||||||
return [
|
return [
|
||||||
(JSON.parse(value) as T),
|
(JSON.parse(value) as T),
|
||||||
@ -845,9 +846,9 @@ export function useGlobalShortcut(keyboardShortcut: string | null | undefined, h
|
|||||||
export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) {
|
export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) {
|
||||||
const [ isReadOnly, setIsReadOnly ] = useState<boolean | undefined>(undefined);
|
const [ isReadOnly, setIsReadOnly ] = useState<boolean | undefined>(undefined);
|
||||||
|
|
||||||
const enableEditing = useCallback(() => {
|
const enableEditing = useCallback((enabled = true) => {
|
||||||
if (noteContext?.viewScope) {
|
if (noteContext?.viewScope) {
|
||||||
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
|
noteContext.viewScope.readOnlyTemporarilyDisabled = enabled;
|
||||||
appContext.triggerEvent("readOnlyTemporarilyDisabled", {noteContext});
|
appContext.triggerEvent("readOnlyTemporarilyDisabled", {noteContext});
|
||||||
}
|
}
|
||||||
}, [noteContext]);
|
}, [noteContext]);
|
||||||
@ -862,7 +863,7 @@ export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: N
|
|||||||
|
|
||||||
useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => {
|
useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => {
|
||||||
if (noteContext?.ntxId === eventNoteContext.ntxId) {
|
if (noteContext?.ntxId === eventNoteContext.ntxId) {
|
||||||
setIsReadOnly(false);
|
setIsReadOnly(!noteContext.viewScope?.readOnlyTemporarilyDisabled);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/u
|
|||||||
import ws from "../../services/ws";
|
import ws from "../../services/ws";
|
||||||
import ActionButton from "../react/ActionButton";
|
import ActionButton from "../react/ActionButton";
|
||||||
import Dropdown from "../react/Dropdown";
|
import Dropdown from "../react/Dropdown";
|
||||||
import { FormDropdownDivider, FormListHeader, FormListItem } from "../react/FormList";
|
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList";
|
||||||
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks";
|
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteProperty, useTriliumOption } from "../react/hooks";
|
||||||
import { ParentComponent } from "../react/react_utils";
|
import { ParentComponent } from "../react/react_utils";
|
||||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||||
@ -98,7 +98,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
}
|
}
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
<CommandItem command="showRevisions" icon="bx bx-history" text={t("revisions_button.note_revisions")} />
|
<CommandItem command="showRevisions" icon="bx bx-history" text={t("note_actions.view_revisions")} />
|
||||||
<CommandItem command="forceSaveRevision" icon="bx bx-save" disabled={isInOptionsOrHelp} text={t("note_actions.save_revision")} />
|
<CommandItem command="forceSaveRevision" icon="bx bx-save" disabled={isInOptionsOrHelp} text={t("note_actions.save_revision")} />
|
||||||
<CommandItem icon="bx bx-trash destructive-action-icon" text={t("note_actions.delete_note")} destructive
|
<CommandItem icon="bx bx-trash destructive-action-icon" text={t("note_actions.delete_note")} destructive
|
||||||
disabled={isInOptionsOrHelp}
|
disabled={isInOptionsOrHelp}
|
||||||
@ -114,23 +114,22 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
|
|
||||||
function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) {
|
function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<FormDropdownSubmenu title="Development Actions" icon="bx bx-wrench" dropStart>
|
||||||
<FormListHeader text="Development-only Actions" />
|
|
||||||
<FormListItem
|
<FormListItem
|
||||||
icon="bx bx-printer"
|
icon="bx bx-printer"
|
||||||
onClick={() => window.open(`/?print=#root/${note.noteId}`, "_blank")}
|
onClick={() => window.open(`/?print=#root/${note.noteId}`, "_blank")}
|
||||||
>Open print page</FormListItem>
|
>Open print page</FormListItem>
|
||||||
{note.type === "text" && (
|
<FormListItem
|
||||||
<FormListItem
|
icon="bx bx-error"
|
||||||
icon="bx bx-error"
|
disabled={note.type !== "text"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
noteContext?.getTextEditor(editor => {
|
noteContext?.getTextEditor(editor => {
|
||||||
editor.editing.view.change(() => {
|
editor.editing.view.change(() => {
|
||||||
throw new Error("Editor crashed.");
|
throw new Error("Editor crashed.");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}}>Crash editor</FormListItem>)}
|
});
|
||||||
</>
|
}}>Crash editor</FormListItem>
|
||||||
|
</FormDropdownSubmenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
|
|||||||
{
|
{
|
||||||
title: t("classic_editor_toolbar.title"),
|
title: t("classic_editor_toolbar.title"),
|
||||||
icon: "bx bx-text",
|
icon: "bx bx-text",
|
||||||
show: async ({ note, noteContext }) => note?.type === "text"
|
show: async ({ note, noteContext }) => note?.type === "text" && noteContext?.viewScope?.viewMode === "default"
|
||||||
&& options.get("textNoteEditorType") === "ckeditor-classic"
|
&& options.get("textNoteEditorType") === "ckeditor-classic"
|
||||||
&& !(await noteContext?.isReadOnly()),
|
&& !(await noteContext?.isReadOnly()),
|
||||||
toggleCommand: "toggleRibbonTabClassicEditor",
|
toggleCommand: "toggleRibbonTabClassicEditor",
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export default function SharedInfo() {
|
|||||||
|
|
||||||
export function useShareInfo(note: FNote | null | undefined) {
|
export function useShareInfo(note: FNote | null | undefined) {
|
||||||
const [ link, setLink ] = useState<string>();
|
const [ link, setLink ] = useState<string>();
|
||||||
|
const [ linkHref, setLinkHref ] = useState<string>();
|
||||||
const [ syncServerHost ] = useTriliumOption("syncServerHost");
|
const [ syncServerHost ] = useTriliumOption("syncServerHost");
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
@ -52,9 +53,10 @@ export function useShareInfo(note: FNote | null | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLink(`<a href="${link}" class="external tn-link">${link}</a>`);
|
setLink(`<a href="${link}" class="external tn-link">${link}</a>`);
|
||||||
|
setLinkHref(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(refresh, [ note ]);
|
useEffect(refresh, [ note, syncServerHost ]);
|
||||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||||
if (loadResults.getAttributeRows().find((attr) => attr.name?.startsWith("_share") && attributes.isAffecting(attr, note))) {
|
if (loadResults.getAttributeRows().find((attr) => attr.name?.startsWith("_share") && attributes.isAffecting(attr, note))) {
|
||||||
refresh();
|
refresh();
|
||||||
@ -63,7 +65,7 @@ export function useShareInfo(note: FNote | null | undefined) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { link, isSharedExternally: !!syncServerHost };
|
return { link, linkHref, isSharedExternally: !!syncServerHost };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getShareId(note: FNote) {
|
function getShareId(note: FNote) {
|
||||||
|
|||||||
@ -158,7 +158,7 @@ function ExistingAnonymizedDatabases({ databases }: { databases: AnonymizedDbRes
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function VacuumDatabaseOptions() {
|
function VacuumDatabaseOptions() {
|
||||||
@ -175,11 +175,11 @@ function VacuumDatabaseOptions() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</OptionsSection>
|
</OptionsSection>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExperimentalOptions() {
|
function ExperimentalOptions() {
|
||||||
const [ enabledExperimentalFeatures, setEnabledExperimentalFeatures ] = useTriliumOptionJson<string[]>("experimentalFeatures");
|
const [ enabledExperimentalFeatures, setEnabledExperimentalFeatures ] = useTriliumOptionJson<string[]>("experimentalFeatures", true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsSection title={t("experimental_features.title")}>
|
<OptionsSection title={t("experimental_features.title")}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user