mirror of
https://github.com/TriliumNext/Trilium.git
synced 2025-12-10 03:53:37 -06:00
Compare commits
73 Commits
6570a55e7e
...
28bb4edbac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28bb4edbac | ||
|
|
9445e64c2e | ||
|
|
e6fba03ba7 | ||
|
|
b027ca5c09 | ||
|
|
e98df30500 | ||
|
|
111c44dadf | ||
|
|
cb31c25e6c | ||
|
|
5d59c953c2 | ||
|
|
a2cff42981 | ||
|
|
cae892a971 | ||
|
|
f8447d923e | ||
|
|
3b8dabc9d2 | ||
|
|
cda39e967c | ||
|
|
7da9367dc9 | ||
|
|
82d97ef26f | ||
|
|
9e094f1d96 | ||
|
|
da7e15c268 | ||
|
|
24806a810c | ||
|
|
a2ace4510a | ||
|
|
5c8132088f | ||
|
|
7ee060b228 | ||
|
|
4b2a4b8f7b | ||
|
|
5a668ede01 | ||
|
|
9e099444b6 | ||
|
|
e3f5b3535a | ||
|
|
346ad1e8a3 | ||
|
|
2a9558e9c5 | ||
|
|
c324f66aef | ||
|
|
e688f2cdb6 | ||
|
|
2ff8762a22 | ||
|
|
4d75221938 | ||
|
|
658b699b71 | ||
|
|
72b0d03546 | ||
|
|
19980807f2 | ||
|
|
3514e3d057 | ||
|
|
fb6c82740c | ||
|
|
8df5a010c9 | ||
|
|
895e9b8bf0 | ||
|
|
bfcf85e0d2 | ||
|
|
5770222304 | ||
|
|
d5d2815bdf | ||
|
|
7fc3d413e5 | ||
|
|
474228b630 | ||
|
|
0805e077a1 | ||
|
|
6b059a9a75 | ||
|
|
7377e4e34d | ||
|
|
6fac947d9c | ||
|
|
5973e5ca26 | ||
|
|
608ab53933 | ||
|
|
05679f7a8d | ||
|
|
fcf51ec6da | ||
|
|
d15b5f8cbc | ||
|
|
ef3cbcac6d | ||
|
|
b16893c4d2 | ||
|
|
a365814aaa | ||
|
|
eca2116adc | ||
|
|
4cfa403657 | ||
|
|
70ded4c2cd | ||
|
|
3fe45db6ef | ||
|
|
11467775b6 | ||
|
|
1e5fcf635e | ||
|
|
223ba4643f | ||
|
|
200fd76929 | ||
|
|
c5c4ecd6e6 | ||
|
|
bedca9f82c | ||
|
|
adc356eff3 | ||
|
|
c4285772b3 | ||
|
|
a02235f2bd | ||
|
|
5f215b14c2 | ||
|
|
6e29fe8d58 | ||
|
|
43ceb1982d | ||
|
|
d02ec47d77 | ||
|
|
9942950710 |
@ -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) {
|
||||
|
||||
@ -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} />)
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -2524,6 +2528,7 @@ iframe.print-iframe {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Calendar collection */
|
||||
@ -2554,3 +2559,9 @@ iframe.print-iframe {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop .title-row {
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 に報告することを検討してください。",
|
||||
|
||||
55
apps/client/src/widgets/Breadcrumb.css
Normal file
55
apps/client/src/widgets/Breadcrumb.css
Normal 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;
|
||||
}
|
||||
}
|
||||
166
apps/client/src/widgets/Breadcrumb.tsx
Normal file
166
apps/client/src/widgets/Breadcrumb.tsx
Normal 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;
|
||||
}
|
||||
7
apps/client/src/widgets/TabHistoryNavigationButtons.css
Normal file
7
apps/client/src/widgets/TabHistoryNavigationButtons.css
Normal 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;
|
||||
}
|
||||
64
apps/client/src/widgets/TabHistoryNavigationButtons.tsx
Normal file
64
apps/client/src/widgets/TabHistoryNavigationButtons.tsx
Normal 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 };
|
||||
}
|
||||
@ -2,6 +2,10 @@
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
|
||||
.floating-buttons-children {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.presentation-container {
|
||||
|
||||
1
apps/client/src/widgets/containers/content_header.css
Normal file
1
apps/client/src/widgets/containers/content_header.css
Normal file
@ -0,0 +1 @@
|
||||
/** Intentionally left empty for now **/
|
||||
@ -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);
|
||||
}
|
||||
@ -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 {
|
||||
@ -49,6 +50,7 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
||||
.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 {
|
||||
|
||||
@ -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,26 +18,22 @@ 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) => {
|
||||
onContextMenu={webContents ? handleHistoryContextMenu(webContents) : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function handleHistoryContextMenu(webContents: WebContents) {
|
||||
return async (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const webContents = webContentsRef.current;
|
||||
if (!webContents || webContents.navigationHistory.length() < 2) {
|
||||
return;
|
||||
}
|
||||
@ -46,20 +44,19 @@ export default function HistoryNavigationButton({ launcherNote, command }: Histo
|
||||
const activeIndex = webContents.navigationHistory.getActiveIndex();
|
||||
|
||||
for (const idx in history) {
|
||||
const { notePath } = link.parseNavigationStateFromUrl(history[idx].url);
|
||||
if (!notePath) continue;
|
||||
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,
|
||||
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"
|
||||
checked: index === activeIndex,
|
||||
enabled: index !== activeIndex,
|
||||
uiIcon: note?.getIcon()
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,7 +77,5 @@ export default function HistoryNavigationButton({ launcherNote, command }: Histo
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
.note-icon-widget {
|
||||
padding-inline-start: 7px;
|
||||
padding-inline-start: 10px;
|
||||
margin-inline-end: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
@ -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() {
|
||||
let childNotes: FNote[] | undefined;
|
||||
if (parentNoteId) {
|
||||
const parentNote = await froca.getNote(parentNoteId);
|
||||
const childNotes = await parentNote?.getChildNotes();
|
||||
childNotes = await parentNote?.getChildNotes();
|
||||
}
|
||||
setChildNotes(childNotes ?? []);
|
||||
})();
|
||||
}, [ 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;
|
||||
}
|
||||
|
||||
@ -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[] = [];
|
||||
|
||||
@ -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[]>();
|
||||
@ -95,7 +96,7 @@ 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} />
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
12
docs/README-ja.md
vendored
@ -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
22
docs/README-pt_BR.md
vendored
@ -12,13 +12,14 @@
|
||||
# Trilium Notes
|
||||
|
||||

|
||||
\
|
||||
\
|
||||

|
||||
\
|
||||
\
|
||||
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
|
||||
[](https://hosted.weblate.org/engage/trilium/)
|
||||
[](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.
|
||||
|
||||
@ -63,7 +63,7 @@ const mainConfig = [
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["**/*.{js,ts,mjs,cjs}"],
|
||||
files: ["**/*.{js,ts,mjs,cjs,tsx}"],
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user