Compare commits

...

73 Commits

Author SHA1 Message Date
Elian Doran
28bb4edbac
chore(layout): revert work on floating panel 2025-12-09 19:18:48 +02:00
Elian Doran
9445e64c2e
Translations update from Hosted Weblate (#8004) 2025-12-09 17:49:32 +02:00
Matheus Fongaro (MatioZG)
e6fba03ba7
Translated using Weblate (Portuguese (Brazil))
Currently translated at 3.2% (5 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pt_BR/
2025-12-09 16:16:40 +01:00
green
b027ca5c09
Translated using Weblate (Japanese)
Currently translated at 100.0% (1646 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-09 16:16:39 +01:00
Elian Doran
e98df30500
fix(popup-editor): broken title bar layout 2025-12-09 17:16:29 +02:00
Elian Doran
111c44dadf
fix(content_header): z-index issues 2025-12-09 17:11:28 +02:00
Elian Doran
cb31c25e6c
fix(content_header): note icon dropdown broken when scrolling 2025-12-09 17:08:03 +02:00
Elian Doran
5d59c953c2
fix(layout): title row background in settings 2025-12-09 16:39:31 +02:00
Elian Doran
a2cff42981
fix(layout): scrollbar design in code note 2025-12-09 16:31:47 +02:00
Elian Doran
cae892a971
fix(layout): title not visible on dark code theme 2025-12-09 16:27:19 +02:00
Elian Doran
f8447d923e
feat(ribbon): hide when in options 2025-12-09 16:22:03 +02:00
Elian Doran
3b8dabc9d2
Back/forward navigation in tab bar (#8003) 2025-12-09 16:11:17 +02:00
Elian Doran
cda39e967c
chore(tab_navigation): address requested changes 2025-12-09 16:02:24 +02:00
Elian Doran
7da9367dc9
fix(tab_navigation): affecting server and mobile views 2025-12-09 15:59:15 +02:00
Elian Doran
82d97ef26f
feat(tab_navigation): hide buttons if launcher ones are used 2025-12-09 15:30:46 +02:00
Elian Doran
9e094f1d96
feat(tab_navigation): add it to horizontal layout as well 2025-12-09 15:14:13 +02:00
Elian Doran
da7e15c268
refactor(tab_navigation): sort imports 2025-12-09 15:06:54 +02:00
Elian Doran
24806a810c
feat(tab_navigation): display note icon in history navigation 2025-12-09 15:02:52 +02:00
Elian Doran
a2ace4510a
Translations update from Hosted Weblate (#8000) 2025-12-09 15:00:26 +02:00
Elian Doran
5c8132088f
feat(client): use chevrons to display note path 2025-12-09 14:49:19 +02:00
Elian Doran
7ee060b228
feat(tab_navigation): improve indicator for current item in context menu 2025-12-09 14:41:17 +02:00
Elian Doran
4b2a4b8f7b
feat(tab_navigation): reflect state of history by disabling the buttons 2025-12-09 14:29:45 +02:00
Elian Doran
5a668ede01
chore(tab_navigation): reintroduce history cleaning 2025-12-09 14:24:39 +02:00
Elian Doran
9e099444b6
feat(tab_navigation): functional context menu 2025-12-09 14:23:06 +02:00
Elian Doran
e3f5b3535a
feat(tab_navigation): functional back/forward buttons 2025-12-09 14:10:11 +02:00
Elian Doran
346ad1e8a3
feat(tab_navigation): add the buttons on vertical layout 2025-12-09 14:06:57 +02:00
Elian Doran
2a9558e9c5
style(ribbon): make icons slightly bigger 2025-12-09 13:18:25 +02:00
Hosted Weblate
c324f66aef
Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-09 12:15:20 +01:00
Elian Doran
e688f2cdb6
Add breadcrumbs to navigation (#7995) 2025-12-09 13:15:03 +02:00
Elian Doran
2ff8762a22
chore(client): fix typecheck 2025-12-09 13:07:41 +02:00
Elian Doran
4d75221938
chore(breadcrumbs): address requested changes 2025-12-09 12:41:54 +02:00
Elian Doran
658b699b71
fix(collections/geomap): fake floating buttons mispositioned 2025-12-09 12:24:28 +02:00
Elian Doran
72b0d03546
chore(layout): remove some title margins 2025-12-09 12:23:05 +02:00
Elian Doran
19980807f2
style(ribbon): fix some alignment issues & decrease button size 2025-12-09 12:20:27 +02:00
Elian Doran
3514e3d057
fix(floating_buttons): wrong position when at the top of the note 2025-12-09 11:46:16 +02:00
Elian Doran
fb6c82740c
chore(ribbon): remove top transition completely 2025-12-09 11:38:04 +02:00
Elian Doran
8df5a010c9
fix(ribbon): note buttons cut off 2025-12-09 11:36:00 +02:00
Elian Doran
895e9b8bf0
chore(client/layout): reduce transitions 2025-12-09 11:30:18 +02:00
Elian Doran
bfcf85e0d2
fix(client/layout): content header height not properly resized when switching notes 2025-12-09 11:27:05 +02:00
Elian Doran
5770222304
fix(client/floating_buttons): clipped by floating content header 2025-12-09 11:08:28 +02:00
Elian Doran
d5d2815bdf
fix(client/floating_buttons): clipped by ribbon 2025-12-09 09:20:31 +02:00
Elian Doran
7fc3d413e5
fix(client): 1px scroll in full-height note 2025-12-09 09:13:18 +02:00
Elian Doran
474228b630
style(client): remove bottom border & box-shadow for content header 2025-12-09 08:22:51 +02:00
Elian Doran
0805e077a1
feat(ribbon): basic implementation for scroll pinning 2025-12-09 08:18:27 +02:00
Elian Doran
6b059a9a75
feat(ribbon): context menu for root item 2025-12-09 08:01:52 +02:00
Elian Doran
7377e4e34d
chore(ribbon): improve paddings slightly 2025-12-09 07:58:59 +02:00
Elian Doran
6fac947d9c
chore(ribbon): address requested changes 2025-12-09 07:50:21 +02:00
Elian Doran
5973e5ca26
chore(ribbon): remove label for the root entirely 2025-12-09 07:46:46 +02:00
Elian Doran
608ab53933
chore(ribbon): reduce note title padding 2025-12-08 23:41:17 +02:00
Elian Doran
05679f7a8d
feat(ribbon): prototype sticky ribbon 2025-12-08 23:33:55 +02:00
Elian Doran
fcf51ec6da
chore(eslint): apply to .tsx as well 2025-12-08 23:14:09 +02:00
Elian Doran
d15b5f8cbc
style(next): basic styling of ribbon as a floating toolbar 2025-12-08 23:13:58 +02:00
Elian Doran
ef3cbcac6d
refactor(breadcrumb): fix eslint issues 2025-12-08 23:09:00 +02:00
Elian Doran
b16893c4d2
feat(breadcrumb): collapse items if the list is too big 2025-12-08 23:03:31 +02:00
Elian Doran
a365814aaa
feat(breadcrumb): improve overflow slightly 2025-12-08 22:54:31 +02:00
Elian Doran
eca2116adc
feat(breadcrumb): make the root note clickable 2025-12-08 22:46:04 +02:00
Elian Doran
4cfa403657
feat(breadcrumb): apply ellipsis to dropdown 2025-12-08 22:40:37 +02:00
Elian Doran
70ded4c2cd
chore(breadcrumb): use bold for highlighting active entry 2025-12-08 22:38:06 +02:00
Elian Doran
3fe45db6ef
feat(breadcrumb): improve overflow support 2025-12-08 22:17:49 +02:00
Elian Doran
11467775b6
feat(breadcrumb): basic overflow support 2025-12-08 22:09:46 +02:00
Elian Doran
1e5fcf635e
feat(breadcrumb): show root title if it's the one active 2025-12-08 22:05:09 +02:00
Elian Doran
223ba4643f
fix(breadcrumb): breadcrumb shown if there are no children 2025-12-08 21:57:51 +02:00
Elian Doran
200fd76929
feat(breadcrumb): display a checkbox for the current note 2025-12-08 21:52:36 +02:00
Elian Doran
c5c4ecd6e6
feat(breadcrumb): show current item 2025-12-08 17:04:08 +02:00
Elian Doran
bedca9f82c
feat(breadcrumb): hide root icon 2025-12-08 16:45:26 +02:00
Elian Doran
adc356eff3
fix(breadcrumb): navigation on first level not working 2025-12-08 16:41:06 +02:00
Elian Doran
c4285772b3
feat(breadcrumb): basic navigation in separator 2025-12-08 16:34:40 +02:00
Elian Doran
a02235f2bd
feat(breadcrumb): set up dropdown 2025-12-08 16:03:12 +02:00
Elian Doran
5f215b14c2
feat(breadcrumb): start implementing interactive breadcrumbs 2025-12-08 16:01:34 +02:00
Elian Doran
6e29fe8d58
feat(breadcrumb): hide preview 2025-12-08 15:56:03 +02:00
Elian Doran
43ceb1982d
feat(breadcrumb): hide last note 2025-12-08 15:53:17 +02:00
Elian Doran
d02ec47d77
feat(breadcrumb): get breadcrumb to render 2025-12-08 15:16:06 +02:00
Elian Doran
9942950710
feat(layout): relocate title into scrollable region 2025-12-08 14:54:57 +02:00
29 changed files with 540 additions and 142 deletions

View File

@ -64,6 +64,9 @@ function initOnElectron() {
if (options.get("nativeTitleBarVisible") !== "true") {
initTitleBarButtons(style, currentWindow);
}
// Clear navigation history on frontend refresh.
currentWindow.webContents.navigationHistory.clear();
}
function initTitleBarButtons(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {

View File

@ -44,6 +44,8 @@ import NoteDetail from "../widgets/NoteDetail.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import SpacerWidget from "../widgets/launch_bar/SpacerWidget.jsx";
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
import Breadcrumb from "../widgets/Breadcrumb.jsx";
import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx";
export default class DesktopLayout {
@ -79,6 +81,7 @@ export default class DesktopLayout {
.class("tab-row-container")
.child(new FlexContainer("row").id("tab-row-left-spacer"))
.optChild(launcherPaneIsHorizontal, <LeftPaneToggle isHorizontalLayout={true} />)
.child(<TabHistoryNavigationButtons />)
.child(new TabRowWidget().class("full-width"))
.optChild(customTitleBarButtons, <TitleBarButtons />)
.css("height", "40px")
@ -101,7 +104,12 @@ export default class DesktopLayout {
new FlexContainer("column")
.id("rest-pane")
.css("flex-grow", "1")
.optChild(!fullWidthTabBar, new FlexContainer("row").child(new TabRowWidget()).optChild(customTitleBarButtons, <TitleBarButtons />).css("height", "40px"))
.optChild(!fullWidthTabBar,
new FlexContainer("row")
.child(<TabHistoryNavigationButtons />)
.child(new TabRowWidget())
.optChild(customTitleBarButtons, <TitleBarButtons />)
.css("height", "40px"))
.child(
new FlexContainer("row")
.filling()
@ -117,19 +125,24 @@ export default class DesktopLayout {
new NoteWrapperWidget()
.child(
new FlexContainer("row")
.class("title-row")
.css("height", "50px")
.css("min-height", "50px")
.class("breadcrumb-row")
.css("height", "30px")
.css("min-height", "30px")
.css("align-items", "center")
.cssBlock(".title-row > * { margin: 5px; }")
.child(<NoteIconWidget />)
.child(<NoteTitleWidget />)
.css("padding", "10px")
.cssBlock(".breadcrumb-row > * { margin: 5px; }")
.child(<Breadcrumb />)
.child(<SpacerWidget baseSize={0} growthFactor={1} />)
.child(<MovePaneButton direction="left" />)
.child(<MovePaneButton direction="right" />)
.child(<ClosePaneButton />)
.child(<CreatePaneButton />)
)
.child(new FlexContainer("row")
.class("title-row")
.child(<NoteIconWidget />)
.child(<NoteTitleWidget />)
)
.child(<Ribbon />)
.child(new WatchedFileUpdateStatusWidget())
.child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)

View File

@ -99,7 +99,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
const viewMode = viewScope.viewMode || "default";
let linkTitle = options.title;
if (!linkTitle) {
if (linkTitle === undefined) {
if (viewMode === "attachments" && viewScope.attachmentId) {
const attachment = await froca.getAttachment(viewScope.attachmentId);

View File

@ -4,6 +4,8 @@ import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js";
import appContext from "../components/app_context.js";
export const NOTE_PATH_TITLE_SEPARATOR = " ";
async function resolveNotePath(notePath: string, hoistedNoteId = "root") {
const runPath = await resolveNotePathToSegments(notePath, hoistedNoteId);
@ -254,7 +256,7 @@ async function getNotePathTitle(notePath: string) {
const titlePath = await getNotePathTitleComponents(notePath);
return titlePath.join(" / ");
return titlePath.join(NOTE_PATH_TITLE_SEPARATOR);
}
async function getNoteTitleWithPathAsSuffix(notePath: string) {

View File

@ -24,8 +24,8 @@
--bs-body-font-family: var(--main-font-family) !important;
--bs-body-font-weight: var(--main-font-weight) !important;
--bs-body-color: var(--main-text-color) !important;
--bs-body-bg: var(--main-background-color) !important;
--ck-mention-list-max-height: 500px;
--bs-body-bg: var(--main-background-color) !important;
--ck-mention-list-max-height: 500px;
--tn-modal-max-height: 90vh;
--tree-item-light-theme-max-color-lightness: 50;
@ -471,7 +471,7 @@ body.mobile .dropdown .dropdown-submenu > span {
padding-inline-start: 12px;
}
.dropdown-menu kbd {
.dropdown-menu kbd {
color: var(--muted-text-color);
border: none;
background-color: transparent;
@ -487,7 +487,7 @@ body.mobile .dropdown .dropdown-submenu > span {
border: 1px solid transparent !important;
}
/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns.
/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns.
* It usually wraps a menu item followed by a separator / header and another menu item. */
.dropdown-no-break {
break-inside: avoid;
@ -1367,6 +1367,10 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
background-color: var(--scrollbar-background-color);
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-corner {
background-color: inherit;
}
@ -1591,7 +1595,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
inset-inline-start: 0;
inset-inline-end: 0;
margin: 0 !important;
max-height: 85vh;
max-height: 85vh;
display: flex;
}
@ -2093,7 +2097,7 @@ body.zen .note-split.type-text .scrolling-container {
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);
}
@ -2365,7 +2369,7 @@ footer.webview-footer button {
margin-bottom: 0;
}
.admonition::before {
.admonition::before {
color: var(--accent-color);
font-family: boxicons !important;
position: absolute;
@ -2391,7 +2395,7 @@ footer.webview-footer button {
.ck-content ul.todo-list li:has(> span.todo-list__label input[type="checkbox"]:checked) > span.todo-list__label span.todo-list__label__description {
text-decoration: line-through;
opacity: 0.6;
opacity: 0.6;
}
.chat-options-container {
@ -2524,6 +2528,7 @@ iframe.print-iframe {
position: relative;
flex-grow: 1;
width: 100%;
overflow: hidden;
}
/* Calendar collection */
@ -2538,7 +2543,7 @@ iframe.print-iframe {
body.mobile {
.split-note-container-widget {
flex-direction: column !important;
.note-split {
width: 100%;
}
@ -2553,4 +2558,10 @@ iframe.print-iframe {
opacity: 0.4;
}
}
}
}
body.desktop .title-row {
height: 50px;
min-height: 50px;
align-items: center;
}

View File

@ -164,7 +164,7 @@ ul.editability-dropdown li.dropdown-item > div {
background: var(--cmd-button-hover-background-color);
}
/*
/*
* Note info
*/
@ -177,4 +177,4 @@ ul.editability-dropdown li.dropdown-item > div {
/* Narrow width layout */
.note-info-widget {
container: info-section / inline-size;
}
}

View File

@ -502,7 +502,7 @@ div.bookmark-folder-widget .note-link .bx {
font-size: 1.2em;
}
/*
/*
* QUICK SEARCH BOX
*/
@ -613,7 +613,7 @@ div.quick-search .dropdown-menu {
* As a temporary workaround, the backdrop and transparency are disabled for the
* vertical layout.
*/
body.layout-vertical.background-effects div.quick-search .dropdown-menu {
body.layout-vertical.background-effects div.quick-search .dropdown-menu {
--menu-background-color: var(--menu-background-color-no-backdrop) !important;
}
@ -945,12 +945,26 @@ body.electron.background-effects.layout-horizontal .tab-row-container .toggle-bu
position: absolute;
bottom: 0;
inset-inline-start: -10px;
inset-inline-end: -10px;
inset-inline-end: -6px;
top: 32px;
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
body.electron.background-effects.layout-horizontal .tab-row-container .tab-history-navigation-buttons {
position: relative;
&:after {
content: "";
position: absolute;
bottom: 0;
inset-inline-start: 0;
inset-inline-end: -7px;
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
}
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-left,
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-right {
position: relative;
@ -1569,7 +1583,7 @@ div.floating-buttons .show-floating-buttons-button {
div.floating-buttons .show-floating-buttons-button::before {
animation: floating-buttons-show-hide-button-animation 400ms ease-out;
}
div.floating-buttons .show-floating-buttons-button:hover,
div.floating-buttons .show-floating-buttons-button:active {
box-shadow: var(--floating-button-show-button-hover-shadow);
@ -1831,7 +1845,7 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
.excalidraw .dropdown-menu {
border: unset !important;
box-shadow: unset !important;
box-shadow: unset !important;
background-color: transparent !important;
--island-bg-color: var(--menu-background-color);
--shadow-island: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
@ -1850,4 +1864,4 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
.excalidraw .dropdown-menu:before {
content: unset !important;
}
}

View File

@ -2117,5 +2117,9 @@
"unknown_http_error_title": "Communication error with the server",
"unknown_http_error_content": "Status code: {{statusCode}}\nURL: {{method}} {{url}}\nMessage: {{message}}",
"traefik_blocks_requests": "If you are using the Traefik reverse proxy, it introduced a breaking change which affects the communication with the server."
},
"tab_history_navigation_buttons": {
"go-back": "Go back to previous note",
"go-forward": "Go forward to next note"
}
}

View File

@ -1779,7 +1779,7 @@
"placeholder": "ここにノートの内容を入力...",
"auto-detect-language": "自動検出",
"keeps-crashing": "編集コンポーネントがクラッシュし続けます。Trilium を再起動してください。問題が解決しない場合は、バグレポートの作成をご検討ください。",
"editor_crashed_title": "テキストエディタがクラッシュしました",
"editor_crashed_title": "テキストエディタがクラッシュしました",
"editor_crashed_content": "コンテンツは正常に復元されましたが、最近の変更の一部が保存されていない可能性があります。",
"editor_crashed_details_button": "詳細を見る...",
"editor_crashed_details_intro": "このエラーが何度も発生する場合は、以下の情報を貼り付けて GitHub に報告することを検討してください。",

View File

@ -0,0 +1,55 @@
.breadcrumb-row {
position: relative;
}
.component.breadcrumb {
contain: none;
display: flex;
margin: 0;
align-items: center;
font-size: 0.9em;
gap: 0.25em;
flex-wrap: nowrap;
overflow: hidden;
max-width: 85%;
> span,
> span > span {
display: flex;
align-items: center;
min-width: 0;
a {
color: inherit;
text-decoration: none;
min-width: 0;
max-width: 150px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: block;
flex-shrink: 2;
}
}
> span:last-of-type a {
max-width: 300px;
flex-shrink: 1;
}
ul {
flex-direction: column;
list-style-type: none;
margin: 0;
padding: 0;
}
.dropdown-item span,
.dropdown-item strong {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: block;
max-width: 300px;
}
}

View File

@ -0,0 +1,166 @@
import "./Breadcrumb.css";
import { useMemo } from "preact/hooks";
import { Fragment } from "preact/jsx-runtime";
import NoteContext from "../components/note_context";
import froca from "../services/froca";
import ActionButton from "./react/ActionButton";
import Dropdown from "./react/Dropdown";
import { FormListItem } from "./react/FormList";
import { useChildNotes, useNoteContext, useNoteLabel, useNoteProperty } from "./react/hooks";
import Icon from "./react/Icon";
import NoteLink from "./react/NoteLink";
import link_context_menu from "../menus/link_context_menu";
const COLLAPSE_THRESHOLD = 5;
const INITIAL_ITEMS = 2;
const FINAL_ITEMS = 2;
export default function Breadcrumb() {
const { note, noteContext } = useNoteContext();
const notePath = buildNotePaths(noteContext?.notePathArray);
return (
<div className="breadcrumb">
{notePath.length > COLLAPSE_THRESHOLD ? (
<>
{notePath.slice(0, INITIAL_ITEMS).map((item, index) => (
<Fragment key={item}>
{index === 0
? <BreadcrumbRoot noteContext={noteContext} />
: <BreadcrumbItem notePath={item} />
}
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />
</Fragment>
))}
<BreadcrumbCollapsed items={notePath.slice(INITIAL_ITEMS, -FINAL_ITEMS)} noteContext={noteContext} />
{notePath.slice(-FINAL_ITEMS).map((item, index) => (
<Fragment key={item}>
<BreadcrumbSeparator notePath={notePath[notePath.length - FINAL_ITEMS - (1 - index)]} activeNotePath={item} noteContext={noteContext} />
<BreadcrumbItem notePath={item} />
</Fragment>
))}
</>
) : (
notePath.map((item, index) => (
<Fragment key={item}>
{index === 0
? <BreadcrumbRoot noteContext={noteContext} />
: <BreadcrumbItem notePath={item} />
}
{(index < notePath.length - 1 || note?.hasChildren()) &&
<BreadcrumbSeparator notePath={item} activeNotePath={notePath[index + 1]} noteContext={noteContext} />}
</Fragment>
))
)}
</div>
);
}
function BreadcrumbRoot({ noteContext }: { noteContext: NoteContext | undefined }) {
const note = useMemo(() => froca.getNoteFromCache("root"), []);
useNoteLabel(note, "iconClass");
const title = useNoteProperty(note, "title");
return (note &&
<ActionButton
icon={note.getIcon()}
text={title ?? ""}
onClick={() => noteContext?.setNote("root")}
onContextMenu={(e) => {
e.preventDefault();
link_context_menu.openContextMenu(note.noteId, e);
}}
/>
);
}
function BreadcrumbItem({ notePath }: { notePath: string }) {
return (
<NoteLink
notePath={notePath}
noPreview
/>
);
}
function BreadcrumbSeparator({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) {
return (
<Dropdown
text={<Icon icon="bx bx-chevron-right" />}
noSelectButtonStyle
buttonClassName="icon-action"
hideToggleArrow
dropdownOptions={{ popperConfig: { strategy: "fixed" } }}
>
<BreadcrumbSeparatorDropdownContent notePath={notePath} noteContext={noteContext} activeNotePath={activeNotePath} />
</Dropdown>
);
}
function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNotePath }: { notePath: string, activeNotePath: string, noteContext: NoteContext | undefined }) {
const notePathComponents = notePath.split("/");
const parentNoteId = notePathComponents.at(-1);
const childNotes = useChildNotes(parentNoteId);
return (
<ul className="breadcrumb-child-list">
{childNotes.map((note) => {
const childNotePath = `${notePath}/${note.noteId}`;
return <li key={note.noteId}>
<FormListItem
icon={note.getIcon()}
onClick={() => noteContext?.setNote(childNotePath)}
>
{childNotePath !== activeNotePath
? <span>{note.title}</span>
: <strong>{note.title}</strong>}
</FormListItem>
</li>;
})}
</ul>
);
}
function BreadcrumbCollapsed({ items, noteContext }: { items: string[], noteContext: NoteContext | undefined }) {
return (
<Dropdown
text={<Icon icon="bx bx-dots-horizontal-rounded" />}
noSelectButtonStyle
buttonClassName="icon-action"
hideToggleArrow
dropdownOptions={{ popperConfig: { strategy: "fixed" } }}
>
<ul className="breadcrumb-child-list">
{items.map((notePath) => {
const notePathComponents = notePath.split("/");
const noteId = notePathComponents[notePathComponents.length - 1];
const note = froca.getNoteFromCache(noteId);
if (!note) return null;
return <li key={note.noteId}>
<FormListItem
icon={note.getIcon()}
onClick={() => noteContext?.setNote(notePath)}
>
<span>{note.title}</span>
</FormListItem>
</li>;
})}
</ul>
</Dropdown>
);
}
function buildNotePaths(notePathArray: string[] | undefined) {
if (!notePathArray) return [];
let prefix = "";
const output: string[] = [];
for (const notePath of notePathArray) {
output.push(`${prefix}${notePath}`);
prefix += `${notePath}/`;
}
return output;
}

View File

@ -0,0 +1,7 @@
.component.tab-history-navigation-buttons {
contain: none;
flex-shrink: 0;
display: flex;
align-items: center;
margin-inline-end: 0.5em;
}

View File

@ -0,0 +1,64 @@
import "./TabHistoryNavigationButtons.css";
import { useEffect, useMemo, useState } from "preact/hooks";
import { t } from "../services/i18n";
import { dynamicRequire, isElectron } from "../services/utils";
import { handleHistoryContextMenu } from "./launch_bar/HistoryNavigation";
import ActionButton from "./react/ActionButton";
import { useLauncherVisibility } from "./react/hooks";
export default function TabHistoryNavigationButtons() {
const webContents = useMemo(() => isElectron() ? dynamicRequire("@electron/remote").getCurrentWebContents() : undefined, []);
const onContextMenu = webContents ? handleHistoryContextMenu(webContents) : undefined;
const { canGoBack, canGoForward } = useBackForwardState(webContents);
const legacyBackVisible = useLauncherVisibility("_lbBackInHistory");
const legacyForwardVisible = useLauncherVisibility("_lbForwardInHistory");
return (isElectron() &&
<div className="tab-history-navigation-buttons">
{!legacyBackVisible && <ActionButton
icon="bx bx-left-arrow-alt"
text={t("tab_history_navigation_buttons.go-back")}
triggerCommand="backInNoteHistory"
onContextMenu={onContextMenu}
disabled={!canGoBack}
/>}
{!legacyForwardVisible && <ActionButton
icon="bx bx-right-arrow-alt"
text={t("tab_history_navigation_buttons.go-forward")}
triggerCommand="forwardInNoteHistory"
onContextMenu={onContextMenu}
disabled={!canGoForward}
/>}
</div>
);
}
function useBackForwardState(webContents: Electron.WebContents | undefined) {
const [ canGoBack, setCanGoBack ] = useState(webContents?.navigationHistory.canGoBack());
const [ canGoForward, setCanGoForward ] = useState(webContents?.navigationHistory.canGoForward());
useEffect(() => {
if (!webContents) return;
const updateNavigationState = () => {
setCanGoBack(webContents.navigationHistory.canGoBack());
setCanGoForward(webContents.navigationHistory.canGoForward());
};
webContents.on("did-navigate", updateNavigationState);
webContents.on("did-navigate-in-page", updateNavigationState);
return () => {
webContents.removeListener("did-navigate", updateNavigationState);
webContents.removeListener("did-navigate-in-page", updateNavigationState);
};
}, [ webContents ]);
if (!webContents) {
return { canGoBack: true, canGoForward: true };
}
return { canGoBack, canGoForward };
}

View File

@ -2,9 +2,13 @@
position: absolute;
top: 1em;
right: 1em;
.floating-buttons-children {
top: 0;
}
}
.presentation-container {
width: 100%;
height: 100%;
}
}

View File

@ -0,0 +1 @@
/** Intentionally left empty for now **/

View File

@ -6,4 +6,5 @@
.note-split.type-code:not(.mime-text-x-sqlite) > .scrolling-container {
background-color: var(--code-background-color);
}
--scrollbar-background-color: var(--main-background-color);
}

View File

@ -34,7 +34,8 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
}
.modal.popup-editor-dialog .modal-header .note-title-widget {
margin-top: 8px;
display: flex;
align-items: center;
}
.modal.popup-editor-dialog .modal-body {
@ -42,13 +43,14 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
height: 75vh;
overflow: auto;
display: flex;
flex-direction: column;
flex-direction: column;
}
.modal.popup-editor-dialog .title-row,
.modal.popup-editor-dialog .modal-title,
.modal.popup-editor-dialog .note-icon-widget {
height: 32px;
min-height: unset;
}
.modal.popup-editor-dialog .note-icon-widget {
@ -99,7 +101,7 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
}
.modal.popup-editor-dialog .note-detail-code-editor {
padding: 0;
padding: 0;
& .cm-editor {
margin: 0;

View File

@ -1,11 +1,13 @@
import { useEffect, useRef } from "preact/hooks";
import type { WebContents } from "electron";
import { useMemo } from "preact/hooks";
import FNote from "../../entities/fnote";
import contextMenu, { MenuCommandItem } from "../../menus/context_menu";
import froca from "../../services/froca";
import link from "../../services/link";
import tree from "../../services/tree";
import { dynamicRequire, isElectron } from "../../services/utils";
import { LaunchBarActionButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
import type { WebContents } from "electron";
import contextMenu, { MenuCommandItem } from "../../menus/context_menu";
import tree from "../../services/tree";
import link from "../../services/link";
interface HistoryNavigationProps {
launcherNote: FNote;
@ -16,71 +18,64 @@ const HISTORY_LIMIT = 20;
export default function HistoryNavigationButton({ launcherNote, command }: HistoryNavigationProps) {
const { icon, title } = useLauncherIconAndTitle(launcherNote);
const webContentsRef = useRef<WebContents>(null);
useEffect(() => {
if (isElectron()) {
const webContents = dynamicRequire("@electron/remote").getCurrentWebContents();
// without this, the history is preserved across frontend reloads
webContents?.clearHistory();
webContentsRef.current = webContents;
}
}, []);
const webContents = useMemo(() => isElectron() ? dynamicRequire("@electron/remote").getCurrentWebContents() : undefined, []);
return (
<LaunchBarActionButton
icon={icon}
text={title}
triggerCommand={command}
onContextMenu={async (e) => {
e.preventDefault();
const webContents = webContentsRef.current;
if (!webContents || webContents.navigationHistory.length() < 2) {
return;
}
let items: MenuCommandItem<string>[] = [];
const history = webContents.navigationHistory.getAllEntries();
const activeIndex = webContents.navigationHistory.getActiveIndex();
for (const idx in history) {
const { notePath } = link.parseNavigationStateFromUrl(history[idx].url);
if (!notePath) continue;
const title = await tree.getNotePathTitle(notePath);
items.push({
title,
command: idx,
uiIcon:
parseInt(idx) === activeIndex
? "bx bx-radio-circle-marked" // compare with type coercion!
: parseInt(idx) < activeIndex
? "bx bx-left-arrow-alt"
: "bx bx-right-arrow-alt"
});
}
items.reverse();
if (items.length > HISTORY_LIMIT) {
items = items.slice(0, HISTORY_LIMIT);
}
contextMenu.show({
x: e.pageX,
y: e.pageY,
items,
selectMenuItemHandler: (item: MenuCommandItem<string>) => {
if (item && item.command && webContents) {
const idx = parseInt(item.command, 10);
webContents.navigationHistory.goToIndex(idx);
}
}
});
}}
onContextMenu={webContents ? handleHistoryContextMenu(webContents) : undefined}
/>
)
);
}
export function handleHistoryContextMenu(webContents: WebContents) {
return async (e: MouseEvent) => {
e.preventDefault();
if (!webContents || webContents.navigationHistory.length() < 2) {
return;
}
let items: MenuCommandItem<string>[] = [];
const history = webContents.navigationHistory.getAllEntries();
const activeIndex = webContents.navigationHistory.getActiveIndex();
for (const idx in history) {
const { noteId, notePath } = link.parseNavigationStateFromUrl(history[idx].url);
if (!noteId || !notePath) continue;
const title = await tree.getNotePathTitle(notePath);
const index = parseInt(idx, 10);
const note = froca.getNoteFromCache(noteId);
items.push({
title,
command: idx,
checked: index === activeIndex,
enabled: index !== activeIndex,
uiIcon: note?.getIcon()
});
}
items.reverse();
if (items.length > HISTORY_LIMIT) {
items = items.slice(0, HISTORY_LIMIT);
}
contextMenu.show({
x: e.pageX,
y: e.pageY,
items,
selectMenuItemHandler: (item: MenuCommandItem<string>) => {
if (item && item.command && webContents) {
const idx = parseInt(item.command, 10);
webContents.navigationHistory.goToIndex(idx);
}
}
});
};
}

View File

@ -1,5 +1,5 @@
.note-icon-widget {
padding-inline-start: 7px;
padding-inline-start: 10px;
margin-inline-end: 0;
width: 50px;
height: 50px;
@ -13,7 +13,7 @@
cursor: pointer;
color: var(--muted-text-color);
}
.note-icon-widget button.note-icon:disabled {
cursor: default;
opacity: .75;
@ -68,4 +68,4 @@
border: 1px dashed var(--muted-text-color);
width: 1em;
height: 1em;
}
}

View File

@ -27,4 +27,4 @@ body.mobile .note-title-widget input.note-title {
body.desktop .note-title-widget input.note-title {
font-size: 180%;
}
}

View File

@ -886,15 +886,42 @@ async function isNoteReadOnly(note: FNote, noteContext: NoteContext) {
return true;
}
export function useChildNotes(parentNoteId: string) {
export function useChildNotes(parentNoteId: string | undefined) {
const [ childNotes, setChildNotes ] = useState<FNote[]>([]);
useEffect(() => {
(async function() {
const parentNote = await froca.getNote(parentNoteId);
const childNotes = await parentNote?.getChildNotes();
let childNotes: FNote[] | undefined;
if (parentNoteId) {
const parentNote = await froca.getNote(parentNoteId);
childNotes = await parentNote?.getChildNotes();
}
setChildNotes(childNotes ?? []);
})();
}, [ parentNoteId ]);
}, [ parentNoteId ]);
return childNotes;
}
export function useLauncherVisibility(launchNoteId: string) {
const checkIfVisible = useCallback(() => {
const note = froca.getNoteFromCache(launchNoteId);
return note?.getParentBranches().some(branch =>
[ "_lbVisibleLaunchers", "_lbMobileVisibleLaunchers" ].includes(branch.parentNoteId)) ?? false;
}, [ launchNoteId ]);
const [ isVisible, setIsVisible ] = useState<boolean>(checkIfVisible());
// React to note not being available in the cache.
useEffect(() => {
froca.getNote(launchNoteId).then(() => setIsVisible(checkIfVisible()));
}, [ launchNoteId, checkIfVisible ]);
// React to changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getBranchRows().some(branch => branch.noteId === launchNoteId)) {
setIsVisible(checkIfVisible());
}
});
return isVisible;
}

View File

@ -44,7 +44,7 @@ export function disposeReactWidget(container: Element) {
render(null, container);
}
export function joinElements(components: ComponentChild[] | undefined, separator = ", ") {
export function joinElements(components: ComponentChild[] | undefined, separator: ComponentChild = ", ") {
if (!components) return <></>;
const joinedComponents: ComponentChild[] = [];

View File

@ -6,6 +6,7 @@ import { useEffect, useMemo, useState } from "preact/hooks";
import { NotePathRecord } from "../../entities/fnote";
import NoteLink from "../react/NoteLink";
import { joinElements } from "../react/react_utils";
import { NOTE_PATH_TITLE_SEPARATOR } from "../../services/tree";
export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabContext) {
const [ sortedNotePaths, setSortedNotePaths ] = useState<NotePathRecord[]>();
@ -33,7 +34,7 @@ export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabConte
<div className="note-path-intro">
{sortedNotePaths?.length ? t("note_paths.intro_placed") : t("note_paths.intro_not_placed")}
</div>
<ul className="note-path-list">
{sortedNotePaths?.length ? sortedNotePaths.map(sortedNotePath => (
<NotePath
@ -42,7 +43,7 @@ export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabConte
/>
)) : undefined}
</ul>
<Button
triggerCommand="cloneNoteIdsTo"
text={t("note_paths.clone_button")}
@ -55,7 +56,7 @@ export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabConte
function NotePath({ currentNotePath, notePathRecord }: { currentNotePath?: string | null, notePathRecord?: NotePathRecord }) {
const notePath = notePathRecord?.notePath ?? [];
const notePathString = useMemo(() => notePath.join("/"), [ notePath ]);
const [ classes, icons ] = useMemo(() => {
const classes: string[] = [];
const icons: { icon: string, title: string }[] = [];
@ -95,11 +96,11 @@ function NotePath({ currentNotePath, notePathRecord }: { currentNotePath?: strin
<li class={classes}>
{joinElements(fullNotePaths.map(notePath => (
<NoteLink notePath={notePath} noPreview />
)), " / ")}
)), NOTE_PATH_TITLE_SEPARATOR)}
{icons.map(({ icon, title }) => (
<span class={icon} title={title} />
))}
</li>
)
}
}

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks";
import { useElementSize, useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks";
import "./style.css";
import { Indexed, numberObjectsInPlace } from "../../services/utils";
@ -42,6 +42,16 @@ export default function Ribbon() {
refresh();
}, [ note, noteType, isReadOnlyTemporarilyDisabled ]);
// Manage height.
const containerRef = useRef<HTMLDivElement>(null);
const size = useElementSize(containerRef);
useEffect(() => {
if (!containerRef.current || !size) return;
const parentEl = containerRef.current.closest<HTMLDivElement>(".note-split");
if (!parentEl) return;
parentEl.style.setProperty("--ribbon-height", `${size.height}px`);
}, [ size ]);
// Automatically activate the first ribbon tab that needs to be activated whenever a note changes.
useEffect(() => {
if (!computedTabs) return;
@ -63,9 +73,11 @@ export default function Ribbon() {
}
}, [ computedTabs, activeTabIndex ]));
const shouldShowRibbon = (noteContext?.viewScope?.viewMode === "default" && !noteContext.noteId?.startsWith("_options"));
return (
<div
className={clsx("ribbon-container", noteContext?.viewScope?.viewMode !== "default" && "hidden-ext")}
ref={containerRef}
className={clsx("ribbon-container", !shouldShowRibbon && "hidden-ext")}
style={{ contain: "none" }}
>
<div className="ribbon-top-row">

View File

@ -1,5 +1,13 @@
.ribbon-container {
margin-bottom: 5px;
position: relative;
z-index: 998;
}
/* When content header is floating, ribbon sticks below it */
.scrolling-container:has(.content-header-widget.floating) .ribbon-container {
position: sticky;
top: var(--content-header-height, 100px);
}
.ribbon-top-row {
@ -24,12 +32,15 @@
max-width: max-content;
flex-grow: 10;
user-select: none;
display: flex;
align-items: center;
font-size: 0.9em;
padding-top: 2px;
}
.ribbon-tab-title .bx {
font-size: 150%;
position: relative;
top: 3px;
}
.ribbon-tab-title.active {
@ -71,12 +82,9 @@
display: flex;
border-bottom: 1px solid var(--main-border-color);
margin-inline-end: 5px;
}
.ribbon-button-container > * {
position: relative;
top: -3px;
margin-inline-start: 10px;
align-items: center;
height: 36px;
gap: 10px;
}
.ribbon-body {
@ -386,6 +394,8 @@ body[dir=rtl] .attribute-list-editor {
.note-actions {
width: 35px;
height: 35px;
display: flex;
align-items: center;
}
.note-actions .dropdown-menu {
@ -404,4 +414,4 @@ body[dir=rtl] .attribute-list-editor {
background-color: transparent !important;
pointer-events: none; /* makes it unclickable */
}
/* #endregion */
/* #endregion */

View File

@ -1 +1,9 @@
{}
{
"get-started": {
"title": "Iniciar",
"desktop_title": "Baixe a aplicação para desktop (v{{version}})",
"architecture": "Arquitetura:",
"older_releases": "Veja os lançamentos anteriores",
"server_title": "Configure um servidor para acessar em múltiplos dispositivos"
}
}

12
docs/README-ja.md vendored
View File

@ -81,17 +81,13 @@ Trilium Notes
[OpenIDとTOTPの統合](https://docs.triliumnotes.org/user-guide/setup/server/mfa)
* セルフホスト同期サーバーとの
[同期](https://docs.triliumnotes.org/user-guide/setup/synchronization)
* there are [3rd party services for hosting synchronisation
server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)
* [同期サーバーをホストするためのサードパーティサービス](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)があります
* インターネット上でノートの
[共有](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing)(公開)
* ノートごとに調整可能で強力な
[ノート暗号化](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes)
* [Excalidraw](https://excalidraw.com/) をベースにした図のスケッチ(ノートタイプ「キャンバス」)
* [Relation
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
[note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map)
for visualizing notes and their relations
* ノートとその関係を視覚化するための[リレーションマップ](https://docs.triliumnotes.org/user-guide/note-types/relation-map)と[ノート/リンクマップ](https://docs.triliumnotes.org/user-guide/note-types/note-map)
* [Mind Elixir](https://docs.mind-elixir.com/) をベースとしたマインドマップ
* 位置ピンと GPX トラック付きの
[ジオマップ](https://docs.triliumnotes.org/user-guide/collections/geomap)
@ -111,8 +107,8 @@ Trilium Notes
* [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper)
でWebコンテンツを簡単に保存
* カスタマイズ可能な UI (サイドバー ボタン、ユーザー定義のウィジェットなど)
* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics),
along with a Grafana Dashboard.
* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics)
Grafana ダッシュボード。
✨ TriliumNext 関連のその他の情報については、次のサードパーティのリソース/コミュニティをご覧ください:

22
docs/README-pt_BR.md vendored
View File

@ -12,13 +12,14 @@
# Trilium Notes
![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran)
![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran)\
![Patrocinadores
LiberaPay](https://img.shields.io/liberapay/patrons/ElianDoran)\
![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/trilium)
![GitHub Downloads (all assets, all
releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\
![Downloads no GitHub (todos os arquivos, todas as
versões)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\
[![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
[![Translation
status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
[![Status da
tradução](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
<!-- translate:off -->
<!-- LANGUAGE SWITCHER -->
@ -29,14 +30,15 @@ script)](./README-ZH_TW.md) | [English](../README.md) | [French](./README-fr.md)
[Spanish](./README-es.md)
<!-- translate:on -->
Trilium Notes is a free and open-source, cross-platform hierarchical note taking
application with focus on building large personal knowledge bases.
Trilium Notes é uma aplicação de anotações gratuita e de código aberto,
multiplataforma e hierárquica, com foco em construir grandes bases de
conhecimento pessoal.
<img src="./app.png" alt="Trilium Screenshot" width="1000">
## ⏬ Download
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest)
stable version, recommended for most users.
## ⏬ Baixar
- [Último lançamento](https://github.com/TriliumNext/Trilium/releases/latest) -
versão estável, recomendada para a maioria dos usuários.
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly)
unstable development version, updated daily with the latest features and
fixes.

View File

@ -63,7 +63,7 @@ const mainConfig = [
}
},
{
files: ["**/*.{js,ts,mjs,cjs}"],
files: ["**/*.{js,ts,mjs,cjs,tsx}"],
languageOptions: {
parser: tsParser