mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 15:56:29 +01:00
Compare commits
319 Commits
fix/try-to
...
fix/html-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba1c6ba0e1 | ||
|
|
83be42f4ea | ||
|
|
ab9fec0186 | ||
|
|
c6dd32ea7b | ||
|
|
1d4cd538ac | ||
|
|
dc99f725f9 | ||
|
|
2f804f3eac | ||
|
|
9d6bb306e7 | ||
|
|
59a01b816c | ||
|
|
508f46af42 | ||
|
|
1af865a577 | ||
|
|
74834af222 | ||
|
|
f55e33f303 | ||
|
|
bb55544f25 | ||
|
|
2e9e9a60bf | ||
|
|
f7e77cd6cb | ||
|
|
a7a94789e6 | ||
|
|
864ac1a270 | ||
|
|
fbec6d8873 | ||
|
|
5f647a932d | ||
|
|
6e5046c0d4 | ||
|
|
3c9a8e38d3 | ||
|
|
b3a3196136 | ||
|
|
3229b7d106 | ||
|
|
4213c377f8 | ||
|
|
86365ebd44 | ||
|
|
20cf685174 | ||
|
|
aeb5a7b251 | ||
|
|
47a50bb449 | ||
|
|
a2a5b67496 | ||
|
|
3f5239706f | ||
|
|
d2761abd04 | ||
|
|
57983b54d2 | ||
|
|
703cf8434a | ||
|
|
aa4375e25f | ||
|
|
d579e39b40 | ||
|
|
ec646809dd | ||
|
|
ab48a28635 | ||
|
|
3fd7afbb57 | ||
|
|
4074929c6b | ||
|
|
753f1dc7b6 | ||
|
|
f2ce8b9f3c | ||
|
|
735e91e636 | ||
|
|
4df94d1f20 | ||
|
|
70440520e1 | ||
|
|
e49e2d5093 | ||
|
|
f0ac301417 | ||
|
|
168ff90e38 | ||
|
|
5e4f529b26 | ||
|
|
0d1bd3e298 | ||
|
|
70f826b737 | ||
|
|
8bd5af3fd2 | ||
|
|
dbbae87cd3 | ||
|
|
83fd42aff2 | ||
|
|
93c9383a92 | ||
|
|
7c490d8b72 | ||
|
|
b4b5e86a14 | ||
|
|
e166b97b8f | ||
|
|
829f382726 | ||
|
|
4ef103063d | ||
|
|
fa66e50193 | ||
|
|
255ad96c8b | ||
|
|
a12fa1177b | ||
|
|
6fa7cc8201 | ||
|
|
2fff5418a9 | ||
|
|
2e805cd5a3 | ||
|
|
61eaa89de6 | ||
|
|
aa0c021f8b | ||
|
|
4fd02db079 | ||
|
|
88bbc7e8c1 | ||
|
|
af0ba32dd9 | ||
|
|
938d295bf3 | ||
|
|
f82667066f | ||
|
|
03a7fe1282 | ||
|
|
0c0504ffd1 | ||
|
|
e4900ce87b | ||
|
|
04de87722b | ||
|
|
a95e28c085 | ||
|
|
9fbcfb0f0f | ||
|
|
f24a3442fb | ||
|
|
918a945e3b | ||
|
|
3b96b5779b | ||
|
|
a93b20428e | ||
|
|
0522024f6d | ||
|
|
f27f135a61 | ||
|
|
6a76136878 | ||
|
|
1766d28fc2 | ||
|
|
f51d944bb3 | ||
|
|
cabe240e7e | ||
|
|
e72fb39c4d | ||
|
|
0ca30e0e87 | ||
|
|
cc362393be | ||
|
|
40bfd827d2 | ||
|
|
a4046fbf6e | ||
|
|
28605f2687 | ||
|
|
2085d1bbba | ||
|
|
08db03800e | ||
|
|
04b7e0cde9 | ||
|
|
401260d3ca | ||
|
|
53e0c05290 | ||
|
|
cdbb89482e | ||
|
|
e290635ba5 | ||
|
|
e340e6f5e3 | ||
|
|
2d950e8f3a | ||
|
|
4c70d72ba2 | ||
|
|
80edc4c4e0 | ||
|
|
4d07a1aab6 | ||
|
|
e2cd357319 | ||
|
|
620b57bfa6 | ||
|
|
934c9d3df8 | ||
|
|
f034e8bb37 | ||
|
|
93f80c6837 | ||
|
|
a54177fee0 | ||
|
|
0e6ad42923 | ||
|
|
b0beb74011 | ||
|
|
4857fecc41 | ||
|
|
8405d960be | ||
|
|
b9101c9fb2 | ||
|
|
e457e6a2f2 | ||
|
|
f3416fa03e | ||
|
|
3e5ab2b1e1 | ||
|
|
5c0bc9a7c2 | ||
|
|
2adfa55acd | ||
|
|
8125e8afcd | ||
|
|
e328f18558 | ||
|
|
cbdfa9079c | ||
|
|
e08c4515a7 | ||
|
|
650aa16b89 | ||
|
|
11d908218b | ||
|
|
3627a7dc93 | ||
|
|
1e1c8cc4ff | ||
|
|
d616bc09c9 | ||
|
|
f1cef44d5d | ||
|
|
2bb66a7526 | ||
|
|
28a472782f | ||
|
|
a4da002352 | ||
|
|
94fdc2beee | ||
|
|
dd4a01d9f8 | ||
|
|
a2a6c67350 | ||
|
|
2152ca7ba6 | ||
|
|
40e4d236f4 | ||
|
|
0450cd080d | ||
|
|
1eaac79d63 | ||
|
|
19c0305ed9 | ||
|
|
f0d14a966a | ||
|
|
37e6ccdc1a | ||
|
|
06cea99b40 | ||
|
|
1851336862 | ||
|
|
461eb273d9 | ||
|
|
470edc4d70 | ||
|
|
26132a2a56 | ||
|
|
d92bd16042 | ||
|
|
1c7dfa6c91 | ||
|
|
3a3fed4314 | ||
|
|
82bdb76d75 | ||
|
|
066f3ea078 | ||
|
|
9d760a21d5 | ||
|
|
976c795ac6 | ||
|
|
ed320e4e24 | ||
|
|
3e213699e0 | ||
|
|
399c7435ac | ||
|
|
d51fae7878 | ||
|
|
9750e25ad5 | ||
|
|
2f9b2f0e8f | ||
|
|
8deaf22544 | ||
|
|
c38bf09af0 | ||
|
|
4dbc76790a | ||
|
|
917ea3e401 | ||
|
|
5a54dd666f | ||
|
|
733ec2c145 | ||
|
|
e386b03b90 | ||
|
|
25d5d51085 | ||
|
|
50c4301a34 | ||
|
|
0f60c0696b | ||
|
|
3ec2947c4f | ||
|
|
e89162838e | ||
|
|
72181090a5 | ||
|
|
72b2a5cc0d | ||
|
|
1eaeec8100 | ||
|
|
660db3b3ab | ||
|
|
89d2fcb81e | ||
|
|
ccda623840 | ||
|
|
36fb097d1d | ||
|
|
35ef5fd0d3 | ||
|
|
885dd2053b | ||
|
|
6f6f280bdd | ||
|
|
a3e8fd374f | ||
|
|
f91c1f4180 | ||
|
|
d85746c1b9 | ||
|
|
5a17075eef | ||
|
|
6cab47fb55 | ||
|
|
f2db7baeba | ||
|
|
a507991808 | ||
|
|
7c86f90ac6 | ||
|
|
1e9b772692 | ||
|
|
096ab52216 | ||
|
|
88c3cd5cdd | ||
|
|
99a911a220 | ||
|
|
3218ab971b | ||
|
|
274e3c1f7f | ||
|
|
f8916a6e35 | ||
|
|
73f20d01e4 | ||
|
|
2fd3a875b6 | ||
|
|
68cba8d3b2 | ||
|
|
6b28fd405e | ||
|
|
3bccbabe53 | ||
|
|
4b212232c8 | ||
|
|
ac3a8edf2b | ||
|
|
04fbc82d7c | ||
|
|
3f105f7b8b | ||
|
|
b9193a5562 | ||
|
|
e1fa188244 | ||
|
|
80ad87671a | ||
|
|
b6d5a6ec2e | ||
|
|
759398d804 | ||
|
|
c1b30db3d1 | ||
|
|
0c8bfc39ef | ||
|
|
3815fddb27 | ||
|
|
b585a64a38 | ||
|
|
ad85ee3531 | ||
|
|
b607d1e628 | ||
|
|
d7e36bdf93 | ||
|
|
2b8b185b5b | ||
|
|
927ebcbec9 | ||
|
|
ea1397de63 | ||
|
|
ce1f5c6204 | ||
|
|
652114c7b5 | ||
|
|
17cd2128fd | ||
|
|
bc4378cb3e | ||
|
|
9f217b88e4 | ||
|
|
d53faa8c01 | ||
|
|
a934760960 | ||
|
|
82914fc2aa | ||
|
|
db687197de | ||
|
|
efd713dc61 | ||
|
|
3f3c7cfe88 | ||
|
|
73ca285b7a | ||
|
|
168d25c020 | ||
|
|
e8ae5486c8 | ||
|
|
f049b8b915 | ||
|
|
12053e75bb | ||
|
|
62372ed4c5 | ||
|
|
e5caf37697 | ||
|
|
befc5a9530 | ||
|
|
1e00407864 | ||
|
|
73038efccf | ||
|
|
6d37e19b40 | ||
|
|
2c33ef2b0d | ||
|
|
6c30e0836f | ||
|
|
5f77ca31bd | ||
|
|
f7c82d6b09 | ||
|
|
86dd9aa42a | ||
|
|
a85141ace2 | ||
|
|
c33280bbb2 | ||
|
|
df3aa04787 | ||
|
|
4bd25a0d4a | ||
|
|
7fadf4c6e1 | ||
|
|
b24d786933 | ||
|
|
8f69b87dd1 | ||
|
|
8287063aab | ||
|
|
21683db0b8 | ||
|
|
978d829150 | ||
|
|
cc05572a35 | ||
|
|
c5bb310613 | ||
|
|
77551b1fed | ||
|
|
70728c274e | ||
|
|
cee4714665 | ||
|
|
c3eca3b626 | ||
|
|
01e4cd2e78 | ||
|
|
b99d01ad7b | ||
|
|
bf0213907e | ||
|
|
eff5b6459d | ||
|
|
8e29b5eed6 | ||
|
|
c91748da15 | ||
|
|
f04f9dc262 | ||
|
|
e873cdab7e | ||
|
|
f9b6fd6ac5 | ||
|
|
da4810672d | ||
|
|
f772f59d7c | ||
|
|
1964fb90d5 | ||
|
|
5945f2860a | ||
|
|
f45da049b9 | ||
|
|
c0beab8a5d | ||
|
|
cabeb13adb | ||
|
|
e2e9721d5f | ||
|
|
4e9deab605 | ||
|
|
9bb048fb01 | ||
|
|
6849f80506 | ||
|
|
5597f4e2e0 | ||
|
|
45fbcec805 | ||
|
|
d76d50f30e | ||
|
|
4685aef88d | ||
|
|
a106510924 | ||
|
|
9d54503ef7 | ||
|
|
b1449eebf3 | ||
|
|
b213453062 | ||
|
|
076c0321cf | ||
|
|
4d71b73f38 | ||
|
|
b20ffdf7db | ||
|
|
ef018e22d6 | ||
|
|
3fa290a257 | ||
|
|
cdde530b60 | ||
|
|
aa608510d0 | ||
|
|
009fd63ce9 | ||
|
|
bea352855a | ||
|
|
51e8a80ca3 | ||
|
|
8a543d4513 | ||
|
|
945e180a6f | ||
|
|
b93fa332d3 | ||
|
|
9e947f742d | ||
|
|
033e90f8b7 | ||
|
|
be576176c5 | ||
|
|
4da3e8a4d8 | ||
|
|
db2bf537ea | ||
|
|
9a4fdcaef2 | ||
|
|
ca40360f7d | ||
|
|
799e705ff8 | ||
|
|
59486cd55d | ||
|
|
afe3904ea3 |
@@ -1,6 +1,6 @@
|
||||
root = true
|
||||
|
||||
[*.{js,ts}]
|
||||
[*.{js,ts,.tsx}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
|
||||
5
.vscode/snippets.code-snippets
vendored
5
.vscode/snippets.code-snippets
vendored
@@ -20,5 +20,10 @@
|
||||
"scope": "typescript",
|
||||
"prefix": "jqf",
|
||||
"body": ["private $${1:name}!: JQuery<HTMLElement>;"]
|
||||
},
|
||||
"region": {
|
||||
"scope": "css",
|
||||
"prefix": "region",
|
||||
"body": ["/* #region ${1:name} */\n$0\n/* #endregion */"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs": "1.11.14",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "2.2.0",
|
||||
"draggabilly": "3.0.0",
|
||||
@@ -46,7 +46,7 @@
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "16.2.0",
|
||||
"marked": "16.2.1",
|
||||
"mermaid": "11.10.1",
|
||||
"mind-elixir": "5.0.6",
|
||||
"normalize.css": "8.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import froca from "../services/froca.js";
|
||||
import RootCommandExecutor from "./root_command_executor.js";
|
||||
import Entrypoints, { type SqlExecuteResults } from "./entrypoints.js";
|
||||
import Entrypoints from "./entrypoints.js";
|
||||
import options from "../services/options.js";
|
||||
import utils, { hasTouchBar } from "../services/utils.js";
|
||||
import zoomComponent from "./zoom.js";
|
||||
@@ -31,16 +31,14 @@ import { StartupChecks } from "./startup_checks.js";
|
||||
import type { CreateNoteOpts } from "../services/note_create.js";
|
||||
import { ColumnComponent } from "tabulator-tables";
|
||||
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
|
||||
import type RootContainer from "../widgets/containers/root_container.js";
|
||||
import { SqlExecuteResults } from "@triliumnext/commons";
|
||||
|
||||
interface Layout {
|
||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
||||
getRootWidget: (appContext: AppContext) => RootContainer;
|
||||
}
|
||||
|
||||
interface RootWidget extends Component {
|
||||
render: () => JQuery<HTMLElement>;
|
||||
}
|
||||
|
||||
interface BeforeUploadListener extends Component {
|
||||
export interface BeforeUploadListener extends Component {
|
||||
beforeUnloadEvent(): boolean;
|
||||
}
|
||||
|
||||
@@ -85,7 +83,6 @@ export type CommandMappings = {
|
||||
focusTree: CommandData;
|
||||
focusOnTitle: CommandData;
|
||||
focusOnDetail: CommandData;
|
||||
focusOnSearchDefinition: Required<CommandData>;
|
||||
searchNotes: CommandData & {
|
||||
searchString?: string;
|
||||
ancestorNoteId?: string | null;
|
||||
@@ -93,6 +90,11 @@ export type CommandMappings = {
|
||||
closeTocCommand: CommandData;
|
||||
closeHlt: CommandData;
|
||||
showLaunchBarSubtree: CommandData;
|
||||
showHiddenSubtree: CommandData;
|
||||
showSQLConsoleHistory: CommandData;
|
||||
logout: CommandData;
|
||||
switchToMobileVersion: CommandData;
|
||||
switchToDesktopVersion: CommandData;
|
||||
showRevisions: CommandData & {
|
||||
noteId?: string | null;
|
||||
};
|
||||
@@ -138,6 +140,7 @@ export type CommandMappings = {
|
||||
showLeftPane: CommandData;
|
||||
showAttachments: CommandData;
|
||||
showSearchHistory: CommandData;
|
||||
showShareSubtree: CommandData;
|
||||
hoistNote: CommandData & { noteId: string };
|
||||
leaveProtectedSession: CommandData;
|
||||
enterProtectedSession: CommandData;
|
||||
@@ -323,6 +326,7 @@ export type CommandMappings = {
|
||||
printActiveNote: CommandData;
|
||||
exportAsPdf: CommandData;
|
||||
openNoteExternally: CommandData;
|
||||
openNoteCustom: CommandData;
|
||||
renderActiveNote: CommandData;
|
||||
unhoist: CommandData;
|
||||
reloadFrontendApp: CommandData;
|
||||
@@ -526,7 +530,7 @@ export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMapp
|
||||
export class AppContext extends Component {
|
||||
isMainWindow: boolean;
|
||||
components: Component[];
|
||||
beforeUnloadListeners: WeakRef<BeforeUploadListener>[];
|
||||
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
|
||||
tabManager!: TabManager;
|
||||
layout?: Layout;
|
||||
noteTreeWidget?: NoteTreeWidget;
|
||||
@@ -619,7 +623,7 @@ export class AppContext extends Component {
|
||||
component.triggerCommand(commandName, { $el: $(this) });
|
||||
});
|
||||
|
||||
this.child(rootWidget);
|
||||
this.child(rootWidget as Component);
|
||||
|
||||
this.triggerEvent("initialRenderComplete", {});
|
||||
}
|
||||
@@ -649,13 +653,17 @@ export class AppContext extends Component {
|
||||
return $(el).closest(".component").prop("component");
|
||||
}
|
||||
|
||||
addBeforeUnloadListener(obj: BeforeUploadListener) {
|
||||
addBeforeUnloadListener(obj: BeforeUploadListener | (() => boolean)) {
|
||||
if (typeof WeakRef !== "function") {
|
||||
// older browsers don't support WeakRef
|
||||
return;
|
||||
}
|
||||
|
||||
this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
|
||||
if (typeof obj === "object") {
|
||||
this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
|
||||
} else {
|
||||
this.beforeUnloadListeners.push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,25 +673,29 @@ const appContext = new AppContext(window.glob.isMainWindow);
|
||||
$(window).on("beforeunload", () => {
|
||||
let allSaved = true;
|
||||
|
||||
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter((wr) => !!wr.deref());
|
||||
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter((wr) => typeof wr === "function" || !!wr.deref());
|
||||
|
||||
for (const weakRef of appContext.beforeUnloadListeners) {
|
||||
const component = weakRef.deref();
|
||||
for (const listener of appContext.beforeUnloadListeners) {
|
||||
if (typeof listener === "object") {
|
||||
const component = listener.deref();
|
||||
|
||||
if (!component) {
|
||||
continue;
|
||||
}
|
||||
if (!component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component.beforeUnloadEvent()) {
|
||||
console.log(`Component ${component.componentId} is not finished saving its state.`);
|
||||
|
||||
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
|
||||
|
||||
allSaved = false;
|
||||
if (!component.beforeUnloadEvent()) {
|
||||
console.log(`Component ${component.componentId} is not finished saving its state.`);
|
||||
allSaved = false;
|
||||
}
|
||||
} else {
|
||||
if (!listener()) {
|
||||
allSaved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allSaved) {
|
||||
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
|
||||
return "some string";
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import utils from "../services/utils.js";
|
||||
import type { CommandMappings, CommandNames, EventData, EventNames } from "./app_context.js";
|
||||
|
||||
type EventHandler = ((data: any) => void);
|
||||
|
||||
/**
|
||||
* Abstract class for all components in the Trilium's frontend.
|
||||
*
|
||||
@@ -19,6 +21,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
initialized: Promise<void> | null;
|
||||
parent?: TypedComponent<any>;
|
||||
_position!: number;
|
||||
private listeners: Record<string, EventHandler[]> | null = {};
|
||||
|
||||
constructor() {
|
||||
this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`;
|
||||
@@ -76,6 +79,14 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
// Handle React children.
|
||||
if (this.listeners?.[name]) {
|
||||
for (const listener of this.listeners[name]) {
|
||||
listener(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle legacy children.
|
||||
for (const child of this.children) {
|
||||
const ret = child.handleEvent(name, data) as Promise<void>;
|
||||
|
||||
@@ -120,6 +131,35 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
registerHandler<T extends EventNames>(name: T, handler: EventHandler) {
|
||||
if (!this.listeners) {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
if (!this.listeners[name]) {
|
||||
this.listeners[name] = [];
|
||||
}
|
||||
|
||||
if (this.listeners[name].includes(handler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listeners[name].push(handler);
|
||||
}
|
||||
|
||||
removeHandler<T extends EventNames>(name: T, handler: EventHandler) {
|
||||
if (!this.listeners?.[name]?.includes(handler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listeners[name] = this.listeners[name]
|
||||
.filter(listener => listener !== handler);
|
||||
|
||||
if (!this.listeners[name].length) {
|
||||
delete this.listeners[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class Component extends TypedComponent<Component> {}
|
||||
|
||||
@@ -10,22 +10,7 @@ import bundleService from "../services/bundle.js";
|
||||
import froca from "../services/froca.js";
|
||||
import linkService from "../services/link.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
// TODO: Move somewhere else nicer.
|
||||
export type SqlExecuteResults = string[][][];
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
interface SqlExecuteResponse {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
results: SqlExecuteResults;
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
interface CreateChildrenResponse {
|
||||
note: FNote;
|
||||
}
|
||||
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
|
||||
|
||||
export default class Entrypoints extends Component {
|
||||
constructor() {
|
||||
@@ -34,7 +19,7 @@ export default class Entrypoints extends Component {
|
||||
|
||||
openDevToolsCommand() {
|
||||
if (utils.isElectron()) {
|
||||
utils.dynamicRequire("@electron/remote").getCurrentWindow().toggleDevTools();
|
||||
utils.dynamicRequire("@electron/remote").getCurrentWindow().webContents.toggleDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +109,7 @@ export default class Entrypoints extends Component {
|
||||
if (utils.isElectron()) {
|
||||
// standard JS version does not work completely correctly in electron
|
||||
const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents();
|
||||
const activeIndex = parseInt(webContents.navigationHistory.getActiveIndex());
|
||||
const activeIndex = webContents.navigationHistory.getActiveIndex();
|
||||
|
||||
webContents.goToIndex(activeIndex - 1);
|
||||
} else {
|
||||
@@ -136,7 +121,7 @@ export default class Entrypoints extends Component {
|
||||
if (utils.isElectron()) {
|
||||
// standard JS version does not work completely correctly in electron
|
||||
const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents();
|
||||
const activeIndex = parseInt(webContents.navigationHistory.getActiveIndex());
|
||||
const activeIndex = webContents.navigationHistory.getActiveIndex();
|
||||
|
||||
webContents.goToIndex(activeIndex + 1);
|
||||
} else {
|
||||
|
||||
@@ -43,8 +43,6 @@ export default class RootCommandExecutor extends Component {
|
||||
const noteContext = await appContext.tabManager.openTabWithNoteWithHoisting(searchNote.noteId, {
|
||||
activate: true
|
||||
});
|
||||
|
||||
appContext.triggerCommand("focusOnSearchDefinition", { ntxId: noteContext.ntxId });
|
||||
}
|
||||
|
||||
async searchInSubtreeCommand({ notePath }: CommandListenerData<"searchInSubtree">) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import electronContextMenu from "./menus/electron_context_menu.js";
|
||||
import glob from "./services/glob.js";
|
||||
import { t } from "./services/i18n.js";
|
||||
import options from "./services/options.js";
|
||||
import server from "./services/server.js";
|
||||
import type ElectronRemote from "@electron/remote";
|
||||
import type Electron from "electron";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
|
||||
@@ -64,7 +64,7 @@ export interface NoteMetaData {
|
||||
/**
|
||||
* Note is the main node and concept in Trilium.
|
||||
*/
|
||||
class FNote {
|
||||
export default class FNote {
|
||||
private froca: Froca;
|
||||
|
||||
noteId!: string;
|
||||
@@ -1020,6 +1020,14 @@ class FNote {
|
||||
return this.noteId.startsWith("_options");
|
||||
}
|
||||
|
||||
isTriliumSqlite() {
|
||||
return this.mime === "text/x-sqlite;schema=trilium";
|
||||
}
|
||||
|
||||
isTriliumScript() {
|
||||
return this.mime.startsWith("application/javascript");
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides note's date metadata.
|
||||
*/
|
||||
@@ -1027,5 +1035,3 @@ class FNote {
|
||||
return await server.get<NoteMetaData>(`notes/${this.noteId}/metadata`);
|
||||
}
|
||||
}
|
||||
|
||||
export default FNote;
|
||||
|
||||
@@ -1,78 +1,47 @@
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
|
||||
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import OwnedAttributeListWidget from "../widgets/ribbon_widgets/owned_attribute_list.js";
|
||||
import NoteActionsWidget from "../widgets/buttons/note_actions.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import RibbonContainer from "../widgets/containers/ribbon_container.js";
|
||||
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||
import InheritedAttributesWidget from "../widgets/ribbon_widgets/inherited_attribute_list.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import NoteListWidget from "../widgets/note_list.js";
|
||||
import SearchDefinitionWidget from "../widgets/ribbon_widgets/search_definition.js";
|
||||
import SqlResultWidget from "../widgets/sql_result.js";
|
||||
import SqlTableSchemasWidget from "../widgets/sql_table_schemas.js";
|
||||
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||
import ImagePropertiesWidget from "../widgets/ribbon_widgets/image_properties.js";
|
||||
import NotePropertiesWidget from "../widgets/ribbon_widgets/note_properties.js";
|
||||
import NoteIconWidget from "../widgets/note_icon.js";
|
||||
import SearchResultWidget from "../widgets/search_result.js";
|
||||
import NoteIconWidget from "../widgets/note_icon.jsx";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import RootContainer from "../widgets/containers/root_container.js";
|
||||
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
|
||||
import SpacerWidget from "../widgets/spacer.js";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
|
||||
import LeftPaneToggleWidget from "../widgets/buttons/left_pane_toggle.js";
|
||||
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
|
||||
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
|
||||
import BasicPropertiesWidget from "../widgets/ribbon_widgets/basic_properties.js";
|
||||
import NoteInfoWidget from "../widgets/ribbon_widgets/note_info_widget.js";
|
||||
import BookPropertiesWidget from "../widgets/ribbon_widgets/book_properties.js";
|
||||
import NoteMapRibbonWidget from "../widgets/ribbon_widgets/note_map.js";
|
||||
import NotePathsWidget from "../widgets/ribbon_widgets/note_paths.js";
|
||||
import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
|
||||
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
|
||||
import EditButton from "../widgets/floating_buttons/edit_button.js";
|
||||
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
|
||||
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
|
||||
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
|
||||
import SharedInfoWidget from "../widgets/shared_info.js";
|
||||
import FindWidget from "../widgets/find.js";
|
||||
import TocWidget from "../widgets/toc.js";
|
||||
import HighlightsListWidget from "../widgets/highlights_list.js";
|
||||
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
|
||||
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
||||
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
||||
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
|
||||
import LauncherContainer from "../widgets/containers/launcher_container.js";
|
||||
import RevisionsButton from "../widgets/buttons/revisions_button.js";
|
||||
import CodeButtonsWidget from "../widgets/floating_buttons/code_buttons.js";
|
||||
import ApiLogWidget from "../widgets/api_log.js";
|
||||
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
|
||||
import ScriptExecutorWidget from "../widgets/ribbon_widgets/script_executor.js";
|
||||
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
|
||||
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
|
||||
import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js";
|
||||
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
|
||||
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
||||
import ScrollPadding from "../widgets/scroll_padding.js";
|
||||
import options from "../services/options.js";
|
||||
import utils from "../services/utils.js";
|
||||
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
|
||||
import ContextualHelpButton from "../widgets/floating_buttons/help_button.js";
|
||||
import CloseZenButton from "../widgets/close_zen_button.js";
|
||||
import type { AppContext } from "../components/app_context.js";
|
||||
import type { WidgetsByParent } from "../services/bundle.js";
|
||||
import SwitchSplitOrientationButton from "../widgets/floating_buttons/switch_layout_button.js";
|
||||
import ToggleReadOnlyButton from "../widgets/floating_buttons/toggle_read_only_button.js";
|
||||
import PngExportButton from "../widgets/floating_buttons/png_export_button.js";
|
||||
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
|
||||
import FloatingButtons from "../widgets/FloatingButtons.jsx";
|
||||
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
||||
import SearchResult from "../widgets/search_result.jsx";
|
||||
import GlobalMenu from "../widgets/buttons/global_menu.jsx";
|
||||
import SqlResults from "../widgets/sql_result.js";
|
||||
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
|
||||
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
|
||||
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
|
||||
import ApiLog from "../widgets/api_log.jsx";
|
||||
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
|
||||
import SharedInfo from "../widgets/shared_info.jsx";
|
||||
|
||||
export default class DesktopLayout {
|
||||
|
||||
@@ -107,9 +76,9 @@ export default class DesktopLayout {
|
||||
new FlexContainer("row")
|
||||
.class("tab-row-container")
|
||||
.child(new FlexContainer("row").id("tab-row-left-spacer"))
|
||||
.optChild(launcherPaneIsHorizontal, new LeftPaneToggleWidget(true))
|
||||
.optChild(launcherPaneIsHorizontal, <LeftPaneToggle isHorizontalLayout={true} />)
|
||||
.child(new TabRowWidget().class("full-width"))
|
||||
.optChild(customTitleBarButtons, new TitleBarButtonsWidget())
|
||||
.optChild(customTitleBarButtons, <TitleBarButtons />)
|
||||
.css("height", "40px")
|
||||
.css("background-color", "var(--launcher-pane-background-color)")
|
||||
.setParent(appContext)
|
||||
@@ -130,7 +99,7 @@ export default class DesktopLayout {
|
||||
new FlexContainer("column")
|
||||
.id("rest-pane")
|
||||
.css("flex-grow", "1")
|
||||
.optChild(!fullWidthTabBar, new FlexContainer("row").child(new TabRowWidget()).optChild(customTitleBarButtons, new TitleBarButtonsWidget()).css("height", "40px"))
|
||||
.optChild(!fullWidthTabBar, new FlexContainer("row").child(new TabRowWidget()).optChild(customTitleBarButtons, <TitleBarButtons />).css("height", "40px"))
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
.filling()
|
||||
@@ -151,69 +120,30 @@ export default class DesktopLayout {
|
||||
.css("min-height", "50px")
|
||||
.css("align-items", "center")
|
||||
.cssBlock(".title-row > * { margin: 5px; }")
|
||||
.child(new NoteIconWidget())
|
||||
.child(new NoteTitleWidget())
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(new SpacerWidget(0, 1))
|
||||
.child(new MovePaneButton(true))
|
||||
.child(new MovePaneButton(false))
|
||||
.child(new ClosePaneButton())
|
||||
.child(new CreatePaneButton())
|
||||
)
|
||||
.child(
|
||||
new RibbonContainer()
|
||||
// the order of the widgets matter. Some of these want to "activate" themselves
|
||||
// when visible. When this happens to multiple of them, the first one "wins".
|
||||
// promoted attributes should always win.
|
||||
.ribbon(new ClassicEditorToolbar())
|
||||
.ribbon(new ScriptExecutorWidget())
|
||||
.ribbon(new SearchDefinitionWidget())
|
||||
.ribbon(new EditedNotesWidget())
|
||||
.ribbon(new BookPropertiesWidget())
|
||||
.ribbon(new NotePropertiesWidget())
|
||||
.ribbon(new FilePropertiesWidget())
|
||||
.ribbon(new ImagePropertiesWidget())
|
||||
.ribbon(new BasicPropertiesWidget())
|
||||
.ribbon(new OwnedAttributeListWidget())
|
||||
.ribbon(new InheritedAttributesWidget())
|
||||
.ribbon(new NotePathsWidget())
|
||||
.ribbon(new NoteMapRibbonWidget())
|
||||
.ribbon(new SimilarNotesWidget())
|
||||
.ribbon(new NoteInfoWidget())
|
||||
.button(new RevisionsButton())
|
||||
.button(new NoteActionsWidget())
|
||||
)
|
||||
.child(new SharedInfoWidget())
|
||||
.child(<Ribbon />)
|
||||
.child(<SharedInfo />)
|
||||
.child(new WatchedFileUpdateStatusWidget())
|
||||
.child(
|
||||
new FloatingButtons()
|
||||
.child(new RefreshButton())
|
||||
.child(new SwitchSplitOrientationButton())
|
||||
.child(new ToggleReadOnlyButton())
|
||||
.child(new EditButton())
|
||||
.child(new ShowTocWidgetButton())
|
||||
.child(new ShowHighlightsListWidgetButton())
|
||||
.child(new CodeButtonsWidget())
|
||||
.child(new RelationMapButtons())
|
||||
.child(new GeoMapButtons())
|
||||
.child(new CopyImageReferenceButton())
|
||||
.child(new SvgExportButton())
|
||||
.child(new PngExportButton())
|
||||
.child(new BacklinksWidget())
|
||||
.child(new ContextualHelpButton())
|
||||
.child(new HideFloatingButtonsButton())
|
||||
)
|
||||
.child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
|
||||
.child(
|
||||
new ScrollingContainer()
|
||||
.filling()
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(new SqlTableSchemasWidget())
|
||||
.child(<SqlTableSchemas />)
|
||||
.child(new NoteDetailWidget())
|
||||
.child(new NoteListWidget(false))
|
||||
.child(new SearchResultWidget())
|
||||
.child(new SqlResultWidget())
|
||||
.child(new ScrollPaddingWidget())
|
||||
.child(<SearchResult />)
|
||||
.child(<SqlResults />)
|
||||
.child(<ScrollPadding />)
|
||||
)
|
||||
.child(new ApiLogWidget())
|
||||
.child(<ApiLog />)
|
||||
.child(new FindWidget())
|
||||
.child(
|
||||
...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC
|
||||
@@ -232,11 +162,11 @@ export default class DesktopLayout {
|
||||
)
|
||||
)
|
||||
)
|
||||
.child(new CloseZenButton())
|
||||
.child(<CloseZenModeButton />)
|
||||
|
||||
// Desktop-specific dialogs.
|
||||
.child(new PasswordNoteSetDialog())
|
||||
.child(new UploadAttachmentsDialog());
|
||||
.child(<PasswordNoteSetDialog />)
|
||||
.child(<UploadAttachmentsDialog />);
|
||||
|
||||
applyModals(rootContainer);
|
||||
return rootContainer;
|
||||
@@ -246,14 +176,18 @@ export default class DesktopLayout {
|
||||
let launcherPane;
|
||||
|
||||
if (isHorizontal) {
|
||||
launcherPane = new FlexContainer("row").css("height", "53px").class("horizontal").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true));
|
||||
launcherPane = new FlexContainer("row")
|
||||
.css("height", "53px")
|
||||
.class("horizontal")
|
||||
.child(new LauncherContainer(true))
|
||||
.child(<GlobalMenu isHorizontalLayout={true} />);
|
||||
} else {
|
||||
launcherPane = new FlexContainer("column")
|
||||
.css("width", "53px")
|
||||
.class("vertical")
|
||||
.child(new GlobalMenuWidget(false))
|
||||
.child(<GlobalMenu isHorizontalLayout={false} />)
|
||||
.child(new LauncherContainer(false))
|
||||
.child(new LeftPaneToggleWidget(false));
|
||||
.child(<LeftPaneToggle isHorizontalLayout={false} />);
|
||||
}
|
||||
|
||||
launcherPane.id("launcher-pane");
|
||||
@@ -24,48 +24,48 @@ import InfoDialog from "../widgets/dialogs/info.js";
|
||||
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
||||
import PopupEditorDialog from "../widgets/dialogs/popup_editor.js";
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import NoteIconWidget from "../widgets/note_icon.js";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
||||
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||
import NoteIconWidget from "../widgets/note_icon";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import NoteListWidget from "../widgets/note_list.js";
|
||||
import { CallToActionDialog } from "../widgets/dialogs/call_to_action.jsx";
|
||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||
import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js";
|
||||
|
||||
export function applyModals(rootContainer: RootContainer) {
|
||||
rootContainer
|
||||
.child(new BulkActionsDialog())
|
||||
.child(new AboutDialog())
|
||||
.child(new HelpDialog())
|
||||
.child(new RecentChangesDialog())
|
||||
.child(new BranchPrefixDialog())
|
||||
.child(new SortChildNotesDialog())
|
||||
.child(new IncludeNoteDialog())
|
||||
.child(new NoteTypeChooserDialog())
|
||||
.child(new JumpToNoteDialog())
|
||||
.child(new AddLinkDialog())
|
||||
.child(new CloneToDialog())
|
||||
.child(new MoveToDialog())
|
||||
.child(new ImportDialog())
|
||||
.child(new ExportDialog())
|
||||
.child(new MarkdownImportDialog())
|
||||
.child(new ProtectedSessionPasswordDialog())
|
||||
.child(new RevisionsDialog())
|
||||
.child(new DeleteNotesDialog())
|
||||
.child(new InfoDialog())
|
||||
.child(new ConfirmDialog())
|
||||
.child(new PromptDialog())
|
||||
.child(new IncorrectCpuArchDialog())
|
||||
.child(<BulkActionsDialog />)
|
||||
.child(<AboutDialog />)
|
||||
.child(<HelpDialog />)
|
||||
.child(<RecentChangesDialog />)
|
||||
.child(<BranchPrefixDialog />)
|
||||
.child(<SortChildNotesDialog />)
|
||||
.child(<IncludeNoteDialog />)
|
||||
.child(<NoteTypeChooserDialog />)
|
||||
.child(<JumpToNoteDialog />)
|
||||
.child(<AddLinkDialog />)
|
||||
.child(<CloneToDialog />)
|
||||
.child(<MoveToDialog />)
|
||||
.child(<ImportDialog />)
|
||||
.child(<ExportDialog />)
|
||||
.child(<MarkdownImportDialog />)
|
||||
.child(<ProtectedSessionPasswordDialog />)
|
||||
.child(<RevisionsDialog />)
|
||||
.child(<DeleteNotesDialog />)
|
||||
.child(<InfoDialog />)
|
||||
.child(<ConfirmDialog />)
|
||||
.child(<PromptDialog />)
|
||||
.child(<IncorrectCpuArchDialog />)
|
||||
.child(new PopupEditorDialog()
|
||||
.child(new FlexContainer("row")
|
||||
.class("title-row")
|
||||
.css("align-items", "center")
|
||||
.cssBlock(".title-row > * { margin: 5px; }")
|
||||
.child(new NoteIconWidget())
|
||||
.child(new NoteTitleWidget()))
|
||||
.child(new ClassicEditorToolbar())
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />))
|
||||
.child(<PopupEditorFormattingToolbar />)
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(new NoteDetailWidget())
|
||||
.child(new NoteListWidget(true)))
|
||||
.child(new CallToActionDialog());
|
||||
.child(<CallToActionDialog />);
|
||||
}
|
||||
@@ -3,30 +3,26 @@ import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import ToggleSidebarButtonWidget from "../widgets/mobile_widgets/toggle_sidebar_button.js";
|
||||
import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
||||
import EditButton from "../widgets/floating_buttons/edit_button.js";
|
||||
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
||||
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
|
||||
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
|
||||
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
|
||||
import NoteListWidget from "../widgets/note_list.js";
|
||||
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
|
||||
import LauncherContainer from "../widgets/containers/launcher_container.js";
|
||||
import RootContainer from "../widgets/containers/root_container.js";
|
||||
import SharedInfoWidget from "../widgets/shared_info.js";
|
||||
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
|
||||
import type AppContext from "../components/app_context.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
|
||||
import MobileEditorToolbar from "../widgets/ribbon_widgets/mobile_editor_toolbar.js";
|
||||
import MobileEditorToolbar from "../widgets/type_widgets/ckeditor/mobile_editor_toolbar.js";
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
import CloseZenButton from "../widgets/close_zen_button.js";
|
||||
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
|
||||
import { useNoteContext } from "../widgets/react/hooks.jsx";
|
||||
import FloatingButtons from "../widgets/FloatingButtons.jsx";
|
||||
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
||||
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
|
||||
import CloseZenModeButton from "../widgets/close_zen_button.js";
|
||||
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||
|
||||
const MOBILE_CSS = `
|
||||
<style>
|
||||
@@ -143,20 +139,12 @@ export default class MobileLayout {
|
||||
.contentSized()
|
||||
.css("font-size", "larger")
|
||||
.css("align-items", "center")
|
||||
.child(new ToggleSidebarButtonWidget().contentSized())
|
||||
.child(new NoteTitleWidget().contentSized().css("position", "relative").css("padding-left", "0.5em"))
|
||||
.child(new MobileDetailMenuWidget(true).contentSized())
|
||||
)
|
||||
.child(new SharedInfoWidget())
|
||||
.child(
|
||||
new FloatingButtons()
|
||||
.child(new RefreshButton())
|
||||
.child(new EditButton())
|
||||
.child(new RelationMapButtons())
|
||||
.child(new SvgExportButton())
|
||||
.child(new BacklinksWidget())
|
||||
.child(new HideFloatingButtonsButton())
|
||||
.child(<ToggleSidebarButton />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(<MobileDetailMenu />)
|
||||
)
|
||||
.child(<SharedInfoWidget />)
|
||||
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(
|
||||
new ScrollingContainer()
|
||||
@@ -164,9 +152,9 @@ export default class MobileLayout {
|
||||
.contentSized()
|
||||
.child(new NoteDetailWidget())
|
||||
.child(new NoteListWidget(false))
|
||||
.child(new FilePropertiesWidget().css("font-size", "smaller"))
|
||||
.child(<FilePropertiesWrapper />)
|
||||
)
|
||||
.child(new MobileEditorToolbar())
|
||||
.child(<MobileEditorToolbar />)
|
||||
)
|
||||
)
|
||||
.child(
|
||||
@@ -174,10 +162,25 @@ export default class MobileLayout {
|
||||
.contentSized()
|
||||
.id("mobile-bottom-bar")
|
||||
.child(new TabRowWidget().css("height", "40px"))
|
||||
.child(new FlexContainer("row").class("horizontal").css("height", "53px").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true)).id("launcher-pane"))
|
||||
.child(new FlexContainer("row")
|
||||
.class("horizontal")
|
||||
.css("height", "53px")
|
||||
.child(new LauncherContainer(true))
|
||||
.child(<GlobalMenuWidget isHorizontalLayout />)
|
||||
.id("launcher-pane"))
|
||||
)
|
||||
.child(new CloseZenButton());
|
||||
.child(<CloseZenModeButton />);
|
||||
applyModals(rootContainer);
|
||||
return rootContainer;
|
||||
}
|
||||
}
|
||||
|
||||
function FilePropertiesWrapper() {
|
||||
const { note } = useNoteContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{note?.type === "file" && <FilePropertiesTab note={note} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import server from "./server.js";
|
||||
import froca from "./froca.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { AttributeRow } from "./load_results.js";
|
||||
import { AttributeType } from "@triliumnext/commons";
|
||||
|
||||
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
|
||||
await server.put(`notes/${noteId}/attribute`, {
|
||||
@@ -25,6 +26,14 @@ async function removeAttributeById(noteId: string, attributeId: string) {
|
||||
await server.remove(`notes/${noteId}/attributes/${attributeId}`);
|
||||
}
|
||||
|
||||
export async function removeOwnedAttributesByNameOrType(note: FNote, type: AttributeType, name: string) {
|
||||
for (const attr of note.getOwnedAttributes()) {
|
||||
if (attr.type === type && attr.name === name) {
|
||||
await server.remove(`notes/${note.noteId}/attributes/${attr.attributeId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a label identified by its name from the given note, if it exists. Note that the label must be owned, i.e.
|
||||
* it will not remove inherited attributes.
|
||||
@@ -52,7 +61,7 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
|
||||
* @param value the value of the attribute to set.
|
||||
*/
|
||||
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
|
||||
if (value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
// Create or update the attribute.
|
||||
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@ import type FNote from "../entities/fnote.js";
|
||||
import toast from "./toast.js";
|
||||
import { BulkAction } from "@triliumnext/commons";
|
||||
|
||||
const ACTION_GROUPS = [
|
||||
export const ACTION_GROUPS = [
|
||||
{
|
||||
title: t("bulk_actions.labels"),
|
||||
actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from "./i18n.js";
|
||||
import toastService, { showError } from "./toast.js";
|
||||
|
||||
function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
|
||||
export function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
|
||||
try {
|
||||
$imageWrapper.attr("contenteditable", "true");
|
||||
selectImage($imageWrapper.get(0));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { byBookType, byNoteType } from "./help_button.js";
|
||||
import { byBookType, byNoteType } from "./in_app_help.js";
|
||||
import fs from "fs";
|
||||
import type { HiddenSubtreeItem } from "@triliumnext/commons";
|
||||
import path from "path";
|
||||
@@ -25,7 +25,7 @@ describe("Help button", () => {
|
||||
...Object.values(byBookType)
|
||||
].filter((noteId) => noteId) as string[];
|
||||
|
||||
const metaPath = path.resolve(path.join(__dirname, "../../../../server/src/assets/doc_notes/en/User Guide/!!!meta.json"));
|
||||
const metaPath = path.resolve(path.join(__dirname, "../../../server/src/assets/doc_notes/en/User Guide/!!!meta.json"));
|
||||
const meta: HiddenSubtreeItem[] = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
||||
const allNoteIds = new Set(getNoteIds(meta));
|
||||
|
||||
43
apps/client/src/services/in_app_help.ts
Normal file
43
apps/client/src/services/in_app_help.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import { ViewTypeOptions } from "./note_list_renderer";
|
||||
import FNote from "../entities/fnote";
|
||||
|
||||
export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
canvas: null,
|
||||
code: null,
|
||||
contentWidget: null,
|
||||
doc: null,
|
||||
file: null,
|
||||
image: null,
|
||||
launcher: null,
|
||||
mermaid: null,
|
||||
mindMap: null,
|
||||
noteMap: null,
|
||||
relationMap: null,
|
||||
render: null,
|
||||
search: null,
|
||||
text: null,
|
||||
webView: null,
|
||||
aiChat: null
|
||||
};
|
||||
|
||||
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
list: "mULW0Q3VojwY",
|
||||
grid: "8QqnMzx393bx",
|
||||
calendar: "xWbu3jpNWapp",
|
||||
table: "2FvYrpmOXm29",
|
||||
geoMap: "81SGnPGMk7Xc",
|
||||
board: "CtBQqbwXDx1w"
|
||||
};
|
||||
|
||||
export function getHelpUrlForNote(note: FNote | null | undefined) {
|
||||
if (note && note.type !== "book" && byNoteType[note.type]) {
|
||||
return byNoteType[note.type];
|
||||
} else if (note?.hasLabel("calendarRoot")) {
|
||||
return "l0tKav7yLHGF";
|
||||
} else if (note?.hasLabel("textSnippet")) {
|
||||
return "pwc194wlRzcH";
|
||||
} else if (note && note.type === "book") {
|
||||
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,10 @@ async function getAction(actionName: string, silent = false) {
|
||||
return action;
|
||||
}
|
||||
|
||||
export function getActionSync(actionName: string) {
|
||||
return keyboardActionRepo[actionName];
|
||||
}
|
||||
|
||||
function updateDisplayedShortcuts($container: JQuery<HTMLElement>) {
|
||||
//@ts-ignore
|
||||
//TODO: each() does not support async callbacks.
|
||||
|
||||
@@ -35,7 +35,7 @@ function download(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFileNote(noteId: string) {
|
||||
export function downloadFileNote(noteId: string) {
|
||||
const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache
|
||||
|
||||
download(url);
|
||||
@@ -163,7 +163,7 @@ async function openExternally(type: string, entityId: string, mime: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime);
|
||||
export const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime);
|
||||
const openAttachmentExternally = async (attachmentId: string, mime: string) => await openExternally("attachments", attachmentId, mime);
|
||||
|
||||
function getHost() {
|
||||
|
||||
@@ -218,7 +218,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
|
||||
if (utils.isElectron()) {
|
||||
const ipc = utils.dynamicRequire("electron").ipcRenderer;
|
||||
|
||||
ipc.on("server-response", async (event: string, arg: Arg) => {
|
||||
ipc.on("server-response", async (_, arg: Arg) => {
|
||||
if (arg.statusCode >= 200 && arg.statusCode < 300) {
|
||||
handleSuccessfulResponse(arg);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { ViewScope } from "./link.js";
|
||||
import FNote from "../entities/fnote";
|
||||
|
||||
const SVG_MIME = "image/svg+xml";
|
||||
|
||||
@@ -148,7 +149,7 @@ export function isElectron() {
|
||||
return !!(window && window.process && window.process.type);
|
||||
}
|
||||
|
||||
function isMac() {
|
||||
export function isMac() {
|
||||
return navigator.platform.indexOf("Mac") > -1;
|
||||
}
|
||||
|
||||
@@ -185,7 +186,11 @@ export function escapeQuotes(value: string) {
|
||||
return value.replaceAll('"', """);
|
||||
}
|
||||
|
||||
function formatSize(size: number) {
|
||||
export function formatSize(size: number | null | undefined) {
|
||||
if (size === null || size === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size = Math.max(Math.round(size / 1024), 1);
|
||||
|
||||
if (size < 1024) {
|
||||
@@ -292,7 +297,7 @@ function isHtmlEmpty(html: string) {
|
||||
);
|
||||
}
|
||||
|
||||
async function clearBrowserCache() {
|
||||
export async function clearBrowserCache() {
|
||||
if (isElectron()) {
|
||||
const win = dynamicRequire("@electron/remote").getCurrentWindow();
|
||||
await win.webContents.session.clearCache();
|
||||
@@ -306,7 +311,13 @@ function copySelectionToClipboard() {
|
||||
}
|
||||
}
|
||||
|
||||
export function dynamicRequire(moduleName: string) {
|
||||
type dynamicRequireMappings = {
|
||||
"@electron/remote": typeof import("@electron/remote"),
|
||||
"electron": typeof import("electron"),
|
||||
"child_process": typeof import("child_process")
|
||||
};
|
||||
|
||||
export function dynamicRequire<T extends keyof dynamicRequireMappings>(moduleName: T): Awaited<dynamicRequireMappings[T]>{
|
||||
if (typeof __non_webpack_require__ !== "undefined") {
|
||||
return __non_webpack_require__(moduleName);
|
||||
} else {
|
||||
@@ -570,8 +581,7 @@ function copyHtmlToClipboard(content: string) {
|
||||
document.removeEventListener("copy", listener);
|
||||
}
|
||||
|
||||
// TODO: Set to FNote once the file is ported.
|
||||
function createImageSrcUrl(note: { noteId: string; title: string }) {
|
||||
export function createImageSrcUrl(note: FNote) {
|
||||
return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`;
|
||||
}
|
||||
|
||||
@@ -740,7 +750,7 @@ function isUpdateAvailable(latestVersion: string | null | undefined, currentVers
|
||||
return compareVersions(latestVersion, currentVersion) > 0;
|
||||
}
|
||||
|
||||
function isLaunchBarConfig(noteId: string) {
|
||||
export function isLaunchBarConfig(noteId: string) {
|
||||
return ["_lbRoot", "_lbAvailableLaunchers", "_lbVisibleLaunchers", "_lbMobileRoot", "_lbMobileAvailableLaunchers", "_lbMobileVisibleLaunchers"].includes(noteId);
|
||||
}
|
||||
|
||||
@@ -788,6 +798,38 @@ export function arrayEqual<T>(a: T[], b: T[]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
type Indexed<T extends object> = T & { index: number };
|
||||
|
||||
/**
|
||||
* Given an object array, alters every object in the array to have an index field assigned to it.
|
||||
*
|
||||
* @param items the objects to be numbered.
|
||||
* @returns the same object for convenience, with the type changed to indicate the new index field.
|
||||
*/
|
||||
export function numberObjectsInPlace<T extends object>(items: T[]): Indexed<T>[] {
|
||||
let index = 0;
|
||||
for (const item of items) {
|
||||
(item as Indexed<T>).index = index++;
|
||||
}
|
||||
return items as Indexed<T>[];
|
||||
}
|
||||
|
||||
export function mapToKeyValueArray<K extends string | number | symbol, V>(map: Record<K, V>) {
|
||||
const values: { key: K, value: V }[] = [];
|
||||
for (const [ key, value ] of Object.entries(map)) {
|
||||
values.push({ key: key as K, value: value as V });
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
export function getErrorMessage(e: unknown) {
|
||||
if (e && typeof e === "object" && "message" in e && typeof e.message === "string") {
|
||||
return e.message;
|
||||
} else {
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
reloadFrontendApp,
|
||||
restartDesktopApp,
|
||||
|
||||
@@ -176,6 +176,11 @@ label.tn-checkbox + label.tn-checkbox {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
label.tn-radio input[type="radio"],
|
||||
label.tn-checkbox input[type="checkbox"] {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
#left-pane input,
|
||||
#left-pane select,
|
||||
#left-pane textarea {
|
||||
@@ -437,14 +442,20 @@ body #context-menu-container .dropdown-item > span {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdown-menu kbd {
|
||||
.dropdown-item span.keyboard-shortcut {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dropdown-menu kbd {
|
||||
color: var(--muted-text-color);
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
padding-bottom: 0;
|
||||
padding: 0;
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dropdown-item,
|
||||
@@ -977,6 +988,11 @@ div[data-notify="container"] {
|
||||
font-family: var(--monospace-font-family);
|
||||
}
|
||||
|
||||
svg.ck-icon .note-icon {
|
||||
color: var(--main-text-color);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ck-content {
|
||||
--ck-content-font-family: var(--detail-font-family);
|
||||
--ck-content-font-size: 1.1em;
|
||||
@@ -2344,14 +2360,6 @@ footer.webview-footer button {
|
||||
padding: 1px 10px 1px 10px;
|
||||
}
|
||||
|
||||
/* Search result highlighting */
|
||||
.search-result-title b,
|
||||
.search-result-content b,
|
||||
.search-result-attributes b {
|
||||
font-weight: 900;
|
||||
color: var(--admonition-warning-accent-color);
|
||||
}
|
||||
|
||||
/* Customized icons */
|
||||
|
||||
.bx-tn-toc::before {
|
||||
|
||||
@@ -121,6 +121,8 @@
|
||||
--quick-search-focus-border: #80808095;
|
||||
--quick-search-focus-background: #ffffff1f;
|
||||
--quick-search-focus-color: white;
|
||||
--quick-search-result-content-background: #0000004d;
|
||||
--quick-search-result-highlight-color: #a4d995;
|
||||
|
||||
--left-pane-collapsed-border-color: #0009;
|
||||
--left-pane-background-color: #1f1f1f;
|
||||
|
||||
@@ -115,6 +115,8 @@
|
||||
--quick-search-focus-border: #00000029;
|
||||
--quick-search-focus-background: #ffffff80;
|
||||
--quick-search-focus-color: #000;
|
||||
--quick-search-result-content-background: #00000017;
|
||||
--quick-search-result-highlight-color: #c65050;
|
||||
|
||||
--left-pane-collapsed-border-color: #0000000d;
|
||||
--left-pane-background-color: #f2f2f2;
|
||||
|
||||
@@ -197,13 +197,17 @@ html body .dropdown-item[disabled] {
|
||||
|
||||
/* Menu item keyboard shortcut */
|
||||
.dropdown-item kbd {
|
||||
margin-left: 16px;
|
||||
font-family: unset !important;
|
||||
font-size: unset !important;
|
||||
color: var(--menu-item-keyboard-shortcut-color) !important;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.dropdown-item span.keyboard-shortcut {
|
||||
color: var(--menu-item-keyboard-shortcut-color) !important;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
position: relative;
|
||||
border-color: transparent !important;
|
||||
|
||||
@@ -455,6 +455,7 @@ optgroup {
|
||||
left: 0;
|
||||
width: var(--box-size);
|
||||
height: 100%;
|
||||
margin: unset;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
background: var(--background) !important;
|
||||
color: var(--color) !important;
|
||||
line-height: unset;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.sql-table-schemas-widget .sql-table-schemas button:hover,
|
||||
@@ -106,18 +105,6 @@
|
||||
--color: var(--main-text-color);
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
|
||||
.tooltip .table-schema {
|
||||
font-family: var(--monospace-font-family);
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
/* Data type */
|
||||
.tooltip .table-schema td:nth-child(2) {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE MAP
|
||||
*/
|
||||
|
||||
@@ -330,7 +330,6 @@ body.layout-horizontal > .horizontal {
|
||||
*/
|
||||
|
||||
.calendar-dropdown-widget {
|
||||
width: unset !important;
|
||||
padding: 12px;
|
||||
color: var(--calendar-color);
|
||||
user-select: none;
|
||||
@@ -587,6 +586,10 @@ div.quick-search .search-button.show {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.quick-search .quick-search-item-icon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
/* Note title */
|
||||
.quick-search .dropdown-menu .dropdown-item > a {
|
||||
color: var(--menu-text-color);
|
||||
@@ -605,6 +608,25 @@ div.quick-search .search-button.show {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Note content snippet */
|
||||
:root .quick-search .search-result-content {
|
||||
background-color: var(--quick-search-result-content-background);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Highlighted search terms */
|
||||
:root .quick-search .search-result-title b,
|
||||
:root .quick-search .search-result-content b,
|
||||
:root .quick-search .search-result-attributes b {
|
||||
color: var(--quick-search-result-highlight-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Divider line */
|
||||
.quick-search .dropdown-item::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* TREE PANE
|
||||
*/
|
||||
@@ -1375,7 +1397,7 @@ div.floating-buttons-children .floating-button:active {
|
||||
}
|
||||
|
||||
/* The first visible floating button */
|
||||
div.floating-buttons-children > *:nth-child(1 of .visible) {
|
||||
div.floating-buttons-children > *:first-child {
|
||||
--border-radius: var(--border-radius-size) 0 0 var(--border-radius-size);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
@@ -1477,13 +1499,6 @@ div.floating-buttons-children .close-floating-buttons:has(.close-floating-button
|
||||
padding-inline-start: 8px;
|
||||
}
|
||||
|
||||
/* Copy image reference */
|
||||
|
||||
.floating-buttons .copy-image-reference-button .hidden-image-copy {
|
||||
/* Take out of the the hidden image from flexbox to prevent the layout being affected */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Code, relation map buttons */
|
||||
|
||||
.floating-buttons .code-buttons-widget,
|
||||
|
||||
@@ -732,7 +732,8 @@
|
||||
"note_type": "笔记类型",
|
||||
"editable": "可编辑",
|
||||
"basic_properties": "基本属性",
|
||||
"language": "语言"
|
||||
"language": "语言",
|
||||
"configure_code_notes": "配置代码注释..."
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "视图类型",
|
||||
@@ -848,7 +849,7 @@
|
||||
"debug": "调试",
|
||||
"debug_description": "调试将打印额外的调试信息到控制台,以帮助调试复杂查询",
|
||||
"action": "操作",
|
||||
"search_button": "搜索 <kbd>回车</kbd>",
|
||||
"search_button": "搜索",
|
||||
"search_execute": "搜索并执行操作",
|
||||
"save_to_note": "保存到笔记",
|
||||
"search_parameters": "搜索参数",
|
||||
@@ -1439,9 +1440,9 @@
|
||||
"open-in-popup": "快速编辑"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "此笔记已公开分享于",
|
||||
"shared_locally": "此笔记已在本地分享于",
|
||||
"help_link": "访问 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a> 获取帮助。"
|
||||
"help_link": "访问 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a> 获取帮助。",
|
||||
"shared_publicly": "该笔记已在 {{- link}} 上公开分享。",
|
||||
"shared_locally": "此笔记在本地通过 {{- link}} 进行共享。"
|
||||
},
|
||||
"note_types": {
|
||||
"text": "文本",
|
||||
@@ -1519,7 +1520,8 @@
|
||||
"hoist-this-note-workspace": "聚焦此笔记(工作区)",
|
||||
"refresh-saved-search-results": "刷新保存的搜索结果",
|
||||
"create-child-note": "创建子笔记",
|
||||
"unhoist": "取消聚焦"
|
||||
"unhoist": "取消聚焦",
|
||||
"toggle-sidebar": "切换侧边栏"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "保持此窗口置顶"
|
||||
|
||||
@@ -729,7 +729,8 @@
|
||||
"note_type": "Notiztyp",
|
||||
"editable": "Bearbeitbar",
|
||||
"basic_properties": "Grundlegende Eigenschaften",
|
||||
"language": "Sprache"
|
||||
"language": "Sprache",
|
||||
"configure_code_notes": "Code-Notizen konfigurieren..."
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "Ansichtstyp",
|
||||
@@ -845,7 +846,7 @@
|
||||
"debug": "debuggen",
|
||||
"debug_description": "Debug gibt zusätzliche Debuginformationen in die Konsole aus, um das Debuggen komplexer Abfragen zu erleichtern",
|
||||
"action": "Aktion",
|
||||
"search_button": "Suchen <kbd>Eingabetaste</kbd>",
|
||||
"search_button": "Suchen",
|
||||
"search_execute": "Aktionen suchen und ausführen",
|
||||
"save_to_note": "Als Notiz speichern",
|
||||
"search_parameters": "Suchparameter",
|
||||
@@ -1227,7 +1228,15 @@
|
||||
"language": "Sprache",
|
||||
"first-day-of-the-week": "Erster Tag der Woche",
|
||||
"sunday": "Sonntag",
|
||||
"monday": "Montag"
|
||||
"monday": "Montag",
|
||||
"first-week-of-the-year": "Erste Woche des Jahres",
|
||||
"first-week-contains-first-day": "Erste Woche enthält den ersten Tag des Jahres",
|
||||
"first-week-contains-first-thursday": "Erste Woche enthält den ersten Donnerstag des Jahres",
|
||||
"first-week-has-minimum-days": "Erste Woche hat Mindestanzahl an Tagen",
|
||||
"min-days-in-first-week": "Mindestanzahl an Tagen in erster Woche",
|
||||
"first-week-info": "Die erste Woche, die den ersten Donnerstag des Jahres enthält, basiert auf dem Standard <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a>.",
|
||||
"first-week-warning": "Das Ändern der Optionen für die erste Woche kann zu Duplikaten mit bestehenden Wochen-Notizen führen. Bestehende Wochen-Notizen werden nicht entsprechend aktualisiert.",
|
||||
"formatting-locale": "Datums- und Zahlenformat"
|
||||
},
|
||||
"backup": {
|
||||
"automatic_backup": "Automatische Sicherung",
|
||||
@@ -1330,7 +1339,8 @@
|
||||
"test_title": "Synchronisierungstest",
|
||||
"test_description": "Dadurch werden die Verbindung und der Handshake zum Synchronisierungsserver getestet. Wenn der Synchronisierungsserver nicht initialisiert ist, wird er dadurch für die Synchronisierung mit dem lokalen Dokument eingerichtet.",
|
||||
"test_button": "Teste die Synchronisierung",
|
||||
"handshake_failed": "Handshake des Synchronisierungsservers fehlgeschlagen, Fehler: {{message}}"
|
||||
"handshake_failed": "Handshake des Synchronisierungsservers fehlgeschlagen, Fehler: {{message}}",
|
||||
"timeout_unit": "Millisekunden"
|
||||
},
|
||||
"api_log": {
|
||||
"close": "Schließen"
|
||||
@@ -1371,7 +1381,7 @@
|
||||
"unhoist-note": "Notiz-Fokus aufheben",
|
||||
"edit-branch-prefix": "Zweig-Präfix bearbeiten",
|
||||
"advanced": "Erweitert",
|
||||
"expand-subtree": "Notizbaum ausklappen",
|
||||
"expand-subtree": "Unterzweig aufklappen",
|
||||
"collapse-subtree": "Notizbaum einklappen",
|
||||
"sort-by": "Sortieren nach...",
|
||||
"recent-changes-in-subtree": "Kürzliche Änderungen im Notizbaum",
|
||||
@@ -1390,11 +1400,12 @@
|
||||
"import-into-note": "In Notiz importieren",
|
||||
"apply-bulk-actions": "Massenaktionen anwenden",
|
||||
"converted-to-attachments": "{{count}} Notizen wurden als Anhang konvertiert.",
|
||||
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?"
|
||||
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?",
|
||||
"open-in-popup": "Schnellbearbeitung"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf",
|
||||
"shared_locally": "Diese Notiz ist lokal geteilt auf",
|
||||
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
|
||||
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
|
||||
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||
},
|
||||
"note_types": {
|
||||
@@ -1415,7 +1426,12 @@
|
||||
"widget": "Widget",
|
||||
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
|
||||
"geo-map": "Geo-Karte",
|
||||
"beta-feature": "Beta"
|
||||
"beta-feature": "Beta",
|
||||
"book": "Sammlung",
|
||||
"ai-chat": "KI Chat",
|
||||
"task-list": "Aufgabenliste",
|
||||
"new-feature": "Neu",
|
||||
"collections": "Sammlungen"
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Notiz schützen",
|
||||
@@ -1468,7 +1484,8 @@
|
||||
"hoist-this-note-workspace": "Diese Notiz fokussieren (Arbeitsbereich)",
|
||||
"refresh-saved-search-results": "Gespeicherte Suchergebnisse aktualisieren",
|
||||
"create-child-note": "Unternotiz anlegen",
|
||||
"unhoist": "Entfokussieren"
|
||||
"unhoist": "Fokus verlassen",
|
||||
"toggle-sidebar": "Seitenleiste ein-/ausblenden"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Dieses Fenster immer oben halten"
|
||||
@@ -1525,7 +1542,9 @@
|
||||
},
|
||||
"clipboard": {
|
||||
"cut": "Notiz(en) wurden in die Zwischenablage ausgeschnitten.",
|
||||
"copied": "Notiz(en) wurden in die Zwischenablage kopiert."
|
||||
"copied": "Notiz(en) wurden in die Zwischenablage kopiert.",
|
||||
"copy_failed": "Speichern in Zwischenablage aufgrund von Berechtigungsproblemen gescheitert.",
|
||||
"copy_success": "In Zwischenablage kopiert."
|
||||
},
|
||||
"entrypoints": {
|
||||
"note-revision-created": "Notizrevision wurde erstellt.",
|
||||
@@ -1569,13 +1588,15 @@
|
||||
},
|
||||
"highlighting": {
|
||||
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
|
||||
"color-scheme": "Farbschema"
|
||||
"color-scheme": "Farbschema",
|
||||
"title": "Code-Blöcke"
|
||||
},
|
||||
"code_block": {
|
||||
"word_wrapping": "Wortumbruch",
|
||||
"theme_none": "Keine Syntax-Hervorhebung",
|
||||
"theme_group_light": "Helle Themen",
|
||||
"theme_group_dark": "Dunkle Themen"
|
||||
"theme_group_dark": "Dunkle Themen",
|
||||
"copy_title": "Kopiere in Zwischenablage"
|
||||
},
|
||||
"classic_editor_toolbar": {
|
||||
"title": "Format"
|
||||
@@ -1613,7 +1634,8 @@
|
||||
"link_context_menu": {
|
||||
"open_note_in_new_tab": "Notiz in neuen Tab öffnen",
|
||||
"open_note_in_new_split": "Notiz in neuen geteilten Tab öffnen",
|
||||
"open_note_in_new_window": "Notiz in neuen Fenster öffnen"
|
||||
"open_note_in_new_window": "Notiz in neuen Fenster öffnen",
|
||||
"open_note_in_popup": "Schnellbearbeitung"
|
||||
},
|
||||
"electron_integration": {
|
||||
"desktop-application": "Desktop Anwendung",
|
||||
@@ -1633,7 +1655,8 @@
|
||||
"full-text-search": "Volltextsuche"
|
||||
},
|
||||
"note_tooltip": {
|
||||
"note-has-been-deleted": "Notiz wurde gelöscht."
|
||||
"note-has-been-deleted": "Notiz wurde gelöscht.",
|
||||
"quick-edit": "Schnellbearbeitung"
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Neue Unternotiz anlegen und zur Karte hinzufügen",
|
||||
@@ -1642,7 +1665,8 @@
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Ort öffnen",
|
||||
"remove-from-map": "Von Karte entfernen"
|
||||
"remove-from-map": "Von Karte entfernen",
|
||||
"add-note": "Markierung an dieser Position erstellen"
|
||||
},
|
||||
"help-button": {
|
||||
"title": "Relevante Hilfeseite öffnen"
|
||||
@@ -1654,10 +1678,12 @@
|
||||
"days": "Tage"
|
||||
},
|
||||
"time_selector": {
|
||||
"invalid_input": "Die eingegebene Zeit ist keine valide Zahl."
|
||||
"invalid_input": "Die eingegebene Zeit ist keine valide Zahl.",
|
||||
"minimum_input": "Die eingegebene Zeit muss mindestens {{minimumSeconds}} Sekunden entsprechen."
|
||||
},
|
||||
"modal": {
|
||||
"close": "Schließen"
|
||||
"close": "Schließen",
|
||||
"help_title": "Zeige mehr Informationen zu diesem Fenster"
|
||||
},
|
||||
"ai_llm": {
|
||||
"n_notes_queued": "{{ count }} Notiz zur Indizierung vorgemerkt",
|
||||
@@ -1823,5 +1849,180 @@
|
||||
"description": "Passe das Format des Datums und der Uhrzeit an, die über <shortcut /> oder die Symbolleiste eingefügt werden. Die verfügbaren Format-Tokens sind unter <doc>Day.js docs</doc> zu finden.",
|
||||
"format_string": "Format Zeichenfolge:",
|
||||
"formatted_time": "Formatiertes Datum/Uhrzeit:"
|
||||
},
|
||||
"multi_factor_authentication": {
|
||||
"title": "Multi-Faktor-Authentifizierung",
|
||||
"description": "Die Multi-Faktor-Authentifizierung (MFA) bietet Ihrem Konto eine zusätzliche Sicherheitsebene. Anstatt sich lediglich mit einem Passwort anzumelden, müssen bei der MFA ein oder mehrere zusätzliche Nachweise erbracht werden, um die Identität zu bestätigen. Auf diese Weise kann selbst bei Bekanntwerden des Passworts, ohne die zweite Information nicht auf Ihr Konto zugegriffen werden. Das ist so, als würden Sie ein zusätzliches Schloss an einer Tür anbringen, wodurch es für andere viel schwieriger wird, einzubrechen.<br><br>Befolgen Sie bitte die nachstehenden Anweisungen, um MFA zu aktivieren. Wenn Sie die Konfiguration nicht korrekt vornehmen, erfolgt die Anmeldung weiterhin nur mit dem Passwort.",
|
||||
"mfa_enabled": "Aktiviere Multi-Faktor-Authentifizierung",
|
||||
"mfa_method": "MFA Methode",
|
||||
"electron_disabled": "Multi-Faktor-Authentifizierung wird aktuell nicht in der Desktop-Version unterstützt.",
|
||||
"totp_title": "Zeitbasiertes Einmalpasswort (TOTP)",
|
||||
"totp_description": "TOTP (Zeitbasiertes Einmalpasswort) ist eine Sicherheitsfunktion, die einen einzigartigen, temporären Code generiert, der sich alle 30 Sekunden ändert. Sie verwenden diesen Code zusammen mit Ihrem Passwort, um sich bei Ihrem Konto anzumelden, wodurch es für andere Personen wesentlich schwieriger wird, darauf unbefugt zuzugreifen.",
|
||||
"totp_secret_title": "Generiere TOTP Geheimnis",
|
||||
"totp_secret_generate": "Generiere TOTP Geheimnis",
|
||||
"totp_secret_regenerate": "TOTP-Geheimnis neu generieren",
|
||||
"no_totp_secret_warning": "Um TOTP zu aktivieren, muss zunächst ein TOTP Geheimnis generiert werden.",
|
||||
"totp_secret_description_warning": "Nach der Generierung des TOTP Geheimnisses ist eine Neuanmeldung mit dem TOTP Geheimnis erforderlich.",
|
||||
"totp_secret_generated": "TOTP Geheimnis generiert",
|
||||
"totp_secret_warning": "Bitte speichere das TOTP Geheimnis an einem sicheren Ort. Es wird nicht noch einmal angezeigt.",
|
||||
"totp_secret_regenerate_confirm": "Möchten Sie das TOTP-Geheimnis wirklich neu generieren? Dadurch werden das bisherige TOTP-Geheimnis und alle vorhandenen Wiederherstellungscodes ungültig.",
|
||||
"recovery_keys_title": "Einmalige Wiederherstellungsschlüssel",
|
||||
"recovery_keys_description": "Einmalige Wiederherstellungsschlüssel werden verwendet, um sich anzumelden, falls Sie keinen Zugriff auf Ihre Authentifizierungscodes haben.",
|
||||
"recovery_keys_description_warning": "Wiederherstellungsschlüssel werden nach dem Verlassen der Seite nicht erneut angezeigt. Bewahren Sie sie an einem sicheren Ort auf.<br>Wiederherstellungsschlüssel können nach ihrer Verwendung nicht erneut verwendet werden.",
|
||||
"recovery_keys_error": "Fehler beim Generieren der Wiederherstellungscodes",
|
||||
"recovery_keys_no_key_set": "Keine Wiederherstellungscodes eingerichtet",
|
||||
"recovery_keys_generate": "Generiere Wiederherstellungscodes",
|
||||
"recovery_keys_regenerate": "Wiederherstellungscodes erneut generieren",
|
||||
"recovery_keys_used": "Verwendet: {{date}}",
|
||||
"recovery_keys_unused": "Wiederherstellungscode {{index}} ist unbenutzt",
|
||||
"oauth_title": "OAuth/OpenID",
|
||||
"oauth_description": "OpenID ist ein standardisiertes Verfahren, mit dem Sie sich über ein Konto eines anderen Dienstes, beispielsweise Google, bei Websites anmelden können, um Ihre Identität zu bestätigen. Der Standardaussteller ist Google, Sie können jedoch jeden anderen OpenID-Anbieter auswählen. Weitere Informationen finden Sie <a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">hier</a>. Befolgen Sie diese <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">Anweisungen</a>, um einen OpenID-Dienst über Google einzurichten.",
|
||||
"oauth_description_warning": "Um OAuth/OpenID zu aktivieren, müssen Sie die OAuth/OpenID-Basis-URL, die Client-ID und den Client-Secret in der Datei config.ini festlegen und die Anwendung neu starten. Wenn Sie die Einstellungen über Umgebungsvariablen vornehmen möchten, legen Sie bitte TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID und TRILIUM_OAUTH_CLIENT_SECRET fest.",
|
||||
"oauth_missing_vars": "Fehlende Einstellung: {{variables}}",
|
||||
"oauth_user_account": "Benutzerkonto: ",
|
||||
"oauth_user_email": "Benutzer E-Mail: ",
|
||||
"oauth_user_not_logged_in": "Nicht eingeloggt!"
|
||||
},
|
||||
"share": {
|
||||
"title": "Freigabeeinstellungen",
|
||||
"redirect_bare_domain": "Hauptdomain zur Freigabeseite weiterleiten",
|
||||
"redirect_bare_domain_description": "Anonyme Benutzer zur Freigabeseite weiterleiten, anstatt die Anmeldung anzuzeigen",
|
||||
"show_login_link": "Zeige Anmeldelink im Design der Freigabeseite",
|
||||
"show_login_link_description": "Füge einen Anmeldelink in der Fußzeile der Freigabeseite hinzu",
|
||||
"check_share_root": "Status des Freigabe-Roots prüfen",
|
||||
"share_root_found": "Freigabe-Root-Notiz '{{noteTitle}}' ist bereit",
|
||||
"share_root_not_found": "Keine Notiz mit #shareRoot Label gefunden",
|
||||
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht geteilt"
|
||||
},
|
||||
"tasks": {
|
||||
"due": {
|
||||
"today": "Heute",
|
||||
"tomorrow": "Morgen",
|
||||
"yesterday": "Gestern"
|
||||
}
|
||||
},
|
||||
"content_widget": {
|
||||
"unknown_widget": "Unbekanntes Widget für '{{id}}'."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "Nicht gesetzt",
|
||||
"configure-languages": "Konfiguriere Sprachen..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "Inhaltssprachen",
|
||||
"description": "Wähle eine oder mehrere Sprachen aus, die in der Sprachauswahl im Abschnitt „Grundlegende Eigenschaften“ einer schreibgeschützten oder bearbeitbaren Textnotiz angezeigt werden sollen. Dadurch stehen Funktionen wie Rechtschreibprüfung oder Unterstützung für Rechts-nach-Links-Sprachen zur Verfügung."
|
||||
},
|
||||
"switch_layout_button": {
|
||||
"title_vertical": "Bearbeitungsbereich nach unten verschieben",
|
||||
"title_horizontal": "Bearbeitungsbereich nach links verschieben"
|
||||
},
|
||||
"toggle_read_only_button": {
|
||||
"unlock-editing": "Bearbeitung freischalten",
|
||||
"lock-editing": "Bearbeitung sperren"
|
||||
},
|
||||
"png_export_button": {
|
||||
"button_title": "Exportiere Diagramm als PNG"
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "Das Diagramm konnte als PNG nicht exportiert werden."
|
||||
},
|
||||
"code_theme": {
|
||||
"title": "Aussehen",
|
||||
"word_wrapping": "Zeilenumbruch",
|
||||
"color-scheme": "Farbschema"
|
||||
},
|
||||
"cpu_arch_warning": {
|
||||
"title": "Bitte lade die ARM64-Version herunter",
|
||||
"message_macos": "TriliumNext läuft aktuell über Rosetta 2. Nutzen Sie die Intel-Version (x64) auf einem Apple-Silicon-Mac, wird dadurch die Leistung und Akkulaufzeit deutlich beeinträchtigt.",
|
||||
"message_windows": "TriliumNext läuft momentan in einer Emulation. Verwenden Sie eine Intel-Version (x64) auf einem Windows ARM Gerät, kann dadurch die Leistung und Akkulaufzeit deutlich beeinträchtigt werden.",
|
||||
"recommendation": "Für ein optimales Erlebnis lade bitte die native ARM64-Version von TriliumNext von unserer Release-Seite herunter.",
|
||||
"download_link": "Lade native Version herunter",
|
||||
"continue_anyway": "Trotzdem fortfahren",
|
||||
"dont_show_again": "Zeige diese Warnung nicht erneut"
|
||||
},
|
||||
"editorfeatures": {
|
||||
"title": "Funktionen",
|
||||
"emoji_completion_enabled": "Emoji-Autovervollständigung aktivieren",
|
||||
"note_completion_enabled": "Automatisches Vervollständigen von Notizen aktivieren"
|
||||
},
|
||||
"table_view": {
|
||||
"new-row": "Neue Zeile",
|
||||
"new-column": "Neue Spalte",
|
||||
"sort-column-by": "Sortiere nach '{{title}}'",
|
||||
"sort-column-ascending": "Aufsteigend",
|
||||
"sort-column-descending": "Absteigend",
|
||||
"sort-column-clear": "Sortierung zurücksetzen",
|
||||
"hide-column": "Spalte '{{title}}' ausblenden",
|
||||
"show-hide-columns": "Zeige/verberge Spalten",
|
||||
"row-insert-above": "Zeile oberhalb einfügen",
|
||||
"row-insert-below": "Zeile unterhalb einfügen",
|
||||
"row-insert-child": "Unternotiz einfügen",
|
||||
"add-column-to-the-left": "Spalte links einfügen",
|
||||
"add-column-to-the-right": "Spalte rechts einfügen",
|
||||
"edit-column": "Spalte editieren",
|
||||
"delete_column_confirmation": "Soll diese Spalte wirklich gelöscht werden? Das entsprechende Attribut wird aus allen Notizen entfernt.",
|
||||
"delete-column": "Spalte entfernen",
|
||||
"new-column-label": "Label",
|
||||
"new-column-relation": "Beziehung"
|
||||
},
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "Wochenenden ausblenden",
|
||||
"display-week-numbers": "Zeige Kalenderwoche",
|
||||
"map-style": "Kartenstil:",
|
||||
"max-nesting-depth": "Maximale Verschachtelungstiefe:",
|
||||
"raster": "Raster",
|
||||
"vector_light": "Vektor (Hell)",
|
||||
"vector_dark": "Vektor (Dunkel)",
|
||||
"show-scale": "Zeige Skalierung"
|
||||
},
|
||||
"table_context_menu": {
|
||||
"delete_row": "Zeile entfernen"
|
||||
},
|
||||
"board_view": {
|
||||
"delete-note": "Lösche Notiz",
|
||||
"move-to": "Verschiebe zu",
|
||||
"insert-above": "Oberhalb einfügen",
|
||||
"insert-below": "Unterhalb einfügen",
|
||||
"delete-column": "Spalte entfernen",
|
||||
"delete-column-confirmation": "Soll die Spalte wirklich gelöscht werden? Abhängige Attribute werden auch in den Notizen unter dieser Spalte gelöscht.",
|
||||
"new-item": "Neuer Artikel",
|
||||
"add-column": "Spalte hinzufügen"
|
||||
},
|
||||
"command_palette": {
|
||||
"tree-action-name": "Struktur: {{name}}",
|
||||
"export_note_title": "Notiz exportieren",
|
||||
"export_note_description": "aktuelle Notiz exportieren",
|
||||
"show_attachments_title": "Zeige Anhänge",
|
||||
"show_attachments_description": "Notizanhänge anzeigen",
|
||||
"search_notes_title": "Suche Notiz",
|
||||
"search_notes_description": "Öffne erweiterte Suche",
|
||||
"search_subtree_title": "Im Unterzweig suchen",
|
||||
"search_subtree_description": "Im aktuellen Unterzweig suchen",
|
||||
"search_history_title": "Zeige Suchhistorie",
|
||||
"search_history_description": "Zeige vorherige Suchen",
|
||||
"configure_launch_bar_title": "Startleiste anpassen",
|
||||
"configure_launch_bar_description": "Öffnen Sie die Einstellungen der Startleiste, um Elemente hinzuzufügen oder zu entfernen."
|
||||
},
|
||||
"content_renderer": {
|
||||
"open_externally": "Öffne extern"
|
||||
},
|
||||
"call_to_action": {
|
||||
"next_theme_title": "Teste das neue Trilium Design",
|
||||
"next_theme_message": "Es wird aktuell das alte Design verwendet. Möchten Sie das neue Design ausprobieren?",
|
||||
"next_theme_button": "Teste das neue Design",
|
||||
"background_effects_title": "Hintergrundeffekte sind jetzt zuverlässig nutzbar",
|
||||
"background_effects_message": "Auf Windows-Geräten sind die Hintergrundeffekte nun vollständig stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird. Diese Technik wird auch in anderen Anwendungen wie dem Windows-Explorer eingesetzt.",
|
||||
"background_effects_button": "Aktiviere Hintergrundeffekte",
|
||||
"dismiss": "Ablehnen"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Ähnliche Einstellungen"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Farbschema für Code-Blöcke in Textnotizen",
|
||||
"related_code_notes": "Farbschema für Code-Notizen"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,7 +732,8 @@
|
||||
"note_type": "Note type",
|
||||
"editable": "Editable",
|
||||
"basic_properties": "Basic Properties",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"configure_code_notes": "Configure code notes..."
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "View type",
|
||||
@@ -848,7 +849,7 @@
|
||||
"debug": "debug",
|
||||
"debug_description": "Debug will print extra debugging information into the console to aid in debugging complex queries",
|
||||
"action": "action",
|
||||
"search_button": "Search <kbd>enter</kbd>",
|
||||
"search_button": "Search",
|
||||
"search_execute": "Search & Execute actions",
|
||||
"save_to_note": "Save to note",
|
||||
"search_parameters": "Search Parameters",
|
||||
@@ -1601,8 +1602,8 @@
|
||||
"open-in-popup": "Quick edit"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "This note is shared publicly on",
|
||||
"shared_locally": "This note is shared locally on",
|
||||
"shared_publicly": "This note is shared publicly on {{- link}}.",
|
||||
"shared_locally": "This note is shared locally on {{- link}}.",
|
||||
"help_link": "For help visit <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||
},
|
||||
"note_types": {
|
||||
@@ -1681,7 +1682,8 @@
|
||||
"hoist-this-note-workspace": "Hoist this note (workspace)",
|
||||
"refresh-saved-search-results": "Refresh saved search results",
|
||||
"create-child-note": "Create child note",
|
||||
"unhoist": "Unhoist"
|
||||
"unhoist": "Unhoist",
|
||||
"toggle-sidebar": "Toggle sidebar"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Keep Window on Top"
|
||||
|
||||
@@ -732,7 +732,8 @@
|
||||
"note_type": "Tipo de nota",
|
||||
"editable": "Editable",
|
||||
"basic_properties": "Propiedades básicas",
|
||||
"language": "Idioma"
|
||||
"language": "Idioma",
|
||||
"configure_code_notes": "Configurar notas de código..."
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "Tipo de vista",
|
||||
@@ -848,7 +849,7 @@
|
||||
"debug": "depurar",
|
||||
"debug_description": "La depuración imprimirá información de depuración adicional en la consola para ayudar a depurar consultas complejas",
|
||||
"action": "acción",
|
||||
"search_button": "Buscar <kbd>Enter</kbd>",
|
||||
"search_button": "Buscar",
|
||||
"search_execute": "Buscar y ejecutar acciones",
|
||||
"save_to_note": "Guardar en nota",
|
||||
"search_parameters": "Parámetros de búsqueda",
|
||||
@@ -1253,7 +1254,12 @@
|
||||
"selected_provider": "Proveedor seleccionado",
|
||||
"selected_provider_description": "Elija el proveedor de IA para el chat y características de completado",
|
||||
"select_model": "Seleccionar modelo...",
|
||||
"select_provider": "Seleccionar proveedor..."
|
||||
"select_provider": "Seleccionar proveedor...",
|
||||
"ai_enabled": "Características de IA activadas",
|
||||
"ai_disabled": "Características de IA desactivadas",
|
||||
"no_models_found_online": "No se encontraron modelos. Por favor, comprueba tu clave de API y la configuración.",
|
||||
"no_models_found_ollama": "No se encontraron modelos de Ollama. Por favor, comprueba si Ollama se está ejecutando.",
|
||||
"error_fetching": "Error al obtener los modelos: {{error}}"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"title": "Factor de zoom (solo versión de escritorio)",
|
||||
@@ -1590,8 +1596,8 @@
|
||||
"open-in-popup": "Edición rápida"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Esta nota está compartida públicamente en",
|
||||
"shared_locally": "Esta nota está compartida localmente en",
|
||||
"shared_publicly": "Esta nota está compartida públicamente en {{- link}}",
|
||||
"shared_locally": "Esta nota está compartida localmente en {{- link}}",
|
||||
"help_link": "Para obtener ayuda visite <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||
},
|
||||
"note_types": {
|
||||
@@ -2001,5 +2007,21 @@
|
||||
"background_effects_message": "En los dispositivos Windows, los efectos de fondo ya son totalmente estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás. Esta técnica también se utiliza en otras aplicaciones como el Explorador de Windows.",
|
||||
"background_effects_button": "Activar efectos de fondo",
|
||||
"dismiss": "Desestimar"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Rendimiento",
|
||||
"enable-motion": "Habilitar transiciones y animaciones",
|
||||
"enable-shadows": "Activar sombras",
|
||||
"enable-backdrop-effects": "Habilitar efectos de fondo para menús, ventanas emergentes y paneles"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Configuración relacionada"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Esquema de colores para bloques de código en notas de texto",
|
||||
"related_code_notes": "Esquema de colores para notas de código"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,7 +848,7 @@
|
||||
"debug": "debug",
|
||||
"debug_description": "Debug imprimera des informations supplémentaires dans la console pour faciliter le débogage des requêtes complexes",
|
||||
"action": "action",
|
||||
"search_button": "Recherche <kbd>Entrée</kbd>",
|
||||
"search_button": "Recherche",
|
||||
"search_execute": "Rechercher et exécuter des actions",
|
||||
"save_to_note": "Enregistrer dans la note",
|
||||
"search_parameters": "Paramètres de recherche",
|
||||
@@ -1389,8 +1389,8 @@
|
||||
"convert-to-attachment-confirm": "Êtes-vous sûr de vouloir convertir les notes sélectionnées en pièces jointes de leurs notes parentes ?"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Cette note est partagée publiquement sur",
|
||||
"shared_locally": "Cette note est partagée localement sur",
|
||||
"shared_publicly": "Cette note est partagée publiquement sur {{- link}}",
|
||||
"shared_locally": "Cette note est partagée localement sur {{- link}}",
|
||||
"help_link": "Pour obtenir de l'aide, visitez le <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||
},
|
||||
"note_types": {
|
||||
|
||||
@@ -1,345 +1,353 @@
|
||||
{
|
||||
"about": {
|
||||
"app_version": "Versione dell'app:",
|
||||
"db_version": "Versione DB:",
|
||||
"sync_version": "Versione Sync:",
|
||||
"data_directory": "Cartella dati:",
|
||||
"title": "Informazioni su Trilium Notes",
|
||||
"build_date": "Data della build:",
|
||||
"build_revision": "Revisione della build:",
|
||||
"homepage": "Homepage:"
|
||||
"about": {
|
||||
"app_version": "Versione dell'app:",
|
||||
"db_version": "Versione DB:",
|
||||
"sync_version": "Versione Sync:",
|
||||
"data_directory": "Cartella dati:",
|
||||
"title": "Informazioni su Trilium Notes",
|
||||
"build_date": "Data della build:",
|
||||
"build_revision": "Revisione della build:",
|
||||
"homepage": "Homepage:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Errore critico",
|
||||
"message": "Si è verificato un errore critico che impedisce l'avvio dell'applicazione client:\n\n{{message}}\n\nQuesto è probabilmente causato da un errore di script inaspettato. Prova a avviare l'applicazione in modo sicuro e controlla il problema."
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Errore critico",
|
||||
"message": "Si è verificato un errore critico che impedisce l'avvio dell'applicazione client:\n\n{{message}}\n\nQuesto è probabilmente causato da un errore di script inaspettato. Prova a avviare l'applicazione in modo sicuro e controlla il problema."
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Non si è riusciti a caricare uno script personalizzato",
|
||||
"message": "Lo script della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}"
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "Impossibile inizializzare un widget",
|
||||
"message-custom": "Il widget personalizzato della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}",
|
||||
"message-unknown": "Un widget sconosciuto non è stato inizializzato a causa di:\n\n{{message}}"
|
||||
}
|
||||
"bundle-error": {
|
||||
"title": "Non si è riusciti a caricare uno script personalizzato",
|
||||
"message": "Lo script della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Aggiungi un collegamento",
|
||||
"note": "Nota",
|
||||
"search_note": "cerca una nota per nome",
|
||||
"link_title_mirrors": "il titolo del collegamento rispecchia il titolo della nota corrente",
|
||||
"link_title_arbitrary": "il titolo del collegamento può essere modificato arbitrariamente",
|
||||
"link_title": "Titolo del collegamento",
|
||||
"button_add_link": "Aggiungi il collegamento <kbd>invio</kbd>",
|
||||
"help_on_links": "Aiuto sui collegamenti"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix": "Modifica il prefisso del ramo",
|
||||
"help_on_tree_prefix": "Aiuto sui prefissi dell'Albero",
|
||||
"prefix": "Prefisso: ",
|
||||
"save": "Salva",
|
||||
"branch_prefix_saved": "Il prefisso del ramo è stato salvato."
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Azioni massive",
|
||||
"affected_notes": "Note influenzate",
|
||||
"include_descendants": "Includi i discendenti della nota selezionata",
|
||||
"available_actions": "Azioni disponibili",
|
||||
"chosen_actions": "Azioni scelte",
|
||||
"execute_bulk_actions": "Esegui le azioni massive",
|
||||
"bulk_actions_executed": "Le azioni massive sono state eseguite con successo.",
|
||||
"none_yet": "Ancora nessuna... aggiungi una azione cliccando su una di quelle disponibili sopra.",
|
||||
"labels": "Etichette",
|
||||
"relations": "Relazioni",
|
||||
"notes": "Note",
|
||||
"other": "Altro"
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Clona note in...",
|
||||
"help_on_links": "Aiuto sui collegamenti",
|
||||
"notes_to_clone": "Note da clonare",
|
||||
"target_parent_note": "Nodo padre obiettivo",
|
||||
"search_for_note_by_its_name": "cerca una nota per nome",
|
||||
"cloned_note_prefix_title": "Le note clonate saranno mostrate nell'albero delle note con il dato prefisso",
|
||||
"prefix_optional": "Prefisso (opzionale)",
|
||||
"clone_to_selected_note": "Clona sotto la nota selezionata <kbd>invio</kbd>",
|
||||
"no_path_to_clone_to": "Nessun percorso per clonare dentro.",
|
||||
"note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "Annulla",
|
||||
"ok": "OK",
|
||||
"confirmation": "Conferma",
|
||||
"are_you_sure_remove_note": "Sei sicuro di voler rimuovere la nota \"{{title}}\" dalla mappa delle relazioni? ",
|
||||
"if_you_dont_check": "Se non lo selezioni, la nota sarà rimossa solamente dalla mappa delle relazioni.",
|
||||
"also_delete_note": "Rimuove anche la nota"
|
||||
},
|
||||
"delete_notes": {
|
||||
"ok": "OK",
|
||||
"close": "Chiudi",
|
||||
"delete_notes_preview": "Anteprima di eliminazione delle note",
|
||||
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere disfatto tramite i cambiamenti recenti)",
|
||||
"erase_notes_description": "L'eliminazione normale (soft) marca le note come eliminate e potranno essere recuperate entro un certo lasso di tempo (dalla finestra dei cambiamenti recenti). Selezionando questa opzione le note si elimineranno immediatamente e non sarà possibile recuperarle.",
|
||||
"erase_notes_warning": "Elimina le note in modo permanente (non potrà essere disfatto), compresi tutti i cloni. Ciò forzerà un nuovo caricamento dell'applicazione.",
|
||||
"cancel": "Annulla",
|
||||
"notes_to_be_deleted": "Le seguenti note saranno eliminate ({{- noteCount}})",
|
||||
"no_note_to_delete": "Nessuna nota sarà eliminata (solo i cloni).",
|
||||
"broken_relations_to_be_deleted": "Le seguenti relazioni saranno interrotte ed eliminate ({{- relationCount}})",
|
||||
"deleted_relation_text": "La nota {{- note}} (da eliminare) è referenziata dalla relazione {{- relation}} originata da {{- source}}."
|
||||
},
|
||||
"info": {
|
||||
"okButton": "OK",
|
||||
"closeButton": "Chiudi"
|
||||
},
|
||||
"export": {
|
||||
"close": "Chiudi",
|
||||
"export_note_title": "Esporta la nota",
|
||||
"export_status": "Stato dell'esportazione",
|
||||
"export": "Esporta",
|
||||
"choose_export_type": "Scegli prima il tipo di esportazione, per favore",
|
||||
"export_in_progress": "Esportazione in corso: {{progressCount}}",
|
||||
"export_finished_successfully": "Esportazione terminata con successo.",
|
||||
"format_pdf": "PDF- allo scopo di stampa o esportazione.",
|
||||
"export_type_subtree": "Questa nota e tutti i suoi discendenti",
|
||||
"format_html": "HTML - raccomandato in quanto mantiene tutti i formati",
|
||||
"format_html_zip": "HTML in archivio ZIP - questo è raccomandato in quanto conserva tutta la formattazione.",
|
||||
"format_markdown": "MArkdown - questo conserva la maggior parte della formattazione."
|
||||
},
|
||||
"password_not_set": {
|
||||
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
|
||||
"body2": "Per proteggere le note, fare clic su <a class=\"open-password-options-button\" href=\"javascript:\">qui</a> per aprire la finestra di dialogo Opzioni e impostare la password."
|
||||
},
|
||||
"protected_session_password": {
|
||||
"close_label": "Chiudi"
|
||||
},
|
||||
"abstract_bulk_action": {
|
||||
"remove_this_search_action": "Rimuovi questa azione di ricerca"
|
||||
},
|
||||
"etapi": {
|
||||
"new_token_title": "Nuovo token ETAPI",
|
||||
"new_token_message": "Inserire il nuovo nome del token"
|
||||
},
|
||||
"electron_integration": {
|
||||
"zoom-factor": "Fattore di ingrandimento",
|
||||
"desktop-application": "Applicazione Desktop"
|
||||
},
|
||||
"note_autocomplete": {
|
||||
"search-for": "Cerca \"{{term}}\"",
|
||||
"create-note": "Crea e collega la nota figlia \"{{term}}\"",
|
||||
"insert-external-link": "Inserisci il collegamento esterno a \"{{term}}\"",
|
||||
"clear-text-field": "Pulisci il campo di testo",
|
||||
"show-recent-notes": "Mostra le note recenti",
|
||||
"full-text-search": "Ricerca full text"
|
||||
},
|
||||
"note_tooltip": {
|
||||
"note-has-been-deleted": "La nota è stata eliminata.",
|
||||
"quick-edit": "Modifica veloce"
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Crea una nota figlia e aggiungila alla mappa",
|
||||
"create-child-note-instruction": "Clicca sulla mappa per creare una nuova nota qui o premi Escape per uscire.",
|
||||
"unable-to-load-map": "Impossibile caricare la mappa."
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Apri la posizione",
|
||||
"remove-from-map": "Rimuovi dalla mappa",
|
||||
"add-note": "Aggiungi un marcatore in questa posizione"
|
||||
},
|
||||
"debug": {
|
||||
"debug": "Debug"
|
||||
},
|
||||
"database_anonymization": {
|
||||
"light_anonymization": "Anonimizzazione parziale",
|
||||
"title": "Anonimizzazione del Database",
|
||||
"full_anonymization": "Anonimizzazione completa",
|
||||
"full_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà (rimuove tutti i contenuti delle note, lasciando solo la struttura e qualche metadato non sensibile) per condividerlo online allo scopo di debugging, senza paura di far trapelare i tuoi dati personali.",
|
||||
"save_fully_anonymized_database": "Salva il database completamente anonimizzato",
|
||||
"light_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà in parzialmente — in particolare, solo il contenuto delle note sarà rimosso, ma i titoli e gli attributi rimarranno. Inoltre, note con script personalizzati JS di frontend/backend e widget personalizzati lasciando rimarranno. Ciò mette a disposizione più contesto per il debug dei problemi.",
|
||||
"choose_anonymization": "Puoi decidere da solo se fornire un database completamente o parzialmente anonimizzato. Anche un database completamente anonimizzato è molto utile, sebbene in alcuni casi i database parzialmente anonimizzati possono accelerare il processo di identificazione dei bug e la loro correzione.",
|
||||
"no_anonymized_database_yet": "Nessun database ancora anonimizzato.",
|
||||
"save_lightly_anonymized_database": "Salva il database parzialmente anonimizzato",
|
||||
"successfully_created_fully_anonymized_database": "Database completamente anonimizzato creato in {{anonymizedFilePath}}",
|
||||
"successfully_created_lightly_anonymized_database": "Database parzialmente anonimizzato creato in {{anonymizedFilePath}}"
|
||||
},
|
||||
"cpu_arch_warning": {
|
||||
"title": "Per favore scarica la versione ARM64",
|
||||
"continue_anyway": "Continua Comunque",
|
||||
"dont_show_again": "Non mostrare più questo avviso",
|
||||
"download_link": "Scarica la Versione Nativa"
|
||||
},
|
||||
"editorfeatures": {
|
||||
"title": "Caratteristiche",
|
||||
"emoji_completion_enabled": "Abilita il completamento automatico delle Emoji",
|
||||
"note_completion_enabled": "Abilita il completamento automatico delle note"
|
||||
},
|
||||
"table_view": {
|
||||
"new-row": "Nuova riga",
|
||||
"new-column": "Nuova colonna",
|
||||
"sort-column-by": "Ordina per \"{{title}}\"",
|
||||
"sort-column-ascending": "Ascendente",
|
||||
"sort-column-descending": "Discendente",
|
||||
"sort-column-clear": "Cancella l'ordinamento",
|
||||
"hide-column": "Nascondi la colonna \"{{title}}\"",
|
||||
"show-hide-columns": "Mostra/nascondi le colonne",
|
||||
"row-insert-above": "Inserisci una riga sopra",
|
||||
"row-insert-below": "Inserisci una riga sotto"
|
||||
},
|
||||
"abstract_search_option": {
|
||||
"remove_this_search_option": "Rimuovi questa opzione di ricerca",
|
||||
"failed_rendering": "Opzione di ricerca di rendering non riuscita: {{dto}} con errore: {{error}} {{stack}}"
|
||||
},
|
||||
"ancestor": {
|
||||
"label": "Antenato"
|
||||
},
|
||||
"add_label": {
|
||||
"add_label": "Aggiungi etichetta",
|
||||
"label_name_placeholder": "nome dell'etichetta",
|
||||
"new_value_placeholder": "nuovo valore",
|
||||
"to_value": "al valore"
|
||||
},
|
||||
"update_label_value": {
|
||||
"to_value": "al valore",
|
||||
"label_name_placeholder": "nome dell'etichetta"
|
||||
},
|
||||
"delete_label": {
|
||||
"delete_label": "Elimina etichetta",
|
||||
"label_name_placeholder": "nome dell'etichetta",
|
||||
"label_name_title": "Sono ammessi i caratteri alfanumerici, il carattere di sottolineato e i due punti."
|
||||
},
|
||||
"tree-context-menu": {
|
||||
"move-to": "Muovi in...",
|
||||
"cut": "Taglia"
|
||||
},
|
||||
"electron_context_menu": {
|
||||
"cut": "Taglia",
|
||||
"copy": "Copia",
|
||||
"paste": "Incolla",
|
||||
"copy-link": "Copia collegamento",
|
||||
"paste-as-plain-text": "Incolla come testo semplice"
|
||||
},
|
||||
"editing": {
|
||||
"editor_type": {
|
||||
"multiline-toolbar": "Mostra la barra degli strumenti su più linee se non entra."
|
||||
}
|
||||
},
|
||||
"edit_button": {
|
||||
"edit_this_note": "Modifica questa nota"
|
||||
},
|
||||
"shortcuts": {
|
||||
"shortcuts": "Scorciatoie"
|
||||
},
|
||||
"shared_switch": {
|
||||
"toggle-on-title": "Condividi la nota",
|
||||
"toggle-off-title": "Non condividere la nota"
|
||||
},
|
||||
"search_string": {
|
||||
"search_prefix": "Cerca:"
|
||||
},
|
||||
"attachment_detail": {
|
||||
"open_help_page": "Apri la pagina di aiuto sugli allegati"
|
||||
},
|
||||
"search_definition": {
|
||||
"ancestor": "antenato",
|
||||
"debug": "debug",
|
||||
"action": "azione",
|
||||
"add_search_option": "Aggiungi un opzione di ricerca:",
|
||||
"search_string": "cerca la stringa",
|
||||
"limit": "limite"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"board_view": {
|
||||
"insert-below": "Inserisci sotto",
|
||||
"delete-column": "Elimina la colonna",
|
||||
"delete-column-confirmation": "Sei sicuro di vole eliminare questa colonna? Il corrispondente attributo sarà eliminato anche nelle note sotto questa colonna."
|
||||
},
|
||||
"backup": {
|
||||
"enable_weekly_backup": "Abilita le archiviazioni settimanali",
|
||||
"enable_monthly_backup": "Abilita le archiviazioni mensili",
|
||||
"backup_recommendation": "Si raccomanda di mantenere attive le archiviazioni, sebbene ciò possa rendere l'avvio dell'applicazione lento con database grandi e/o dispositivi di archiviazione lenti.",
|
||||
"backup_now": "Archivia adesso",
|
||||
"backup_database_now": "Archivia il database adesso",
|
||||
"existing_backups": "Backup esistenti",
|
||||
"date-and-time": "Data e ora",
|
||||
"path": "Percorso",
|
||||
"database_backed_up_to": "Il database è stato archiviato in {{backupFilePath}}",
|
||||
"enable_daily_backup": "Abilita le archiviazioni giornaliere",
|
||||
"no_backup_yet": "Ancora nessuna archiviazione"
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "Aggiorna"
|
||||
},
|
||||
"consistency_checks": {
|
||||
"find_and_fix_button": "Trova e correggi i problemi di coerenza",
|
||||
"finding_and_fixing_message": "In cerca e correzione dei problemi di coerenza...",
|
||||
"issues_fixed_message": "Qualsiasi problema di coerenza che possa essere stato trovato ora è corretto."
|
||||
},
|
||||
"database_integrity_check": {
|
||||
"check_button": "Controllo dell'integrità del database",
|
||||
"checking_integrity": "Controllo dell'integrità del database in corso...",
|
||||
"title": "Controllo di Integrità del database",
|
||||
"description": "Controllerà che il database non sia corrotto a livello SQLite. Può durare un po' di tempo, a seconda della grandezza del DB.",
|
||||
"integrity_check_failed": "Controllo di integrità fallito: {{results}}"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sincronizza",
|
||||
"force_full_sync_button": "Forza una sincronizzazione completa",
|
||||
"failed": "Sincronizzazione fallita: {{message}}"
|
||||
},
|
||||
"sync_2": {
|
||||
"config_title": "Configurazione per la Sincronizzazione",
|
||||
"proxy_label": "Server Proxy per la sincronizzazione (opzionale)",
|
||||
"test_title": "Test di sincronizzazione",
|
||||
"timeout": "Timeout per la sincronizzazione",
|
||||
"timeout_unit": "millisecondi",
|
||||
"save": "Salva",
|
||||
"help": "Aiuto"
|
||||
},
|
||||
"search_engine": {
|
||||
"save_button": "Salva"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tabelle"
|
||||
},
|
||||
"tab_row": {
|
||||
"close_tab": "Chiudi la scheda",
|
||||
"add_new_tab": "Aggiungi una nuova scheda",
|
||||
"close": "Chiudi",
|
||||
"close_other_tabs": "Chiudi le altre schede",
|
||||
"close_right_tabs": "Chiudi le schede a destra",
|
||||
"close_all_tabs": "Chiudi tutte le schede",
|
||||
"reopen_last_tab": "Riapri l'ultima scheda chiusa",
|
||||
"move_tab_to_new_window": "Sposta questa scheda in una nuova finestra",
|
||||
"copy_tab_to_new_window": "Copia questa scheda in una nuova finestra",
|
||||
"new_tab": "Nuova scheda"
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "Sommario"
|
||||
},
|
||||
"table_of_contents": {
|
||||
"title": "Sommario"
|
||||
},
|
||||
"tray": {
|
||||
"title": "Vassoio di Sistema",
|
||||
"enable_tray": "Abilita il vassoio (Trilium necessita di essere riavviato affinché la modifica abbia effetto)"
|
||||
},
|
||||
"heading_style": {
|
||||
"title": "Stile dell'Intestazione",
|
||||
"plain": "Normale",
|
||||
"underline": "Sottolineato",
|
||||
"markdown": "Stile Markdown"
|
||||
},
|
||||
"highlights_list": {
|
||||
"title": "Punti salienti"
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Punti salienti",
|
||||
"options": "Opzioni"
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "Ricerca rapida",
|
||||
"searching": "Ricerca in corso..."
|
||||
"widget-error": {
|
||||
"title": "Impossibile inizializzare un widget",
|
||||
"message-custom": "Il widget personalizzato della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}",
|
||||
"message-unknown": "Un widget sconosciuto non è stato inizializzato a causa di:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Aggiungi un collegamento",
|
||||
"note": "Nota",
|
||||
"search_note": "cerca una nota per nome",
|
||||
"link_title_mirrors": "il titolo del collegamento rispecchia il titolo della nota corrente",
|
||||
"link_title_arbitrary": "il titolo del collegamento può essere modificato arbitrariamente",
|
||||
"link_title": "Titolo del collegamento",
|
||||
"button_add_link": "Aggiungi il collegamento <kbd>invio</kbd>",
|
||||
"help_on_links": "Aiuto sui collegamenti"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix": "Modifica il prefisso del ramo",
|
||||
"help_on_tree_prefix": "Aiuto sui prefissi dell'Albero",
|
||||
"prefix": "Prefisso: ",
|
||||
"save": "Salva",
|
||||
"branch_prefix_saved": "Il prefisso del ramo è stato salvato."
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Azioni massive",
|
||||
"affected_notes": "Note influenzate",
|
||||
"include_descendants": "Includi i discendenti della nota selezionata",
|
||||
"available_actions": "Azioni disponibili",
|
||||
"chosen_actions": "Azioni scelte",
|
||||
"execute_bulk_actions": "Esegui le azioni massive",
|
||||
"bulk_actions_executed": "Le azioni massive sono state eseguite con successo.",
|
||||
"none_yet": "Ancora nessuna... aggiungi una azione cliccando su una di quelle disponibili sopra.",
|
||||
"labels": "Etichette",
|
||||
"relations": "Relazioni",
|
||||
"notes": "Note",
|
||||
"other": "Altro"
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Clona note in...",
|
||||
"help_on_links": "Aiuto sui collegamenti",
|
||||
"notes_to_clone": "Note da clonare",
|
||||
"target_parent_note": "Nodo padre obiettivo",
|
||||
"search_for_note_by_its_name": "cerca una nota per nome",
|
||||
"cloned_note_prefix_title": "Le note clonate saranno mostrate nell'albero delle note con il dato prefisso",
|
||||
"prefix_optional": "Prefisso (opzionale)",
|
||||
"clone_to_selected_note": "Clona sotto la nota selezionata <kbd>invio</kbd>",
|
||||
"no_path_to_clone_to": "Nessun percorso per clonare dentro.",
|
||||
"note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "Annulla",
|
||||
"ok": "OK",
|
||||
"confirmation": "Conferma",
|
||||
"are_you_sure_remove_note": "Sei sicuro di voler rimuovere la nota \"{{title}}\" dalla mappa delle relazioni? ",
|
||||
"if_you_dont_check": "Se non lo selezioni, la nota sarà rimossa solamente dalla mappa delle relazioni.",
|
||||
"also_delete_note": "Rimuove anche la nota"
|
||||
},
|
||||
"delete_notes": {
|
||||
"ok": "OK",
|
||||
"close": "Chiudi",
|
||||
"delete_notes_preview": "Anteprima di eliminazione delle note",
|
||||
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere disfatto tramite i cambiamenti recenti)",
|
||||
"erase_notes_description": "L'eliminazione normale (soft) marca le note come eliminate e potranno essere recuperate entro un certo lasso di tempo (dalla finestra dei cambiamenti recenti). Selezionando questa opzione le note si elimineranno immediatamente e non sarà possibile recuperarle.",
|
||||
"erase_notes_warning": "Elimina le note in modo permanente (non potrà essere disfatto), compresi tutti i cloni. Ciò forzerà un nuovo caricamento dell'applicazione.",
|
||||
"cancel": "Annulla",
|
||||
"notes_to_be_deleted": "Le seguenti note saranno eliminate ({{- noteCount}})",
|
||||
"no_note_to_delete": "Nessuna nota sarà eliminata (solo i cloni).",
|
||||
"broken_relations_to_be_deleted": "Le seguenti relazioni saranno interrotte ed eliminate ({{- relationCount}})",
|
||||
"deleted_relation_text": "La nota {{- note}} (da eliminare) è referenziata dalla relazione {{- relation}} originata da {{- source}}."
|
||||
},
|
||||
"info": {
|
||||
"okButton": "OK",
|
||||
"closeButton": "Chiudi"
|
||||
},
|
||||
"export": {
|
||||
"close": "Chiudi",
|
||||
"export_note_title": "Esporta la nota",
|
||||
"export_status": "Stato dell'esportazione",
|
||||
"export": "Esporta",
|
||||
"choose_export_type": "Scegli prima il tipo di esportazione, per favore",
|
||||
"export_in_progress": "Esportazione in corso: {{progressCount}}",
|
||||
"export_finished_successfully": "Esportazione terminata con successo.",
|
||||
"format_pdf": "PDF- allo scopo di stampa o esportazione.",
|
||||
"export_type_subtree": "Questa nota e tutti i suoi discendenti",
|
||||
"format_html": "HTML - raccomandato in quanto mantiene tutti i formati",
|
||||
"format_html_zip": "HTML in archivio ZIP - questo è raccomandato in quanto conserva tutta la formattazione.",
|
||||
"format_markdown": "MArkdown - questo conserva la maggior parte della formattazione.",
|
||||
"export_type_single": "Solo questa nota, senza le sottostanti"
|
||||
},
|
||||
"password_not_set": {
|
||||
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
|
||||
"body2": "Per proteggere le note, fare clic su <a class=\"open-password-options-button\" href=\"javascript:\">qui</a> per aprire la finestra di dialogo Opzioni e impostare la password."
|
||||
},
|
||||
"protected_session_password": {
|
||||
"close_label": "Chiudi"
|
||||
},
|
||||
"abstract_bulk_action": {
|
||||
"remove_this_search_action": "Rimuovi questa azione di ricerca"
|
||||
},
|
||||
"etapi": {
|
||||
"new_token_title": "Nuovo token ETAPI",
|
||||
"new_token_message": "Inserire il nuovo nome del token"
|
||||
},
|
||||
"electron_integration": {
|
||||
"zoom-factor": "Fattore di ingrandimento",
|
||||
"desktop-application": "Applicazione Desktop"
|
||||
},
|
||||
"note_autocomplete": {
|
||||
"search-for": "Cerca \"{{term}}\"",
|
||||
"create-note": "Crea e collega la nota figlia \"{{term}}\"",
|
||||
"insert-external-link": "Inserisci il collegamento esterno a \"{{term}}\"",
|
||||
"clear-text-field": "Pulisci il campo di testo",
|
||||
"show-recent-notes": "Mostra le note recenti",
|
||||
"full-text-search": "Ricerca full text"
|
||||
},
|
||||
"note_tooltip": {
|
||||
"note-has-been-deleted": "La nota è stata eliminata.",
|
||||
"quick-edit": "Modifica veloce"
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Crea una nota figlia e aggiungila alla mappa",
|
||||
"create-child-note-instruction": "Clicca sulla mappa per creare una nuova nota qui o premi Escape per uscire.",
|
||||
"unable-to-load-map": "Impossibile caricare la mappa."
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Apri la posizione",
|
||||
"remove-from-map": "Rimuovi dalla mappa",
|
||||
"add-note": "Aggiungi un marcatore in questa posizione"
|
||||
},
|
||||
"debug": {
|
||||
"debug": "Debug"
|
||||
},
|
||||
"database_anonymization": {
|
||||
"light_anonymization": "Anonimizzazione parziale",
|
||||
"title": "Anonimizzazione del Database",
|
||||
"full_anonymization": "Anonimizzazione completa",
|
||||
"full_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà (rimuove tutti i contenuti delle note, lasciando solo la struttura e qualche metadato non sensibile) per condividerlo online allo scopo di debugging, senza paura di far trapelare i tuoi dati personali.",
|
||||
"save_fully_anonymized_database": "Salva il database completamente anonimizzato",
|
||||
"light_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà in parzialmente — in particolare, solo il contenuto delle note sarà rimosso, ma i titoli e gli attributi rimarranno. Inoltre, note con script personalizzati JS di frontend/backend e widget personalizzati lasciando rimarranno. Ciò mette a disposizione più contesto per il debug dei problemi.",
|
||||
"choose_anonymization": "Puoi decidere da solo se fornire un database completamente o parzialmente anonimizzato. Anche un database completamente anonimizzato è molto utile, sebbene in alcuni casi i database parzialmente anonimizzati possono accelerare il processo di identificazione dei bug e la loro correzione.",
|
||||
"no_anonymized_database_yet": "Nessun database ancora anonimizzato.",
|
||||
"save_lightly_anonymized_database": "Salva il database parzialmente anonimizzato",
|
||||
"successfully_created_fully_anonymized_database": "Database completamente anonimizzato creato in {{anonymizedFilePath}}",
|
||||
"successfully_created_lightly_anonymized_database": "Database parzialmente anonimizzato creato in {{anonymizedFilePath}}"
|
||||
},
|
||||
"cpu_arch_warning": {
|
||||
"title": "Per favore scarica la versione ARM64",
|
||||
"continue_anyway": "Continua Comunque",
|
||||
"dont_show_again": "Non mostrare più questo avviso",
|
||||
"download_link": "Scarica la Versione Nativa"
|
||||
},
|
||||
"editorfeatures": {
|
||||
"title": "Caratteristiche",
|
||||
"emoji_completion_enabled": "Abilita il completamento automatico delle Emoji",
|
||||
"note_completion_enabled": "Abilita il completamento automatico delle note"
|
||||
},
|
||||
"table_view": {
|
||||
"new-row": "Nuova riga",
|
||||
"new-column": "Nuova colonna",
|
||||
"sort-column-by": "Ordina per \"{{title}}\"",
|
||||
"sort-column-ascending": "Ascendente",
|
||||
"sort-column-descending": "Discendente",
|
||||
"sort-column-clear": "Cancella l'ordinamento",
|
||||
"hide-column": "Nascondi la colonna \"{{title}}\"",
|
||||
"show-hide-columns": "Mostra/nascondi le colonne",
|
||||
"row-insert-above": "Inserisci una riga sopra",
|
||||
"row-insert-below": "Inserisci una riga sotto"
|
||||
},
|
||||
"abstract_search_option": {
|
||||
"remove_this_search_option": "Rimuovi questa opzione di ricerca",
|
||||
"failed_rendering": "Opzione di ricerca di rendering non riuscita: {{dto}} con errore: {{error}} {{stack}}"
|
||||
},
|
||||
"ancestor": {
|
||||
"label": "Antenato"
|
||||
},
|
||||
"add_label": {
|
||||
"add_label": "Aggiungi etichetta",
|
||||
"label_name_placeholder": "nome dell'etichetta",
|
||||
"new_value_placeholder": "nuovo valore",
|
||||
"to_value": "al valore"
|
||||
},
|
||||
"update_label_value": {
|
||||
"to_value": "al valore",
|
||||
"label_name_placeholder": "nome dell'etichetta"
|
||||
},
|
||||
"delete_label": {
|
||||
"delete_label": "Elimina etichetta",
|
||||
"label_name_placeholder": "nome dell'etichetta",
|
||||
"label_name_title": "Sono ammessi i caratteri alfanumerici, il carattere di sottolineato e i due punti."
|
||||
},
|
||||
"tree-context-menu": {
|
||||
"move-to": "Muovi in...",
|
||||
"cut": "Taglia"
|
||||
},
|
||||
"electron_context_menu": {
|
||||
"cut": "Taglia",
|
||||
"copy": "Copia",
|
||||
"paste": "Incolla",
|
||||
"copy-link": "Copia collegamento",
|
||||
"paste-as-plain-text": "Incolla come testo semplice"
|
||||
},
|
||||
"editing": {
|
||||
"editor_type": {
|
||||
"multiline-toolbar": "Mostra la barra degli strumenti su più linee se non entra."
|
||||
}
|
||||
},
|
||||
"edit_button": {
|
||||
"edit_this_note": "Modifica questa nota"
|
||||
},
|
||||
"shortcuts": {
|
||||
"shortcuts": "Scorciatoie"
|
||||
},
|
||||
"shared_switch": {
|
||||
"toggle-on-title": "Condividi la nota",
|
||||
"toggle-off-title": "Non condividere la nota"
|
||||
},
|
||||
"search_string": {
|
||||
"search_prefix": "Cerca:"
|
||||
},
|
||||
"attachment_detail": {
|
||||
"open_help_page": "Apri la pagina di aiuto sugli allegati"
|
||||
},
|
||||
"search_definition": {
|
||||
"ancestor": "antenato",
|
||||
"debug": "debug",
|
||||
"action": "azione",
|
||||
"add_search_option": "Aggiungi un opzione di ricerca:",
|
||||
"search_string": "cerca la stringa",
|
||||
"limit": "limite"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"board_view": {
|
||||
"insert-below": "Inserisci sotto",
|
||||
"delete-column": "Elimina la colonna",
|
||||
"delete-column-confirmation": "Sei sicuro di vole eliminare questa colonna? Il corrispondente attributo sarà eliminato anche nelle note sotto questa colonna."
|
||||
},
|
||||
"backup": {
|
||||
"enable_weekly_backup": "Abilita le archiviazioni settimanali",
|
||||
"enable_monthly_backup": "Abilita le archiviazioni mensili",
|
||||
"backup_recommendation": "Si raccomanda di mantenere attive le archiviazioni, sebbene ciò possa rendere l'avvio dell'applicazione lento con database grandi e/o dispositivi di archiviazione lenti.",
|
||||
"backup_now": "Archivia adesso",
|
||||
"backup_database_now": "Archivia il database adesso",
|
||||
"existing_backups": "Backup esistenti",
|
||||
"date-and-time": "Data e ora",
|
||||
"path": "Percorso",
|
||||
"database_backed_up_to": "Il database è stato archiviato in {{backupFilePath}}",
|
||||
"enable_daily_backup": "Abilita le archiviazioni giornaliere",
|
||||
"no_backup_yet": "Ancora nessuna archiviazione"
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "Aggiorna"
|
||||
},
|
||||
"consistency_checks": {
|
||||
"find_and_fix_button": "Trova e correggi i problemi di coerenza",
|
||||
"finding_and_fixing_message": "In cerca e correzione dei problemi di coerenza...",
|
||||
"issues_fixed_message": "Qualsiasi problema di coerenza che possa essere stato trovato ora è corretto."
|
||||
},
|
||||
"database_integrity_check": {
|
||||
"check_button": "Controllo dell'integrità del database",
|
||||
"checking_integrity": "Controllo dell'integrità del database in corso...",
|
||||
"title": "Controllo di Integrità del database",
|
||||
"description": "Controllerà che il database non sia corrotto a livello SQLite. Può durare un po' di tempo, a seconda della grandezza del DB.",
|
||||
"integrity_check_failed": "Controllo di integrità fallito: {{results}}"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sincronizza",
|
||||
"force_full_sync_button": "Forza una sincronizzazione completa",
|
||||
"failed": "Sincronizzazione fallita: {{message}}"
|
||||
},
|
||||
"sync_2": {
|
||||
"config_title": "Configurazione per la Sincronizzazione",
|
||||
"proxy_label": "Server Proxy per la sincronizzazione (opzionale)",
|
||||
"test_title": "Test di sincronizzazione",
|
||||
"timeout": "Timeout per la sincronizzazione",
|
||||
"timeout_unit": "millisecondi",
|
||||
"save": "Salva",
|
||||
"help": "Aiuto"
|
||||
},
|
||||
"search_engine": {
|
||||
"save_button": "Salva"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tabelle"
|
||||
},
|
||||
"tab_row": {
|
||||
"close_tab": "Chiudi la scheda",
|
||||
"add_new_tab": "Aggiungi una nuova scheda",
|
||||
"close": "Chiudi",
|
||||
"close_other_tabs": "Chiudi le altre schede",
|
||||
"close_right_tabs": "Chiudi le schede a destra",
|
||||
"close_all_tabs": "Chiudi tutte le schede",
|
||||
"reopen_last_tab": "Riapri l'ultima scheda chiusa",
|
||||
"move_tab_to_new_window": "Sposta questa scheda in una nuova finestra",
|
||||
"copy_tab_to_new_window": "Copia questa scheda in una nuova finestra",
|
||||
"new_tab": "Nuova scheda"
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "Sommario"
|
||||
},
|
||||
"table_of_contents": {
|
||||
"title": "Sommario"
|
||||
},
|
||||
"tray": {
|
||||
"title": "Vassoio di Sistema",
|
||||
"enable_tray": "Abilita il vassoio (Trilium necessita di essere riavviato affinché la modifica abbia effetto)"
|
||||
},
|
||||
"heading_style": {
|
||||
"title": "Stile dell'Intestazione",
|
||||
"plain": "Normale",
|
||||
"underline": "Sottolineato",
|
||||
"markdown": "Stile Markdown"
|
||||
},
|
||||
"highlights_list": {
|
||||
"title": "Punti salienti"
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Punti salienti",
|
||||
"options": "Opzioni"
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "Ricerca rapida",
|
||||
"searching": "Ricerca in corso..."
|
||||
},
|
||||
"help": {
|
||||
"goUpDown": "su/giù nella lista delle note",
|
||||
"collapseExpand": "collassa/espande il nodo",
|
||||
"notSet": "non impostato",
|
||||
"goBackForwards": "indietro/avanti nella cronologia",
|
||||
"showJumpToNoteDialog": "mostra <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">la finestra di dialogo \"Salta alla nota\"<a>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
"debug": "デバッグ",
|
||||
"debug_description": "デバッグは複雑なクエリのデバッグを支援するために、追加のデバッグ情報をコンソールに出力します",
|
||||
"action": "アクション",
|
||||
"search_button": "検索 <kbd>Enter</kbd>",
|
||||
"search_button": "検索",
|
||||
"search_execute": "検索とアクションの実行",
|
||||
"save_to_note": "ノートに保存",
|
||||
"search_parameters": "検索パラメータ",
|
||||
@@ -977,8 +977,6 @@
|
||||
},
|
||||
"open-help-page": "ヘルプページを開く",
|
||||
"shared_info": {
|
||||
"shared_publicly": "このノートは一般公開されています",
|
||||
"shared_locally": "このノートはローカルで共有されています",
|
||||
"help_link": "ヘルプについては、<a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>をご覧ください。"
|
||||
},
|
||||
"highlights_list_2": {
|
||||
|
||||
@@ -13,29 +13,127 @@
|
||||
"critical-error": {
|
||||
"title": "Błąd krytyczny",
|
||||
"message": "Wystąpił krytyczny błąd uniemożliwiający uruchomienie aplikacji:\n\n{{message}}\n\nJest to spowodowane najprawdopodobniej niespodziewanym błędem skryptu. Spróbuj uruchomić aplikację ponownie w trybie bezpiecznym i zaadresuj problem."
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "Nie udało się zainicjować widżetu",
|
||||
"message-custom": "Niestandardowy widżet z notatki o identyfikatorze \"{{id}}\", i tytule \"{{title}}\" nie mógł zostać zainicjowany z powodu:\n\n{{message}}",
|
||||
"message-unknown": "Nieznany widżet nie mógł być zainicjowany z powodu:\n\n{{message}}"
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Nie udało się załadować niestandardowego skryptu",
|
||||
"message": "Skrypt z notatki o identyfikatorze \"{{id}}\", tytule \"{{title}}: nie został uruchomiony z powodu:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Dodaj link"
|
||||
"add_link": "Dodaj link",
|
||||
"note": "Notatka",
|
||||
"search_note": "Wyszukaj notatkę po nazwie",
|
||||
"link_title_arbitrary": "Tytuł linku można dowolnie zmieniać",
|
||||
"link_title": "Tytuł linku",
|
||||
"button_add_link": "Dodaj link"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "Zapisz"
|
||||
"save": "Zapisz",
|
||||
"edit_branch_prefix": "Edytuj prefiks gałęzi",
|
||||
"prefix": "Prefiks: ",
|
||||
"branch_prefix_saved": "Zapisano prefiks gałęzi."
|
||||
},
|
||||
"bulk_actions": {
|
||||
"labels": "Etykiety",
|
||||
"notes": "Notatki",
|
||||
"other": "Inne",
|
||||
"relations": "Powiązania"
|
||||
"relations": "Powiązania",
|
||||
"bulk_actions": "Działania zbiorcze",
|
||||
"include_descendants": "Uwzględnia rozwinięcia wybranych notatek",
|
||||
"available_actions": "Dostępne działania",
|
||||
"chosen_actions": "Wybrane działania",
|
||||
"execute_bulk_actions": "Wykonaj zbiór działań",
|
||||
"bulk_actions_executed": "Zbiór działań został wykonany prawidłowo.",
|
||||
"none_yet": "Brak zaznaczonych działań... dodaj działanie poprzez kliknięcie jednej z dostępnych opcji powyżej."
|
||||
},
|
||||
"confirm": {
|
||||
"ok": "OK",
|
||||
"cancel": "Anuluj"
|
||||
"cancel": "Anuluj",
|
||||
"confirmation": "Potwierdzenie",
|
||||
"are_you_sure_remove_note": "Czy napewno chcesz usunąć notatkę \"{{title}}\" z mapy powiązań? ",
|
||||
"if_you_dont_check": "Jeśli nie zaznaczysz tej opcji, notatka zostanie usunięta jedynie z mapy powiązań.",
|
||||
"also_delete_note": "Usuń dodatkowo notatkę"
|
||||
},
|
||||
"delete_notes": {
|
||||
"cancel": "Anuluj",
|
||||
"close": "Zamknij"
|
||||
"close": "Zamknij",
|
||||
"delete_notes_preview": "Usuń podgląd notatek",
|
||||
"delete_all_clones_description": "Usuń również wszystkie sklonowania (działanie może zostać cofnięte w ostatnich zmianach)",
|
||||
"erase_notes_description": "Normalne (miękkie) usuwanie zaznacza jedynie notatki jako usunięte i można je przywrócić (w oknie dialogowym ostatnich zmian) przez wyznaczony okres czasu. Zaznaczenie tej opcji spowoduje natychmiastowe usunięcie notatek, bez możliwości ich przywrócenia.",
|
||||
"erase_notes_warning": "Usuń notatki permanentnie (bez opcji ich przywrócenia), włączając wszystkie kopie. Działanie to wymaga ponownego uruchomienia aplikacji.",
|
||||
"notes_to_be_deleted": "Następujące notatki zostaną usunięte ({{notesCount}})",
|
||||
"no_note_to_delete": "Żadne notatki nie zostaną usunięte (jedynie kopie).",
|
||||
"broken_relations_to_be_deleted": "Następujące powiązania zostaną uszkodzone i usunięte ({{ relationCount}})",
|
||||
"ok": "OK",
|
||||
"deleted_relation_text": "Notatka {{- note}} (do usunięcia) jest powiązana przez relację {{- relation}} pochodzącą z {{- source}}."
|
||||
},
|
||||
"export": {
|
||||
"close": "Zamknij"
|
||||
"close": "Zamknij",
|
||||
"export_note_title": "Eksportuj notatkę",
|
||||
"export_type_subtree": "Ta notatka oraz wszystkie podrzędne",
|
||||
"format_html": "HTML - rekomendowany jako zachowujący całość formatowania",
|
||||
"format_html_zip": "HTML w archiwum ZIP - rekomendowany jako zachowujący całość formatowania.",
|
||||
"format_markdown": "Markdown - zachowuje większość formatowania.",
|
||||
"format_opml": "OPML - format wymiany danych dla outlinerów zawierający tylko tekst. Formatowanie, obrazy i pliki nie są uwzględnione.",
|
||||
"opml_version_1": "OPML v1.0 - tylko zwykły tekst",
|
||||
"opml_version_2": "OPML v2.0 - umożliwia również HTML",
|
||||
"export_type_single": "Tylko ta notatka, bez elementów podrzędnych",
|
||||
"export": "Eksportuj",
|
||||
"choose_export_type": "Wybierz najpierw rodzaj pliku do eksportu",
|
||||
"export_status": "Status eksportu",
|
||||
"export_in_progress": "Postęp eksportowania: {{progressCount}}",
|
||||
"export_finished_successfully": "Eksportowanie zakończone.",
|
||||
"format_pdf": "PDF - w celu drukowania lub udostępniania."
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Sklonuj notatki do...",
|
||||
"notes_to_clone": "Notatki do sklonowania",
|
||||
"search_for_note_by_its_name": "Wyszukaj notatkę po jej nazwie",
|
||||
"cloned_note_prefix_title": "Sklonowana notatka zostanie wyświetlona w drzewie notatki z podanym prefiksem",
|
||||
"prefix_optional": "Prefiks (opcjonalne)",
|
||||
"clone_to_selected_note": "Sklonuj do wybranej notatki",
|
||||
"no_path_to_clone_to": "Brak ścieżki do sklonowania.",
|
||||
"note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\""
|
||||
},
|
||||
"help": {
|
||||
"title": "Ściągawka",
|
||||
"noteNavigation": "Nawigacja po notatkach",
|
||||
"goUpDown": "przewijanie w górę/w dół w liście notatek",
|
||||
"collapseExpand": "zwiń/rozwiń zbiór",
|
||||
"notSet": "niezdefiniowany",
|
||||
"goBackForwards": "przewijaj do tyłu/do przodu w historii",
|
||||
"showJumpToNoteDialog": "pokaż <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"przejdź do dialogu</a>",
|
||||
"scrollToActiveNote": "przewiń do aktywnej notatki",
|
||||
"jumpToParentNote": "przejdź do głównej notatki",
|
||||
"collapseWholeTree": "zwiń całe drzewko notatki",
|
||||
"collapseSubTree": "zwiń gałąź notatki",
|
||||
"tabShortcuts": "Skóry kart",
|
||||
"newTabNoteLink": "link notatki otwiera notatkę w nowej karcie",
|
||||
"newTabWithActivationNoteLink": "link notatki otwiera i aktywuje notatkę w nowej karcie",
|
||||
"onlyInDesktop": "Tylko na komputerze stacjonarnym (wersja Electron)",
|
||||
"openEmptyTab": "Otwórz pustą kartę",
|
||||
"closeActiveTab": "zamknij aktywną kartę",
|
||||
"activateNextTab": "aktywuj następną kartę",
|
||||
"activatePreviousTab": "aktywuj poprzednią kartę",
|
||||
"creatingNotes": "Tworzenie notatek",
|
||||
"createNoteAfter": "Utwórz nową notatkę obok obecnie aktywnej",
|
||||
"createNoteInto": "Utwórz nową podnotatkę w obecnie otwartej",
|
||||
"editBranchPrefix": "edytuj <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefiks</a> aktywnej kopii notatki",
|
||||
"movingCloningNotes": "Przenoszenie / kopiowanie notatek",
|
||||
"moveNoteUpDown": "Przenieś notatkę w górę/w dół na liście notatek",
|
||||
"moveNoteUpHierarchy": "Przenieś notatkę w górę w hierarchii",
|
||||
"multiSelectNote": "Zaznacz wiele notatek powyżej/poniżej",
|
||||
"selectAllNotes": "Wybierz wszystkie notatki na obecnym poziomie",
|
||||
"selectNote": "Wybierz notatkę",
|
||||
"copyNotes": "skopiuj obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">klonowania</a>)",
|
||||
"cutNotes": "przytnij obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla przenoszenia notatek)",
|
||||
"pasteNotes": "wklej notatkę jako podnotatka w obecnej notatce (rozumiane jako przenieś lub skopiuj, w zależności czy notatka była skopiowana czy wycięta)",
|
||||
"deleteNotes": "usuń notatkę / gałąź",
|
||||
"editingNotes": "Edytowanie notatek"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
"code_block": {
|
||||
"theme_none": "Sem destaque de sintaxe",
|
||||
"theme_group_light": "Temas claros",
|
||||
"theme_group_dark": "Temas escuros"
|
||||
"theme_group_dark": "Temas escuros",
|
||||
"word_wrapping": "Quebra automática de linhas",
|
||||
"copy_title": "Copiar para a área de transferência"
|
||||
},
|
||||
"about": {
|
||||
"title": "Sobre o Trilium Notes",
|
||||
"homepage": "Página inicial:",
|
||||
"app_version": "Versão do App:",
|
||||
"db_version": "Versão do db:",
|
||||
"db_version": "Versão do banco de dados:",
|
||||
"sync_version": "Versão de sincronização:",
|
||||
"build_date": "Data de compilação:",
|
||||
"build_revision": "Revisão da compilação:",
|
||||
@@ -192,7 +194,34 @@
|
||||
"ai_disabled": "Recursos de IA desabilitados",
|
||||
"no_models_found_online": "Nenhum modelo encontrado. Por favor, verifique sua chave de API e as configurações.",
|
||||
"no_models_found_ollama": "Nenhum modelo Ollama encontrado. Por favor, verifique se o Ollama está em execução.",
|
||||
"error_fetching": "Erro ao obter modelos: {{error}}"
|
||||
"error_fetching": "Erro ao obter modelos: {{error}}",
|
||||
"ollama_tab": "Ollama",
|
||||
"enable_ai_desc": "Habilitar recursos de IA como sumarização de notas, geração de conteúdo, e outras capacidades de LLM",
|
||||
"provider_precedence": "Prioridade de provedor",
|
||||
"provider_precedence_description": "Lista de provedores em ordem de prioridade, separados por vírgula (por exemplo, 'openai, anthropic, ollama')",
|
||||
"temperature_description": "Controla a aleatoriedade em respostas (0 = determinística, 2 = aleatoriedade máxima)",
|
||||
"ollama_settings": "Configurações do Ollama",
|
||||
"ollama_url_description": "URL para a API Ollama (padrão: http://localhost:11434)",
|
||||
"ollama_model_description": "Modelo Ollama usado para complementação de chat",
|
||||
"anthropic_configuration": "Configuração da Anthropic",
|
||||
"voyage_configuration": "Configuração da Voyage IA",
|
||||
"voyage_url_description": "Padrão: https://api.voyageai.com/v1",
|
||||
"ollama_configuration": "Configuração da Ollama",
|
||||
"enable_ollama": "Habilitar Ollama",
|
||||
"enable_ollama_description": "Habilitar Ollama para uso do modelo local de IA",
|
||||
"ollama_url": "URL da Ollama",
|
||||
"ollama_model": "Modelo do Ollama",
|
||||
"refresh_models": "Atualizar Modelos",
|
||||
"refreshing_models": "Atualizando…",
|
||||
"enable_automatic_indexing": "Habilitar indexação automática",
|
||||
"rebuild_index": "Reconstruir Índice",
|
||||
"rebuild_index_error": "Ocorreu um erro ao iniciar a reconstrução do índice. Verifique os logs para obter detalhes.",
|
||||
"note_title": "Título da nota",
|
||||
"error": "Erro",
|
||||
"last_attempt": "Última Tentativa",
|
||||
"actions": "Ações",
|
||||
"partial": "{{ percentage }}% concluído",
|
||||
"show_thinking_description": "Exibir o processo de linha de raciocínio da AI"
|
||||
},
|
||||
"confirm": {
|
||||
"confirmation": "Confirmação",
|
||||
@@ -581,7 +610,8 @@
|
||||
"you_can_also_open": ", você também pode abrir o(a) ",
|
||||
"open_help_page": "Abrir página de ajuda nos anexos",
|
||||
"list_of_all_attachments": "Lista de todos os anexos",
|
||||
"attachment_deleted": "Este anexo foi excluído."
|
||||
"attachment_deleted": "Este anexo foi excluído.",
|
||||
"owning_note": "Nota proprietária: "
|
||||
},
|
||||
"ancestor": {
|
||||
"depth_gt": "é maior que {{count}}",
|
||||
@@ -682,7 +712,8 @@
|
||||
"open_externally": "Abrir externamente"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Fechar"
|
||||
"close": "Fechar",
|
||||
"help_title": "Exibir mais informações sobre esta tela"
|
||||
},
|
||||
"api_log": {
|
||||
"close": "Fechar"
|
||||
@@ -718,14 +749,47 @@
|
||||
"insert-note-after": "Inserir nota após",
|
||||
"insert-child-note": "Inserir nota filha",
|
||||
"delete": "Excluir",
|
||||
"search-in-subtree": "Buscar na subárvore"
|
||||
"search-in-subtree": "Buscar na subárvore",
|
||||
"hoist-note": "Fixar nota",
|
||||
"unhoist-note": "Desafixar nota",
|
||||
"edit-branch-prefix": "Editar prefixo da ramificação",
|
||||
"advanced": "Avançado",
|
||||
"expand-subtree": "Expandir subárvore",
|
||||
"collapse-subtree": "Recolher subárvore",
|
||||
"sort-by": "Ordenar por...",
|
||||
"recent-changes-in-subtree": "Alterações recentes na subárvore",
|
||||
"convert-to-attachment": "Converter para anexo",
|
||||
"copy-note-path-to-clipboard": "Copiar caminho da nota para a área de transferência",
|
||||
"protect-subtree": "Proteger subárvore",
|
||||
"unprotect-subtree": "Desproteger subárvore",
|
||||
"copy-clone": "Copiar / clonar",
|
||||
"clone-to": "Clonar para...",
|
||||
"cut": "Recortar",
|
||||
"move-to": "Mover para...",
|
||||
"paste-into": "Colar em",
|
||||
"paste-after": "Colar depois",
|
||||
"duplicate": "Duplicar",
|
||||
"export": "Exportar",
|
||||
"import-into-note": "Importar na nota",
|
||||
"apply-bulk-actions": "Aplicar ações em massa",
|
||||
"converted-to-attachments": "{{count}} notas foram convertidas em anexos.",
|
||||
"convert-to-attachment-confirm": "Tem certeza de que deseja converter as notas selecionadas em anexos de suas notas-pai?",
|
||||
"open-in-popup": "Edição rápida"
|
||||
},
|
||||
"command_palette": {
|
||||
"search_subtree_title": "Buscar na Subárvore",
|
||||
"search_subtree_description": "Buscar dentro da subárvore atual",
|
||||
"search_history_title": "Exibir Histórico de Busca",
|
||||
"search_history_description": "Visualizar buscas anteriores",
|
||||
"configure_launch_bar_title": "Configurar Barra de Execução"
|
||||
"configure_launch_bar_title": "Configurar Barra de Execução",
|
||||
"tree-action-name": "Árvore: {{name}}",
|
||||
"export_note_title": "Exportar Nota",
|
||||
"export_note_description": "Exportar a nota atual",
|
||||
"show_attachments_title": "Exibir Anexos",
|
||||
"show_attachments_description": "Exibir anexos da nota",
|
||||
"search_notes_title": "Buscar Notas",
|
||||
"search_notes_description": "Abrir busca avançada",
|
||||
"configure_launch_bar_description": "Abrir a configuração da barra de lançamento, para adicionar ou remover itens."
|
||||
},
|
||||
"delete_note": {
|
||||
"delete_note": "Excluir nota",
|
||||
@@ -915,7 +979,8 @@
|
||||
"note_type": "Tipo da nota",
|
||||
"editable": "Editável",
|
||||
"basic_properties": "Propriedades Básicas",
|
||||
"language": "Idioma"
|
||||
"language": "Idioma",
|
||||
"configure_code_notes": "Configurar notas de código..."
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "Tipo de visualização",
|
||||
@@ -929,7 +994,7 @@
|
||||
"invalid_view_type": "Tipo de visualização inválido '{{type}}'",
|
||||
"calendar": "Calendário",
|
||||
"table": "Tabela",
|
||||
"geo-map": "Geo Map",
|
||||
"geo-map": "Mapa geográfico",
|
||||
"board": "Quadro"
|
||||
},
|
||||
"edited_notes": {
|
||||
@@ -1025,7 +1090,7 @@
|
||||
"limit_description": "Limitar número de resultados",
|
||||
"debug": "depurar",
|
||||
"action": "ação",
|
||||
"search_button": "Pesquisar <kbd>enter</kbd>",
|
||||
"search_button": "Pesquisar",
|
||||
"search_execute": "Pesquisar & Executar ações",
|
||||
"save_to_note": "Salvar para nota",
|
||||
"search_parameters": "Parâmetros de Pesquisa",
|
||||
@@ -1101,12 +1166,14 @@
|
||||
"label_date_created": "notas criadas no último mês",
|
||||
"error": "Erro na busca: {{error}}",
|
||||
"search_prefix": "Busca:",
|
||||
"placeholder": "palavras-chave fulltext, #tag = valor..."
|
||||
"placeholder": "palavras-chave fulltext, #tag = valor...",
|
||||
"complete_help": "ajuda completa da sintaxe de busca"
|
||||
},
|
||||
"attachment_list": {
|
||||
"open_help_page": "Abrir página de ajuda nos anexos",
|
||||
"upload_attachments": "Enviar anexos",
|
||||
"no_attachments": "Esta nota não possuí anexos."
|
||||
"no_attachments": "Esta nota não possuí anexos.",
|
||||
"owning_note": "Nota proprietária: "
|
||||
},
|
||||
"editable_code": {
|
||||
"placeholder": "Digite o conteúdo da sua nota de código aqui…"
|
||||
@@ -1116,10 +1183,12 @@
|
||||
},
|
||||
"empty": {
|
||||
"search_placeholder": "buscar uma nota pelo nome",
|
||||
"enter_workspace": "Entrar no workspace {{title}}"
|
||||
"enter_workspace": "Entrar no workspace {{title}}",
|
||||
"open_note_instruction": "Abra uma nota digitando o título da nota no campo abaixo ou escolha uma nota na árvore."
|
||||
},
|
||||
"file": {
|
||||
"file_preview_not_available": "Prévia não disponível para este formato de arquivo."
|
||||
"file_preview_not_available": "Prévia não disponível para este formato de arquivo.",
|
||||
"too_big": "A prévia mostra apenas os primeiros {{maxNumChars}} caracteres do arquivo por razões de desempenho. Baixe o arquivo e abra-o externamente para ver todo o conteúdo."
|
||||
},
|
||||
"protected_session": {
|
||||
"enter_password_instruction": "É necessário digitar sua senha para mostar notas protegidas:",
|
||||
@@ -1130,7 +1199,8 @@
|
||||
"protecting-in-progress": "Proteções em andamento: {{count}}",
|
||||
"unprotecting-in-progress-count": "Remoções de proteção em andamento: {{count}}",
|
||||
"protecting-title": "Estado da proteção",
|
||||
"unprotecting-title": "Estado da remoção de proteção"
|
||||
"unprotecting-title": "Estado da remoção de proteção",
|
||||
"start_session_button": "Iniciar sessão protegida <kbd>enter</kbd>"
|
||||
},
|
||||
"relation_map": {
|
||||
"open_in_new_tab": "Abrir em nova aba",
|
||||
@@ -1145,10 +1215,15 @@
|
||||
"note_already_in_diagram": "A nota \"{{title}}\" já está no diagrama.",
|
||||
"enter_title_of_new_note": "Digite o título da nova nota",
|
||||
"default_new_note_title": "nova nota",
|
||||
"click_on_canvas_to_place_new_note": "Clique no quadro para incluir uma nova nota"
|
||||
"click_on_canvas_to_place_new_note": "Clique no quadro para incluir uma nova nota",
|
||||
"specify_new_relation_name": "Especifique o novo nome da relação (caracteres disponíveis: alfanuméricos, vírgula e underscore):",
|
||||
"start_dragging_relations": "Comece arrastando as relações daqui e solte-as em outra nota.",
|
||||
"cannot_match_transform": "Não foi possível combinar a transformação: {{transform}}"
|
||||
},
|
||||
"web_view": {
|
||||
"web_view": "Web View"
|
||||
"web_view": "Web View",
|
||||
"embed_websites": "Nota do tipo Visualização Web permite que você incorpore sites dentro do Trilium.",
|
||||
"create_label": "Para começar, crie uma etiqueta com um endereço URL que deseja incorporar, por exemplo, #webViewSrc=\"https://www.google.com\""
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "Recarregar"
|
||||
@@ -1163,17 +1238,26 @@
|
||||
"check_button": "Verificar integridade do banco de dados",
|
||||
"checking_integrity": "Verificando integridade do banco de dados…",
|
||||
"integrity_check_succeeded": "Verificação de integridade bem sucedida - nenhum problema encontrado.",
|
||||
"integrity_check_failed": "Verificação de integridade falhou: {{results}}"
|
||||
"integrity_check_failed": "Verificação de integridade falhou: {{results}}",
|
||||
"title": "Verificação de Integridade do Banco de Dados",
|
||||
"description": "Isso verificará se o banco de dados não está corrompido no nível SQLite. Pode levar algum tempo, dependendo do tamanho do banco de dados."
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sincronizar",
|
||||
"force_full_sync_button": "Forçar sincronização completa",
|
||||
"full_sync_triggered": "Sincronização completa iniciada",
|
||||
"finished-successfully": "Sincronização finalizada com sucesso.",
|
||||
"failed": "Sincronização falhou: {{message}}"
|
||||
"failed": "Sincronização falhou: {{message}}",
|
||||
"fill_entity_changes_button": "Preencher registros de alterações da entidade",
|
||||
"filling_entity_changes": "Preenchendo linhas de alterações da entidade...",
|
||||
"sync_rows_filled_successfully": "Linhas de sincronização preenchidas com sucesso"
|
||||
},
|
||||
"vacuum_database": {
|
||||
"description": "Isso irá reconstruir o banco de dados, o que normalmente irá resultar em uma redução do arquivo do banco de dados. Nenhum dado será alterado."
|
||||
"description": "Isso irá reconstruir o banco de dados, o que normalmente irá resultar em uma redução do arquivo do banco de dados. Nenhum dado será alterado.",
|
||||
"title": "Executar Vacuum no Banco de Dados",
|
||||
"button_text": "Executar Vacuum",
|
||||
"vacuuming_database": "Executando Vacuum...",
|
||||
"database_vacuumed": "Vacuum executado no banco de dados"
|
||||
},
|
||||
"fonts": {
|
||||
"theme_defined": "Tema definido",
|
||||
@@ -1194,8 +1278,9 @@
|
||||
"handwriting-system-fonts": "Fontes de escrita à mão de sistema",
|
||||
"serif": "Serifa",
|
||||
"sans-serif": "Sem Serifa",
|
||||
"monospace": "Monospace",
|
||||
"system-default": "Padrão do Sistema"
|
||||
"monospace": "Monoespaçado",
|
||||
"system-default": "Padrão do Sistema",
|
||||
"note_tree_and_detail_font_sizing": "Note que o tamanho da fonte da árvore e dos detalhes é relativo à configuração principal do tamanho de fonte."
|
||||
},
|
||||
"max_content_width": {
|
||||
"title": "Largura do Conteúdo",
|
||||
@@ -1203,7 +1288,8 @@
|
||||
"max_width_unit": "pixels",
|
||||
"apply_changes_description": "Para aplicar as alterações de largura do conteúdo, clique em",
|
||||
"reload_button": "recarregar frontend",
|
||||
"reload_description": "alterações de opções de aparência"
|
||||
"reload_description": "alterações de opções de aparência",
|
||||
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em telas wide."
|
||||
},
|
||||
"native_title_bar": {
|
||||
"title": "Barra de Título Nativa (requer recarregar o app)",
|
||||
@@ -1237,5 +1323,708 @@
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Atributos próprios"
|
||||
},
|
||||
"database_anonymization": {
|
||||
"no_anonymized_database_yet": "Nenhuma base de dados anonimizada no momento.",
|
||||
"title": "Anonimização do Banco de Dados",
|
||||
"full_anonymization": "Anonimização Completa",
|
||||
"full_anonymization_description": "Esta ação ira gerar uma cópia anônima do banco de dados (sem conteúdo das notas, apenas estrutura e alguns metadados não sensíveis) para compartilhar online e depurar, sem expor seus dados pessoais.",
|
||||
"save_fully_anonymized_database": "Salvar banco de dados totalmente anonimizado",
|
||||
"light_anonymization": "Anonimização Leve",
|
||||
"light_anonymization_description": "Esta ação criará uma nova cópia do banco de dados e aplicará uma anonimização leve — especificamente, apenas o conteúdo de todas as notas será removido, mas títulos e atributos permanecerão. Além disso, notas personalizadas de scripts JS frontend/servidor e widgets personalizados serão mantidos. Isso fornece mais contexto para depurar os problemas.",
|
||||
"choose_anonymization": "Você pode decidir se deseja fornecer um banco de dados totalmente ou levemente anonimizado. Mesmo o banco de dados totalmente anonimizado é muito útil; no entanto, em alguns casos, um banco de dados levemente anonimizado pode acelerar o processo de identificação e correção de bugs.",
|
||||
"save_lightly_anonymized_database": "Salvar banco de dados levemente anonimizado",
|
||||
"error_creating_anonymized_database": "Não foi possível criar o banco de dados anonimizado, verifique os logs do servidor para mais detalhes",
|
||||
"existing_anonymized_databases": "Bancos de dados anonimizado existentes",
|
||||
"creating_fully_anonymized_database": "Criando banco de dados totalmente anonimizado...",
|
||||
"creating_lightly_anonymized_database": "Criando banco de dados levemente anonimizado...",
|
||||
"successfully_created_fully_anonymized_database": "Banco de dados totalmente anonimizado criado em {{anonymizedFilePath}}",
|
||||
"successfully_created_lightly_anonymized_database": "Banco de dados levemente anonimizado criado em {{anonymizedFilePath}}"
|
||||
},
|
||||
"ribbon": {
|
||||
"widgets": "Widgets de fita",
|
||||
"promoted_attributes_message": "A aba de Atributos Promovidos irá abrir automaticamente se existirem atributos promovidos na nota",
|
||||
"edited_notes_message": "A aba de Notas Editadas será aberta automaticamente nas notas do dia"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Desempenho",
|
||||
"enable-motion": "Habilitar transições e animações",
|
||||
"enable-shadows": "Habilitar sombras",
|
||||
"enable-backdrop-effects": "Habilitar efeitos de fundo para menus, popups e painéis"
|
||||
},
|
||||
"zoom_factor": {
|
||||
"title": "Fator do Zoom (apenas versão de área de trabalho)",
|
||||
"description": "O zoom também pode ser controlado com atalhos CTRL+- e CTRL+=."
|
||||
},
|
||||
"code_auto_read_only_size": {
|
||||
"title": "Tamanho para Somente Leitura Automático",
|
||||
"description": "O tamanho para nota somente leitura automático é o tamanho após o qual as notas serão exibidas em um modo somente leitura (por razões de desempenho).",
|
||||
"label": "Tamanho para somente leitura automático (notas de código)",
|
||||
"unit": "caracteres"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "Editor"
|
||||
},
|
||||
"code_mime_types": {
|
||||
"title": "Tipos MIME disponíveis no dropdown"
|
||||
},
|
||||
"vim_key_bindings": {
|
||||
"use_vim_keybindings_in_code_notes": "Atribuições de teclas do Vim",
|
||||
"enable_vim_keybindings": "Habilite as atribuições de teclas do Vim em notas de código (sem modo ex)"
|
||||
},
|
||||
"wrap_lines": {
|
||||
"wrap_lines_in_code_notes": "Quebrar linhas em notas de código",
|
||||
"enable_line_wrap": "Habilitar Quebra de Linha (pode ser necessário recarregar o frontend para entrar em vigor)"
|
||||
},
|
||||
"images": {
|
||||
"images_section_title": "Imagens",
|
||||
"download_images_automatically": "Baixar imagens automaticamente para uso offline.",
|
||||
"download_images_description": "HTML colado pode conter referências a imagens on-line, Trilium vai buscar estas referências e baixar as imagens para que eles estejam disponíveis off-line.",
|
||||
"enable_image_compression": "Habilitar compressão de imagem",
|
||||
"max_image_dimensions": "Largura/altura máxima de uma imagem (a imagem será redimensionada se exceder este valor).",
|
||||
"max_image_dimensions_unit": "pixels",
|
||||
"jpeg_quality_description": "Qualidade JPEG (10 - pior qualidade, 100 - melhor qualidade, 50 - 85 é recomendado)"
|
||||
},
|
||||
"attachment_erasure_timeout": {
|
||||
"attachment_erasure_timeout": "Tempo Limite para Exclusão de um Anexo",
|
||||
"attachment_auto_deletion_description": "Os anexos são automaticamente excluídos (e apagados) se não forem mais referenciados por sua nota após um tempo definido.",
|
||||
"erase_attachments_after": "Apagar anexos não utilizados após:",
|
||||
"manual_erasing_description": "Você também pode ativar a exclusão manualmente (sem considerar o timeout definido acima):",
|
||||
"erase_unused_attachments_now": "Apagar anexos não utilizados agora",
|
||||
"unused_attachments_erased": "Os anexos não utilizados foram apagados."
|
||||
},
|
||||
"network_connections": {
|
||||
"network_connections_title": "Conexões de Rede",
|
||||
"check_for_updates": "Verificar atualizações automaticamente"
|
||||
},
|
||||
"note_erasure_timeout": {
|
||||
"note_erasure_timeout_title": "Tempo Limite para Exclusão de uma Nota",
|
||||
"note_erasure_description": "Notas excluídas (e atributos, revisões...) inicialmente são apenas marcadas como excluídas e é possível recuperá-las a partir do diálogo de Notas Recentes. Depois de um tempo, as notas excluídas são \"apagadas\", o que significa que seu conteúdo não é mais recuperável. Esta configuração permite configurar o período entre excluir e apagar a nota.",
|
||||
"erase_notes_after": "Apagar notas após:",
|
||||
"manual_erasing_description": "Você também pode ativar a exclusão manualmente (sem considerar o timeout definido acima):",
|
||||
"erase_deleted_notes_now": "Apague as notas excluídas agora",
|
||||
"deleted_notes_erased": "As notas excluídas foram removidas permanentemente."
|
||||
},
|
||||
"revisions_snapshot_interval": {
|
||||
"note_revisions_snapshot_interval_title": "Intervalo de Captura de Versão da Nota",
|
||||
"note_revisions_snapshot_description": "O intervalo de captura de versão da nota é o tempo após o qual uma nova revisão será criada para a nota. Veja <doc>wiki</doc> para mais informações.",
|
||||
"snapshot_time_interval_label": "Intervalo de Captura de Versão da Nota:"
|
||||
},
|
||||
"revisions_snapshot_limit": {
|
||||
"note_revisions_snapshot_limit_title": "Limite de Capturas das Versões da Nota",
|
||||
"note_revisions_snapshot_limit_description": "O limite de número de captura de versões das notas refere-se ao número máximo de revisões que podem ser salvas para cada nota. Onde -1 significa nenhum limite, 0 significa excluir todas as revisões. Você pode definir as revisões máximas para uma única nota através da etiqueta #versioningLimit.",
|
||||
"snapshot_number_limit_label": "Quantidade limite de capturas de versão:",
|
||||
"snapshot_number_limit_unit": "capturas",
|
||||
"erase_excess_revision_snapshots": "Apagar capturas de versão excedentes agora",
|
||||
"erase_excess_revision_snapshots_prompt": "As capturas de versão excedentes foram apagadas."
|
||||
},
|
||||
"search_engine": {
|
||||
"title": "Motor de Pesquisa",
|
||||
"custom_search_engine_info": "O motor de busca personalizado requer que sejam definidos um nome e uma URL. Se um destes não estiver definido, o DuckDuckGo será usado como o motor de busca padrão.",
|
||||
"predefined_templates_label": "Modelos de motor de pesquisa predefinidos",
|
||||
"bing": "Bing",
|
||||
"baidu": "Baidu",
|
||||
"duckduckgo": "DuckDuckGo",
|
||||
"custom_name_label": "Nome do motor de pesquisa personalizado",
|
||||
"google": "Google",
|
||||
"custom_name_placeholder": "Nome personalizado do motor de pesquisa",
|
||||
"custom_url_label": "A URL do motor de pesquisa personalizado deve incluir {keyword} como um substituto para o termo pesquisado.",
|
||||
"custom_url_placeholder": "URL personalizada do motor de pesquisa",
|
||||
"save_button": "Salvar"
|
||||
},
|
||||
"tray": {
|
||||
"title": "Bandeja do Sistema",
|
||||
"enable_tray": "Habilitar bandeja (O Trilium precisar ser reiniciado para esta mudança entrar em vigor)"
|
||||
},
|
||||
"heading_style": {
|
||||
"title": "Estilo de Título",
|
||||
"plain": "Plano",
|
||||
"markdown": "Estilo Markdown",
|
||||
"underline": "Sublinhado"
|
||||
},
|
||||
"highlights_list": {
|
||||
"title": "Lista de Destaques",
|
||||
"description": "Você pode personalizar a lista de destaques exibida no painel da direita:",
|
||||
"bold": "Texto em negrito",
|
||||
"italic": "Texto em itálico",
|
||||
"underline": "Texto sublinhado",
|
||||
"color": "Texto colorido",
|
||||
"bg_color": "Texto com cor de fundo",
|
||||
"visibility_title": "Visibilidade da Lista de Destaques",
|
||||
"visibility_description": "Você pode esconder o componente de destaques por nota adicionado a etiqueta #hideHighlightWidget.",
|
||||
"shortcut_info": "Você pode configurar um atalhe de teclado para alternar rapidamente o painel da direita (incluindo os Destaques) em Opções -> Atalhos (nome 'toggleRightPane')."
|
||||
},
|
||||
"table_of_contents": {
|
||||
"title": "Tabela de Conteúdos",
|
||||
"description": "A tabela de conteúdos irá aparecer em notas de texto que possuam mais que um número definido de títulos. Você pode personalizar este número:",
|
||||
"unit": "títulos",
|
||||
"disable_info": "Você também pode usar essa opção para desabilitar a Tabela de Conteúdos configurando um número muito alto.",
|
||||
"shortcut_info": "Você pode configurar um atalhe de teclado para alternar rapidamente o painel da direita (incluindo a Tabela de Conteúdos) em Opções -> Atalhos (nome 'toggleRightPane')."
|
||||
},
|
||||
"text_auto_read_only_size": {
|
||||
"title": "Tamanho para Somente Leitura Automático",
|
||||
"description": "O tamanho para nota somente leitura automático é o tamanho a partir do qual as notas serão exibidas em modo somente leitura (por razões de desempenho).",
|
||||
"label": "Tamanho automático para somente leitura (notas de texto)",
|
||||
"unit": "caracteres"
|
||||
},
|
||||
"custom_date_time_format": {
|
||||
"title": "Formato Personalizado de Data/Hora",
|
||||
"description": "Personaliza o formato de data e hora inseridos via <shortcut /> ou barra de ferramentas. Veja a <doc>documentação do Day.js</doc> para os tokens de formatos disponíveis.",
|
||||
"format_string": "Formato:",
|
||||
"formatted_time": "Data/hora formatada:"
|
||||
},
|
||||
"i18n": {
|
||||
"title": "Localização",
|
||||
"language": "Idioma",
|
||||
"first-day-of-the-week": "Primeiro dia da semana",
|
||||
"sunday": "Domingo",
|
||||
"monday": "Segunda-feira",
|
||||
"first-week-of-the-year": "Primeiro dia do ano",
|
||||
"first-week-contains-first-day": "Primeira semana que contenha o primeiro dia do ano",
|
||||
"first-week-contains-first-thursday": "Primeira semana que contenha a primeira quinta-feira do ano",
|
||||
"first-week-has-minimum-days": "Primeira semana contendo um mínimo de dias",
|
||||
"min-days-in-first-week": "Mínimo de dias da primeira semana",
|
||||
"first-week-info": "Primeira semana que contenha a primeira Quinta-feira do ano é baseado na <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a>.",
|
||||
"first-week-warning": "Alterar as opções de primeira semana pode causar duplicidade nas Notas Semanais existentes e estas Notas não serão atualizadas de acordo.",
|
||||
"formatting-locale": "Formato de data e número"
|
||||
},
|
||||
"backup": {
|
||||
"automatic_backup": "Backup automático",
|
||||
"automatic_backup_description": "O Trilium pode fazer o backup do banco de dados automaticamente:",
|
||||
"enable_daily_backup": "Habilitar backup diário",
|
||||
"enable_weekly_backup": "Habilitar backup semanal",
|
||||
"enable_monthly_backup": "Habilitar backup mensal",
|
||||
"backup_recommendation": "É recomendado manter o backup habilitado, mas isso pode fazer com que a inicialização da aplicação seja lenta com grandes bancos de dados e/ou dispositivos de armazenamento lentos.",
|
||||
"backup_now": "Realizar backup agora",
|
||||
"backup_database_now": "Realizar backup do banco de dados agora",
|
||||
"existing_backups": "Backups existentes",
|
||||
"date-and-time": "Data & hora",
|
||||
"path": "Caminho",
|
||||
"database_backed_up_to": "Backup do banco de dados criado em {{backupFilePath}}",
|
||||
"no_backup_yet": "ainda não existe nenhum backup"
|
||||
},
|
||||
"note_types": {
|
||||
"relation-map": "Map de Relação",
|
||||
"note-map": "Map de Notas",
|
||||
"render-note": "Nota de Renderização",
|
||||
"book": "Coleção",
|
||||
"mermaid-diagram": "Diagrama Mermaid",
|
||||
"canvas": "Quadros",
|
||||
"web-view": "Visualização Web",
|
||||
"mind-map": "Mapa Mental",
|
||||
"file": "Arquivo",
|
||||
"image": "Imagem",
|
||||
"launcher": "Lançador",
|
||||
"doc": "Documento",
|
||||
"widget": "Widget",
|
||||
"confirm-change": "Não é recomentado alterar o tipo da nota quando o conteúdo da nota não está vazio. Quer continuar assim mesmo?",
|
||||
"geo-map": "Mapa geográfico",
|
||||
"beta-feature": "Beta",
|
||||
"ai-chat": "Chat IA",
|
||||
"task-list": "Lista de Tarefas",
|
||||
"new-feature": "Novo",
|
||||
"collections": "Coleções",
|
||||
"text": "Texto",
|
||||
"code": "Código",
|
||||
"saved-search": "Pesquisa Salva"
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Proteger a nota",
|
||||
"toggle-off": "Desproteger a nota",
|
||||
"toggle-on-hint": "A nota não está protegida, clique para protegê-la",
|
||||
"toggle-off-hint": "A nota está protegida, clique para desprotegê-la"
|
||||
},
|
||||
"shared_switch": {
|
||||
"shared": "Compartilhado",
|
||||
"toggle-on-title": "Compartilhar a nota",
|
||||
"toggle-off-title": "Parar de compartilhar a nota",
|
||||
"shared-branch": "Esta nota só existe como uma nota compartilhada, então parar de compartilhar irá excluí-la. Deseja continuar e excluir esta nota?",
|
||||
"inherited": "Não foi possível deixar de compartilhar a nota porque ela é compartilhada através da herança de uma nota superior."
|
||||
},
|
||||
"template_switch": {
|
||||
"template": "Modelo",
|
||||
"toggle-on-hint": "Transformar a nota em um modelo",
|
||||
"toggle-off-hint": "Deixar de usar a nota como um modelo"
|
||||
},
|
||||
"open-help-page": "Abrir página de ajuda",
|
||||
"find": {
|
||||
"case_sensitive": "Diferencia maiúsculas de minúsculas",
|
||||
"match_words": "Corresponder palavras",
|
||||
"find_placeholder": "Buscar no texto...",
|
||||
"replace_placeholder": "Substituir por...",
|
||||
"replace": "Substituir",
|
||||
"replace_all": "Substituir tudo"
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Lista de Destaques",
|
||||
"options": "Opções"
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "Busca rápida",
|
||||
"searching": "Buscando...",
|
||||
"no-results": "Nenhum resultado encontrado",
|
||||
"more-results": "... e mais {{number}} resultados.",
|
||||
"show-in-full-search": "Exibir na busca completa"
|
||||
},
|
||||
"note_tree": {
|
||||
"collapse-title": "Recolher árvore de notas",
|
||||
"scroll-active-title": "Ir até a nota ativa",
|
||||
"tree-settings-title": "Configurações da árvore",
|
||||
"hide-archived-notes": "Ocultar notas arquivadas",
|
||||
"automatically-collapse-notes": "Recolher notas automaticamente",
|
||||
"automatically-collapse-notes-title": "As notas serão recolhidas após um tempo de inatividade para simplificar a árvore.",
|
||||
"save-changes": "Salvar e aplicar alterações",
|
||||
"auto-collapsing-notes-after-inactivity": "Recolhendo notas automaticamente após inatividade...",
|
||||
"saved-search-note-refreshed": "A nota de pesquisa salva foi atualizada.",
|
||||
"hoist-this-note-workspace": "Fixar esta nota (workspace)",
|
||||
"refresh-saved-search-results": "Atualizar resultados de pesquisa salvos",
|
||||
"create-child-note": "Criar nota filha",
|
||||
"unhoist": "Desafixar",
|
||||
"toggle-sidebar": "Alternar barra lateral"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Manter Janela no Topo"
|
||||
},
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "Não foi possível encontrar typeWidget para o tipo '{{type}}'"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "digite o título da nota aqui..."
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "Nenhuma nota encontrada para os parâmetros de busca digitados.",
|
||||
"search_not_executed": "A busca ainda não foi executada. Clique no botão \"Buscar\" acima para ver os resultados."
|
||||
},
|
||||
"spacer": {
|
||||
"configure_launchbar": "Configurar Barra de Lançamento"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "Nenhum linha foi retornada para esta consulta"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tabelas"
|
||||
},
|
||||
"tab_row": {
|
||||
"close_tab": "Fechar aba",
|
||||
"add_new_tab": "Adicionar nova aba",
|
||||
"close": "Fechar",
|
||||
"close_other_tabs": "Fechar as outras abas",
|
||||
"close_right_tabs": "Fechar as abas à direita",
|
||||
"close_all_tabs": "Fechar todas as abas",
|
||||
"reopen_last_tab": "Reabrir a última aba fechada",
|
||||
"move_tab_to_new_window": "Mover esta aba para uma nova janela",
|
||||
"copy_tab_to_new_window": "Copiar esta aba para uma nova janela",
|
||||
"new_tab": "Nova aba"
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "Tabela de Conteúdos",
|
||||
"options": "Opções"
|
||||
},
|
||||
"watched_file_update_status": {
|
||||
"file_last_modified": "O arquivo <code class=\"file-path\"></code> foi modificado pela última vez em <span class=\"file-last-modified\"></span>.",
|
||||
"upload_modified_file": "Enviar arquivo modificado",
|
||||
"ignore_this_change": "Ignorar esta alteração"
|
||||
},
|
||||
"app_context": {
|
||||
"please_wait_for_save": "Por favor aguarde alguns segundos para finalizar a gravação, e então tente novamente."
|
||||
},
|
||||
"note_create": {
|
||||
"duplicated": "A nota \"{{title}}\" foi duplicada."
|
||||
},
|
||||
"image": {
|
||||
"copied-to-clipboard": "Uma referência para esta imagem foi copiada para a área de transferência. Isso pode ser colado em qualquer nota de texto.",
|
||||
"cannot-copy": "Não foi possível copiar a referência da imagem para a área de transferência."
|
||||
},
|
||||
"clipboard": {
|
||||
"cut": "Nota(s) recortadas(s) para a área de transferência.",
|
||||
"copied": "Not(s) copiada(s) para a área de transferência.",
|
||||
"copy_failed": "Não foi possível copiar para a área de transferência por problemas de permissão.",
|
||||
"copy_success": "Copiado para a área de transferência."
|
||||
},
|
||||
"entrypoints": {
|
||||
"note-revision-created": "A revisão da nota foi criada.",
|
||||
"note-executed": "Nota executada.",
|
||||
"sql-error": "Ocorreu um erro durante a execução da consulta SQL: {{message}}"
|
||||
},
|
||||
"branches": {
|
||||
"cannot-move-notes-here": "Não é possível mover notas para cá.",
|
||||
"delete-status": "Remover estado",
|
||||
"delete-notes-in-progress": "Exclusão de notas em andamento: {{count}}",
|
||||
"delete-finished-successfully": "Exclusão concluída com sucesso.",
|
||||
"undeleting-notes-in-progress": "Restauração de notas em andamento: {{count}}",
|
||||
"undeleting-notes-finished-successfully": "Restauração de notas concluída com sucesso."
|
||||
},
|
||||
"frontend_script_api": {
|
||||
"async_warning": "Você está passando uma função assíncrona para `api.runOnBackend()`, o que provavelmente não irá funcionar como esperado.\\nTorne a função síncrona (removendo a palavra-chave `async`), ou use `api.runAsyncOnBackendWithManualTransactionHandling()`.",
|
||||
"sync_warning": "Você está passando uma função síncrona para `api.runAsyncOnBackendWithManualTransactionHandling()`,\\nquando deveria usar `api.runOnBackend()` no lugar."
|
||||
},
|
||||
"ws": {
|
||||
"sync-check-failed": "A verificação de sincronização falhou!",
|
||||
"consistency-checks-failed": "A verificação de consistência falhou! Veja os logs para detalhes.",
|
||||
"encountered-error": "Encontrado o erro \"{{message}}\", verifique o console."
|
||||
},
|
||||
"hoisted_note": {
|
||||
"confirm_unhoisting": "A nota solicitada '{{requestedNote}}' está fora da árvore da nota fixada '{{hoistedNote}}' e você precisa desafixar para acessar a nota. Quer prosseguir e desafixar?"
|
||||
},
|
||||
"launcher_context_menu": {
|
||||
"reset_launcher_confirm": "Você deseja realmente reiniciar \"{{title}}\"? Todos os dados / configurações desta nota (e suas filhas) serão perdidos o lançador irá retornar para sua localização original.",
|
||||
"add-note-launcher": "Adicionar um lançador de nota",
|
||||
"add-script-launcher": "Adicionar um lançador de script",
|
||||
"add-custom-widget": "Adicionar um componente personalizado",
|
||||
"add-spacer": "Adicionar um espaçador",
|
||||
"delete": "Excluir <kbd data-command=\"deleteNotes\"></kbd>",
|
||||
"reset": "Reiniciar",
|
||||
"move-to-visible-launchers": "Mover para lançadores visíveis",
|
||||
"move-to-available-launchers": "Mover para lançadores disponíveis",
|
||||
"duplicate-launcher": "Duplicar o lançador <kbd data-command=\"duplicateSubtree\">"
|
||||
},
|
||||
"editable-text": {
|
||||
"auto-detect-language": "Detectado automaticamente"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "Blocos de Código",
|
||||
"description": "Controla o destaque de sintaxe para blocos de código dentro de notas de texto, notas de código não serão afetadas.",
|
||||
"color-scheme": "Esquema de Cor"
|
||||
},
|
||||
"classic_editor_toolbar": {
|
||||
"title": "Formatação"
|
||||
},
|
||||
"editor": {
|
||||
"title": "Editor"
|
||||
},
|
||||
"editing": {
|
||||
"editor_type": {
|
||||
"label": "Barra de formatação",
|
||||
"floating": {
|
||||
"title": "Flutuando",
|
||||
"description": "ferramentas de edição aparecem perto do cursor;"
|
||||
},
|
||||
"fixed": {
|
||||
"title": "Fixado",
|
||||
"description": "ferramentas de edição aparecem na aba de faixa \"Formatação\"."
|
||||
},
|
||||
"multiline-toolbar": "Exibir a barra de ferramentas em múltiplas linhas se não couber."
|
||||
}
|
||||
},
|
||||
"electron_context_menu": {
|
||||
"add-term-to-dictionary": "Adicionar \"{{term}}\" ao dicionário",
|
||||
"cut": "Cortar",
|
||||
"copy": "Copiar",
|
||||
"copy-link": "Copiar link",
|
||||
"paste": "Colar",
|
||||
"paste-as-plain-text": "Colar como texto sem formatação",
|
||||
"search_online": "Buscar por \"{{term}}\" usando {{searchEngine}}"
|
||||
},
|
||||
"image_context_menu": {
|
||||
"copy_reference_to_clipboard": "Copiar referência para a área de transferência",
|
||||
"copy_image_to_clipboard": "Copiar imagem para a área de transferência"
|
||||
},
|
||||
"link_context_menu": {
|
||||
"open_note_in_new_tab": "Abrir nota em nova aba",
|
||||
"open_note_in_new_split": "Abrir nota em nova divisão",
|
||||
"open_note_in_new_window": "Abrir nota em nova janela",
|
||||
"open_note_in_popup": "Edição rápida"
|
||||
},
|
||||
"electron_integration": {
|
||||
"desktop-application": "Aplicação Desktop",
|
||||
"native-title-bar": "Barra de título nativa",
|
||||
"native-title-bar-description": "Para Windows e macOS, manter a barra de título nativa desabilitada faz a aplicação parecer mais compacta. No Linux, manter a barra de título nativa habilitada faz a aplicação se integrar melhor com o restante do sistema.",
|
||||
"background-effects": "Habilitar efeitos de fundo (apenas Windows 11)",
|
||||
"background-effects-description": "O efeito Mica adicionar um fundo borrado e estilizado às janelas da aplicação, criando profundidade e um visual moderno.",
|
||||
"restart-app-button": "Reiniciar a aplicação para ver as alterações",
|
||||
"zoom-factor": "Fator de Zoom"
|
||||
},
|
||||
"note_autocomplete": {
|
||||
"search-for": "Buscar por \"{{term}}\"",
|
||||
"create-note": "Criar conectar nota filha \"{{term}}\"",
|
||||
"insert-external-link": "Inserir link externo para \"{{term}}\"",
|
||||
"clear-text-field": "Limpar campo de texto",
|
||||
"show-recent-notes": "Exibir notas recentes",
|
||||
"full-text-search": "Busca por texto completo"
|
||||
},
|
||||
"note_tooltip": {
|
||||
"note-has-been-deleted": "A nota foi excluída.",
|
||||
"quick-edit": "Edição rápida"
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Criar uma nova nota filha e adicioná-la ao mapa",
|
||||
"create-child-note-instruction": "Clique no mapa para criar uma nova nota naquele local ou tecle Esc para dispensar.",
|
||||
"unable-to-load-map": "Não foi possível carregar o mapa."
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Abrir local",
|
||||
"remove-from-map": "Remover do mapa",
|
||||
"add-note": "Adicionar um marcador neste local"
|
||||
},
|
||||
"help-button": {
|
||||
"title": "Abrir a página de ajuda relevante"
|
||||
},
|
||||
"duration": {
|
||||
"seconds": "Segundos",
|
||||
"minutes": "Minutos",
|
||||
"hours": "Horas",
|
||||
"days": "Dias"
|
||||
},
|
||||
"share": {
|
||||
"title": "Configurações de Compartilhamento",
|
||||
"redirect_bare_domain": "Redirecionar domínio puro para página Compartilhar",
|
||||
"redirect_bare_domain_description": "Redirecionar usuários anônimos para a página Compartilhar ao invés de exibir o Login",
|
||||
"show_login_link": "Exibir link para o Login no tema de Compartilhar",
|
||||
"show_login_link_description": "Adicionar um link de login no rodapé da página Compartilhar",
|
||||
"check_share_root": "Verificar Estado da Raiz do Compartilhar",
|
||||
"share_root_found": "Compartilhar nota raiz '{{noteTitle}}' está pronto",
|
||||
"share_root_not_found": "Nenhuma nota encontrada com a etiqueta #shareRoot",
|
||||
"share_root_not_shared": "A nota '{{noteTitle}}' possui a etiqueta #shareRoot mas não é compartilhada"
|
||||
},
|
||||
"time_selector": {
|
||||
"invalid_input": "O valor de tempo digitado não é um número válido.",
|
||||
"minimum_input": "O valor de tempo digitado deve ser de pelo menos {{minimumSeconds}} segundos."
|
||||
},
|
||||
"tasks": {
|
||||
"due": {
|
||||
"today": "Hoje",
|
||||
"tomorrow": "Amanhã",
|
||||
"yesterday": "Ontem"
|
||||
}
|
||||
},
|
||||
"content_widget": {
|
||||
"unknown_widget": "Componente desconhecido para \"{{id}}\"."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "Não atribuído",
|
||||
"configure-languages": "Configurar idiomas..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "Idiomas do conteúdo",
|
||||
"description": "Escolha um ou mais idiomas que devem aparecer na seleção de idioma na sessão de Propriedades Básicas de uma nota de texto somente leitura ou editável. Isso irá permitir recursos como verificação ortográfica ou suporte direta-para-esquerda."
|
||||
},
|
||||
"switch_layout_button": {
|
||||
"title_vertical": "Mover painel de edição para baixo",
|
||||
"title_horizontal": "Mover painel de edição para a esquerda"
|
||||
},
|
||||
"toggle_read_only_button": {
|
||||
"unlock-editing": "Desbloquear edição",
|
||||
"lock-editing": "Bloquear edição"
|
||||
},
|
||||
"png_export_button": {
|
||||
"button_title": "Exportar diagrama como PNG"
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "O diagrama não pôde ser exportado como PNG."
|
||||
},
|
||||
"code_theme": {
|
||||
"title": "Aparência",
|
||||
"word_wrapping": "Quebra automática de linha",
|
||||
"color-scheme": "Esquema de cor"
|
||||
},
|
||||
"cpu_arch_warning": {
|
||||
"title": "Por favor, baixe a versão ARM64",
|
||||
"message_macos": "O TriliumNext está rodando através de uma tradução Rosetta 2, o que significa que você está usando a versão Intel (x64) no Apple Silicon Mac. Isso afetará significativamente o desempenho e a duração da bateria.",
|
||||
"message_windows": "O TriliumNext está rodando através de emulação, o que significa que você está usando a versão Intel (x64) no Windows em um dispositivo ARM. Isso afetará significativamente o desempenho e a duração da bateria.",
|
||||
"recommendation": "Para uma melhor experiência, por favor, baixe a versão nativa ARM64 do TriliumNext na nossa página de releases.",
|
||||
"download_link": "Baixar Versão Nativa",
|
||||
"continue_anyway": "Continuar Assim Mesmo",
|
||||
"dont_show_again": "Não exibir este alerta novamente"
|
||||
},
|
||||
"editorfeatures": {
|
||||
"title": "Recursos",
|
||||
"emoji_completion_enabled": "Habilitar auto-completar de Emoji",
|
||||
"note_completion_enabled": "Habilitar auto-completar de notas"
|
||||
},
|
||||
"table_view": {
|
||||
"new-row": "Nova linha",
|
||||
"new-column": "Nova coluna",
|
||||
"sort-column-by": "Ordenar por \"{{title}}\"",
|
||||
"sort-column-ascending": "Crescente",
|
||||
"sort-column-descending": "Decrescente",
|
||||
"sort-column-clear": "Limpar ordenação",
|
||||
"hide-column": "Ocultar coluna \"{{title}}\"",
|
||||
"show-hide-columns": "Exibir/ocultar colunas",
|
||||
"row-insert-above": "Inserir linha acima",
|
||||
"row-insert-below": "Inserir linha abaixo",
|
||||
"row-insert-child": "Inserir nota filha",
|
||||
"add-column-to-the-left": "Adicionar coluna à esquerda",
|
||||
"add-column-to-the-right": "Adicionar coluna à direita",
|
||||
"edit-column": "Editar coluna",
|
||||
"delete_column_confirmation": "Tem certeza de que deseja excluir esta coluna? O atributo correspondente será removido de todas as notas.",
|
||||
"delete-column": "Excluir coluna",
|
||||
"new-column-label": "Título",
|
||||
"new-column-relation": "Relação"
|
||||
},
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "Ocultar fins de semana",
|
||||
"display-week-numbers": "Exibir números de semana",
|
||||
"map-style": "Estilo do mapa:",
|
||||
"max-nesting-depth": "Profundidade máxima de aninhamento:",
|
||||
"vector_light": "Vetor (Claro)",
|
||||
"vector_dark": "Vetor (Escuro)",
|
||||
"show-scale": "Exibir escala",
|
||||
"raster": "Raster"
|
||||
},
|
||||
"table_context_menu": {
|
||||
"delete_row": "Excluir linha"
|
||||
},
|
||||
"board_view": {
|
||||
"delete-note": "Excluir Nota",
|
||||
"move-to": "Mover para",
|
||||
"insert-above": "Inserir acima",
|
||||
"insert-below": "Inserir abaixo",
|
||||
"delete-column": "Excluir coluna",
|
||||
"delete-column-confirmation": "Tem certeza de que deseja excluir esta coluna? O atributo correspondente também será removido de todas as notas abaixo desta coluna.",
|
||||
"new-item": "Novo item",
|
||||
"add-column": "Adicionar Coluna"
|
||||
},
|
||||
"call_to_action": {
|
||||
"next_theme_title": "Testar no novo tema do Trilium",
|
||||
"next_theme_message": "Você está usando o tema legado, deseja experimentar o novo tema?",
|
||||
"next_theme_button": "Experimentar o novo tema",
|
||||
"background_effects_title": "Efeitos de fundo estão estáveis agora",
|
||||
"background_effects_message": "Em dispositivos Windows, efeitos de fundo estão estáveis agora. Os efeitos de fundo adicionam um toque de cor à interface do usuário borrando o plano de fundo atrás dela. Esta técnica também é usada em outras aplicações como o Windows Explorer.",
|
||||
"background_effects_button": "Habilitar os efeitos de fundo",
|
||||
"dismiss": "Dispensar"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Configurações relacionadas"
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Esquema de cores para blocos de código em notas de texto",
|
||||
"related_code_notes": "Esquema de cores para notas de código"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
},
|
||||
"book": {
|
||||
"no_children_help": "Esta coleção não possui nenhum nota filha, então não há nada para exibir. Veja <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> para detalhes."
|
||||
},
|
||||
"render": {
|
||||
"note_detail_render_help_1": "Esta nota de ajuda é mostrada porque esta nota do tipo Renderizar HTML não possui a relação necessária para funcionar corretamente.",
|
||||
"note_detail_render_help_2": "O tipo de nota Renderizar HTML é usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">automação</a>. Em suma, você tem uma nota de código HTML (opcionalmente com algum JavaScript) e esta nota irá renderizá-la. Para fazê-lo funcionar, você precisa definir uma <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relação</a> chamada \"renderNote\" apontando para a nota HTML a ser renderizada."
|
||||
},
|
||||
"etapi": {
|
||||
"title": "ETAPI",
|
||||
"description": "ETAPI é uma API REST usada para acessar a instância do Trilium programaticamente, sem interface gráfica.",
|
||||
"see_more": "Veja mais detalhes no {{- link_to_wiki}}, na {{- link_to_openapi_spec}} ou na {{- link_to_swagger_ui}}.",
|
||||
"wiki": "wiki",
|
||||
"openapi_spec": "Especificação OpenAPI do ETAPI",
|
||||
"swagger_ui": "ETAPI Swagger UI",
|
||||
"create_token": "Criar novo token ETAPI",
|
||||
"existing_tokens": "Tokens existentes",
|
||||
"no_tokens_yet": "Ainda não existem tokens. Clique no botão acima para criar um.",
|
||||
"token_name": "Nome do token",
|
||||
"created": "Criado",
|
||||
"actions": "Ações",
|
||||
"new_token_title": "Novo token ETAPI",
|
||||
"new_token_message": "Por favor, insira o nome do novo token",
|
||||
"default_token_name": "novo token",
|
||||
"error_empty_name": "O nome do token não pode ser vazio",
|
||||
"token_created_title": "Token ETAPI criado",
|
||||
"token_created_message": "Copie o token criado para a área de transferência. O Trilium armazena o token em hash e esta é a última vez que você poderá visualizá-lo.",
|
||||
"rename_token": "Renomear este token",
|
||||
"delete_token": "Excluir / desativar este token",
|
||||
"rename_token_title": "Renomear token",
|
||||
"rename_token_message": "Por favor, insira o nome do novo token",
|
||||
"delete_token_confirmation": "Tem certeza de que deseja excluir o token ETAPI \"{{name}}\"?"
|
||||
},
|
||||
"options_widget": {
|
||||
"options_status": "Estado das opções",
|
||||
"options_change_saved": "As alterações nas opções foram salvas."
|
||||
},
|
||||
"password": {
|
||||
"heading": "Senha",
|
||||
"alert_message": "Por favor, certifique-se de lembrar sua nova senha. A senha é usada para acessar a interface web e para criptografar notas protegidas. Se você esquecer sua senha, todas as suas notas protegidas serão permanentemente perdidas.",
|
||||
"reset_link": "Clique aqui para redefiní-la.",
|
||||
"old_password": "Senha antiga",
|
||||
"new_password": "Nova senha",
|
||||
"new_password_confirmation": "Confirmação da nova senha",
|
||||
"change_password": "Alterar senha",
|
||||
"protected_session_timeout": "Tempo Limite da Sessão Protegida",
|
||||
"protected_session_timeout_description": "O tempo limite da sessão protegida é o período após o qual a sessão protegida é apagada da memória do navegador. Esse tempo é contado a partir da última interação com notas protegidas. Veja",
|
||||
"wiki": "wiki",
|
||||
"for_more_info": "para mais informações.",
|
||||
"protected_session_timeout_label": "Tempo limite da sessão protegida:",
|
||||
"reset_confirmation": "Ao redefinir a senha, você perderá para sempre o acesso a todas as suas notas protegidas existentes. Tem certeza de que deseja redefinir a senha?",
|
||||
"reset_success_message": "A senha foi redefinida. Por favor, defina uma nova senha",
|
||||
"change_password_heading": "Alterar Senha",
|
||||
"set_password_heading": "Definir Senha",
|
||||
"set_password": "Definir Senha",
|
||||
"password_mismatch": "As novas senhas não são iguais.",
|
||||
"password_changed_success": "A senha foi alterada. O Trilium será recarregado após você pressionar OK."
|
||||
},
|
||||
"multi_factor_authentication": {
|
||||
"title": "Autenticação Multifator",
|
||||
"description": "A Autenticação Multifator (MFA) adiciona uma camada extra de segurança à sua conta. Em vez de apenas digitar uma senha para fazer login, a MFA exige que você forneça uma ou mais evidências adicionais para verificar sua identidade. Dessa forma, mesmo que alguém obtenha sua senha, ainda não poderá acessar sua conta sem a segunda informação. É como adicionar uma fechadura extra à sua porta, tornando muito mais difícil para qualquer outra pessoa invadir.<br><br>Siga as instruções abaixo para ativar a MFA. Se a configuração não for feita corretamente, o login voltará a usar apenas a senha.",
|
||||
"mfa_enabled": "Habilitar a Autenticação Multifator",
|
||||
"mfa_method": "Método MFA",
|
||||
"electron_disabled": "A Autenticação Multifator não é suportada na versão desktop atualmente.",
|
||||
"totp_title": "Senha Única Temporária baseada em Tempo (TOTP)",
|
||||
"totp_description": "\"TOTP (Senha Única Temporária baseada em Tempo) é um recurso de segurança que gera um código único e temporário, que muda a cada 30 segundos. Você utiliza esse código junto com sua senha para fazer login na conta, tornando muito mais difícil para qualquer outra pessoa acessá-la.",
|
||||
"totp_secret_title": "Gerar segredo TOTP",
|
||||
"totp_secret_generate": "Gerar segredo TOTP",
|
||||
"totp_secret_regenerate": "Regenerar segredo TOTP",
|
||||
"no_totp_secret_warning": "Para habilitar o TOTP, você precisa primeiro gerar um segredo TOTP.",
|
||||
"totp_secret_description_warning": "Após gerar um novo segredo TOTP, será necessário fazer login novamente com o novo segredo TOTP.",
|
||||
"totp_secret_generated": "Segredo TOTP gerado",
|
||||
"totp_secret_warning": "Por favor, salve o segredo gerado em um local seguro. Ele não será exibido novamente.",
|
||||
"totp_secret_regenerate_confirm": "Tem certeza de que deseja regenerar o segredo TOTP? Isso invalidará o segredo TOTP anterior e todos os códigos de recuperação existentes.",
|
||||
"recovery_keys_title": "Chaves de Recuperação do Single Sign-On",
|
||||
"recovery_keys_description": "As chaves de recuperação do Single Sign-On são usadas para fazer login caso você não consiga acessar seus códigos do Authenticator.",
|
||||
"recovery_keys_description_warning": "As chaves de recuperação não serão exibidas novamente após sair da página. Mantenha-as em um local seguro.<br>Após o uso, uma chave de recuperação não poderá ser usada novamente.",
|
||||
"recovery_keys_error": "Erro ao gerar códigos de recuperação",
|
||||
"recovery_keys_no_key_set": "Nenhum código de recuperação definido",
|
||||
"recovery_keys_generate": "Gerar Códigos de Recuperação",
|
||||
"recovery_keys_regenerate": "Regenerar Códigos de Recuperação",
|
||||
"recovery_keys_used": "Usado: {{date}}",
|
||||
"recovery_keys_unused": "Código de recuperação {{index}} não utilizado",
|
||||
"oauth_title": "OAuth/OpenID",
|
||||
"oauth_description": "OpenID é uma forma padronizada de permitir que você faça login em sites usando uma conta de outro serviço, como o Google, para verificar sua identidade. O emissor padrão é o Google, mas você pode alterá-lo para qualquer outro provedor OpenID. Consulte <a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">aqui</a> para mais informações. Siga estas <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">instruções</a> para configurar um serviço OpenID através do Google.",
|
||||
"oauth_description_warning": "Para habilitar o OAuth/OpenID, você precisa definir a URL base do OAuth/OpenID, o client ID e o client secret no arquivo config.ini e reiniciar a aplicação. Se quiser configurar via variáveis de ambiente, defina TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID e TRILIUM_OAUTH_CLIENT_SECRET.",
|
||||
"oauth_missing_vars": "Configurações ausentes: {{variables}}",
|
||||
"oauth_user_account": "Conta do Usuário: ",
|
||||
"oauth_user_email": "E-mail do Usuário: ",
|
||||
"oauth_user_not_logged_in": "Não está logado!"
|
||||
},
|
||||
"shortcuts": {
|
||||
"keyboard_shortcuts": "Atalhos de Teclado",
|
||||
"multiple_shortcuts": "Atalhos múltiplos para a mesma ação podem ser separados por vírgula.",
|
||||
"electron_documentation": "Consulte a <a href=\"https://www.electronjs.org/docs/latest/api/accelerator\\\">documentação do Electron</a> para ver os modificadores e códigos de teclas disponíveis.",
|
||||
"action_name": "Nome da ação",
|
||||
"default_shortcuts": "Atalhos padrão",
|
||||
"type_text_to_filter": "Digite para filtrar atalhos...",
|
||||
"description": "Descrição",
|
||||
"reload_app": "Recarregar aplicação para aplicar alterações",
|
||||
"set_all_to_default": "Redefinir todos os atalhos para o padrão",
|
||||
"confirm_reset": "Tem certeza de que deseja redefinir todos os atalhos de teclado para o padrão?",
|
||||
"shortcuts": "Atalhos"
|
||||
},
|
||||
"spellcheck": {
|
||||
"title": "Verificação Ortográfica",
|
||||
"description": "Estas opções se aplicam apenas às versões desktop; os navegadores usarão sua própria verificação ortográfica nativa.",
|
||||
"enable": "Habilitar verificação ortográfica",
|
||||
"language_code_label": "Código(s) de idioma",
|
||||
"language_code_placeholder": "por exemplo \"en-US\", \"de-AT\", \"pt-BR\"",
|
||||
"multiple_languages_info": "Múltiplos idiomas podem ser separados por vírgula, por exemplo: \"en-US, de-DE, pt-BR, cs\". ",
|
||||
"available_language_codes_label": "Códigos de idioma disponíveis:",
|
||||
"restart-required": "As alterações nas opções de verificação ortográfica terão efeito após reiniciar o aplicativo."
|
||||
},
|
||||
"sync_2": {
|
||||
"config_title": "Configuração da Sincronização",
|
||||
"server_address": "Endereço da instância do Servidor",
|
||||
"timeout": "Tempo limite da sincronização",
|
||||
"timeout_unit": "milisegundos",
|
||||
"proxy_label": "Servidor proxy para sincronização (opcional)",
|
||||
"note": "Nota",
|
||||
"note_description": "Se você deixar a configuração de proxy em branco, o proxy do sistema será usado (aplica-se apenas à versão desktop/Electron).",
|
||||
"special_value_description": "Outro valor especial é <code>noproxy</code>, que força a ignorar até mesmo o proxy do sistema e respeita <code>NODE_TLS_REJECT_UNAUTHORIZED</code>.",
|
||||
"save": "Salvar",
|
||||
"help": "Ajuda",
|
||||
"test_title": "Teste de Sincronização",
|
||||
"test_description": "Isso testará a conexão e o handshake com o servidor de sincronização. Se o servidor de sincronização não estiver inicializado, isso irá configurá-lo para sincronizar com o documento local.",
|
||||
"test_button": "Testar sincronização",
|
||||
"handshake_failed": "Falha no handshake com o servidor de sincronização, erro: {{message}}"
|
||||
},
|
||||
"shared_info": {
|
||||
"help_link": "Para ajuda, visite a <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>.",
|
||||
"shared_publicly": "Esta nota é compartilhada publicamente em {{- link}}.",
|
||||
"shared_locally": "Esta nota é compartilhada localmente em {{- link}}."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1106,7 +1106,7 @@
|
||||
"limit_description": "Limitează numărul de rezultate",
|
||||
"order_by": "ordonează după",
|
||||
"save_to_note": "Salvează în notiță",
|
||||
"search_button": "Căutare <kbd>Enter</kbd>",
|
||||
"search_button": "Căutare",
|
||||
"search_execute": "Caută și execută acțiunile",
|
||||
"search_note_saved": "Notița de căutare a fost salvată în {{- notePathTitle}}",
|
||||
"search_parameters": "Parametrii de căutare",
|
||||
@@ -1373,8 +1373,8 @@
|
||||
},
|
||||
"shared_info": {
|
||||
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
|
||||
"shared_locally": "Această notiță este partajată local la",
|
||||
"shared_publicly": "Această notiță este partajată public la"
|
||||
"shared_locally": "Această notiță este partajată local la {{- link}}",
|
||||
"shared_publicly": "Această notiță este partajată public la {{- link}}"
|
||||
},
|
||||
"note_types": {
|
||||
"book": "Colecție",
|
||||
|
||||
@@ -742,7 +742,8 @@
|
||||
"save-changes": "Сохранить и применить изменения",
|
||||
"saved-search-note-refreshed": "Сохраненная поисковая заметка обновлена.",
|
||||
"refresh-saved-search-results": "Обновить сохраненные результаты поиска",
|
||||
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве."
|
||||
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве.",
|
||||
"toggle-sidebar": "Переключить боковую панель"
|
||||
},
|
||||
"quick-search": {
|
||||
"no-results": "Результаты не найдены",
|
||||
@@ -967,7 +968,8 @@
|
||||
"editable": "Изменяемое",
|
||||
"language": "Язык",
|
||||
"note_type": "Тип",
|
||||
"basic_properties": "Общее"
|
||||
"basic_properties": "Общее",
|
||||
"configure_code_notes": "Настроить заметки типа \"Код\"..."
|
||||
},
|
||||
"book_properties": {
|
||||
"grid": "Сетка",
|
||||
@@ -1060,7 +1062,7 @@
|
||||
"fast_search": "быстрый поиск",
|
||||
"include_archived": "включать архивированные",
|
||||
"order_by": "сортировать по",
|
||||
"search_button": "Поиск <kbd>enter</kbd>",
|
||||
"search_button": "Поиск",
|
||||
"search_parameters": "Параметры поиска",
|
||||
"ancestor": "предок",
|
||||
"action": "действие",
|
||||
@@ -1973,8 +1975,8 @@
|
||||
},
|
||||
"shared_info": {
|
||||
"help_link": "Для получения справки посетите <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">вики</a>.",
|
||||
"shared_locally": "Заметка общедоступна локально в",
|
||||
"shared_publicly": "Заметка общедоступна публично в"
|
||||
"shared_locally": "Заметка общедоступна локально в {{- link}}.",
|
||||
"shared_publicly": "Заметка общедоступна публично в {{- link}}."
|
||||
},
|
||||
"note_create": {
|
||||
"duplicated": "Создан дубль заметки \"{{title}}\"."
|
||||
|
||||
@@ -729,7 +729,8 @@
|
||||
"note_type": "筆記類型",
|
||||
"editable": "可編輯",
|
||||
"basic_properties": "基本屬性",
|
||||
"language": "語言"
|
||||
"language": "語言",
|
||||
"configure_code_notes": "配寘代碼注釋..."
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "視圖類型",
|
||||
@@ -845,7 +846,7 @@
|
||||
"debug": "除錯",
|
||||
"debug_description": "除錯將顯示額外的除錯資訊至控制台,以幫助除錯複雜查詢",
|
||||
"action": "操作",
|
||||
"search_button": "搜尋 <kbd>Enter</kbd>",
|
||||
"search_button": "搜尋",
|
||||
"search_execute": "搜尋並執行操作",
|
||||
"save_to_note": "儲存至筆記",
|
||||
"search_parameters": "搜尋參數",
|
||||
@@ -1398,9 +1399,9 @@
|
||||
"open-in-popup": "快速編輯"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "此筆記已公開分享在",
|
||||
"shared_locally": "此筆記已在本地分享在",
|
||||
"help_link": "如需幫助,請訪問 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>。"
|
||||
"help_link": "如需幫助,請訪問 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>。",
|
||||
"shared_publicly": "此筆記已公開分享於 {{- link}}。",
|
||||
"shared_locally": "此筆記已於本地分享至 {{- link}}。"
|
||||
},
|
||||
"note_types": {
|
||||
"text": "文字",
|
||||
@@ -1478,7 +1479,8 @@
|
||||
"hoist-this-note-workspace": "聚焦此筆記(工作區)",
|
||||
"refresh-saved-search-results": "重新整理儲存的搜尋結果",
|
||||
"create-child-note": "建立子筆記",
|
||||
"unhoist": "取消聚焦"
|
||||
"unhoist": "取消聚焦",
|
||||
"toggle-sidebar": "切換側邊欄"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "保持此視窗置頂"
|
||||
|
||||
@@ -960,7 +960,7 @@
|
||||
"debug": "debug",
|
||||
"debug_description": "Debug виведе додаткову інформацію для налагодження в консоль, щоб допомогти у налагодженні складних запитів",
|
||||
"action": "дія",
|
||||
"search_button": "Пошук <kbd>enter</kbd>",
|
||||
"search_button": "Пошук",
|
||||
"search_execute": "Пошук & Виконання дій",
|
||||
"save_to_note": "Зберегти до нотатки",
|
||||
"search_parameters": "Параметри пошуку",
|
||||
@@ -1909,8 +1909,8 @@
|
||||
"open-in-popup": "Швидке редагування"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Ця нотатка опублікована на",
|
||||
"shared_locally": "Цю нотатку опубліковано локально на",
|
||||
"shared_publicly": "Ця нотатка опублікована на {{- link}}",
|
||||
"shared_locally": "Цю нотатку опубліковано локально на {{- link}}",
|
||||
"help_link": "Щоб отримати допомогу, відвідайте <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">вікі</a>."
|
||||
},
|
||||
"note_types": {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"about": {
|
||||
"homepage": "Trang chủ:",
|
||||
"title": "Về Trilium Notes"
|
||||
"title": "Về Trilium Notes",
|
||||
"app_version": "Phiên bản:",
|
||||
"db_version": "Phiên bản DB:",
|
||||
"sync_version": "Phiên bản liên kết:"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Thêm liên kết",
|
||||
@@ -26,7 +29,8 @@
|
||||
"close": "Đóng"
|
||||
},
|
||||
"help": {
|
||||
"other": "Khác"
|
||||
"other": "Khác",
|
||||
"notSet": "chưa được đặt"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
@@ -69,12 +73,16 @@
|
||||
"add_label": {
|
||||
"add_label": "Thêm nhãn",
|
||||
"label_name_placeholder": "tên nhãn",
|
||||
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn"
|
||||
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn",
|
||||
"new_value_placeholder": "giá trị mới"
|
||||
},
|
||||
"rename_label": {
|
||||
"rename_label": "Đặt lại tên nhãn"
|
||||
},
|
||||
"call_to_action": {
|
||||
"dismiss": "Bỏ qua"
|
||||
},
|
||||
"abstract_search_option": {
|
||||
"remove_this_search_option": "Xoá lựa chọn tìm kiếm này"
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/client/src/types-fancytree.d.ts
vendored
2
apps/client/src/types-fancytree.d.ts
vendored
@@ -113,7 +113,7 @@ declare namespace Fancytree {
|
||||
generateFormElements(selected?: boolean, active?: boolean): void;
|
||||
|
||||
/** Return the currently active node or null. */
|
||||
getActiveNode(): FancytreeNode;
|
||||
getActiveNode(): FancytreeNode | null;
|
||||
|
||||
/** Return the first top level node if any (not the invisible root node). */
|
||||
getFirstChild(): FancytreeNode;
|
||||
|
||||
@@ -3,7 +3,11 @@ type DateTimeStyle = "full" | "long" | "medium" | "short" | "none" | undefined;
|
||||
/**
|
||||
* Formats the given date and time to a string based on the current locale.
|
||||
*/
|
||||
export function formatDateTime(date: string | Date | number, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") {
|
||||
export function formatDateTime(date: string | Date | number | null | undefined, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") {
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const locale = navigator.language;
|
||||
|
||||
let parsedDate;
|
||||
|
||||
163
apps/client/src/widgets/FloatingButtons.css
Normal file
163
apps/client/src/widgets/FloatingButtons.css
Normal file
@@ -0,0 +1,163 @@
|
||||
/* #region Generic floating buttons styles */
|
||||
.floating-buttons {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floating-buttons-children,
|
||||
.show-floating-buttons {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.note-split.rtl .floating-buttons-children,
|
||||
.note-split.rtl .show-floating-buttons {
|
||||
right: unset;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.note-split.rtl .close-floating-buttons {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.note-split.rtl .close-floating-buttons,
|
||||
.note-split.rtl .show-floating-buttons {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.type-canvas .floating-buttons-children {
|
||||
top: 70px;
|
||||
}
|
||||
|
||||
.type-canvas .floating-buttons-children > * {
|
||||
--border-radius: 0; /* Overridden by themes */
|
||||
}
|
||||
|
||||
.floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.floating-buttons-children > *:not(.has-overflow) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.floating-buttons-children > button, .floating-buttons-children .floating-button {
|
||||
font-size: 150%;
|
||||
padding: 5px 10px 4px 10px;
|
||||
width: 40px;
|
||||
cursor: pointer;
|
||||
color: var(--button-text-color);
|
||||
background: var(--button-background-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.floating-buttons-children > button:hover, .floating-buttons-children .floating-button:hover {
|
||||
text-decoration: none;
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.floating-buttons .floating-buttons-children.temporarily-hidden {
|
||||
display: none;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region Show floating button */
|
||||
.floating-buttons-children.temporarily-hidden+.show-floating-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.show-floating-buttons {
|
||||
/* display: none;*/
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
.show-floating-buttons-button {
|
||||
border: 1px solid transparent;
|
||||
color: var(--button-text-color);
|
||||
padding: 6px;
|
||||
border-radius: 100px !important;
|
||||
}
|
||||
|
||||
.show-floating-buttons-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region Geo map buttons */
|
||||
.leaflet-pane {
|
||||
z-index: 50;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region Close floating buttons */
|
||||
.close-floating-buttons {
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
.close-floating-buttons:first-child {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.close-floating-buttons-button {
|
||||
border: 1px solid transparent;
|
||||
color: var(--button-text-color);
|
||||
padding: 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.close-floating-buttons-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region Backlinks */
|
||||
.backlinks-widget {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.backlinks-ticker {
|
||||
border-radius: 10px;
|
||||
border-color: var(--main-border-color);
|
||||
background-color: var(--more-accented-background-color);
|
||||
padding: 4px 10px 4px 10px;
|
||||
opacity: 90%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.backlinks-count {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.backlinks-items {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 10px;
|
||||
width: 400px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--accented-background-color);
|
||||
color: var(--main-text-color);
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.backlink-excerpt {
|
||||
border-left: 2px solid var(--main-border-color);
|
||||
padding-left: 10px;
|
||||
opacity: 80%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.backlink-excerpt .backlink-link { /* the actual backlink */
|
||||
font-weight: bold;
|
||||
background-color: yellow;
|
||||
}
|
||||
/* #endregion */
|
||||
94
apps/client/src/widgets/FloatingButtons.tsx
Normal file
94
apps/client/src/widgets/FloatingButtons.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { t } from "i18next";
|
||||
import "./FloatingButtons.css";
|
||||
import { useNoteContext, useNoteLabel, useNoteLabelBoolean } from "./react/hooks";
|
||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||
import { ParentComponent } from "./react/react_utils";
|
||||
import { EventData, EventNames } from "../components/app_context";
|
||||
import { type FloatingButtonsList, type FloatingButtonContext } from "./FloatingButtonsDefinitions";
|
||||
import ActionButton from "./react/ActionButton";
|
||||
import { ViewTypeOptions } from "../services/note_list_renderer";
|
||||
|
||||
interface FloatingButtonsProps {
|
||||
items: FloatingButtonsList;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note:
|
||||
*
|
||||
* For floating button widgets that require content to overflow, the has-overflow CSS class should
|
||||
* be applied to the root element of the widget. Additionally, this root element may need to
|
||||
* properly handle rounded corners, as defined by the --border-radius CSS variable.
|
||||
*/
|
||||
export default function FloatingButtons({ items }: FloatingButtonsProps) {
|
||||
const { note, noteContext } = useNoteContext();
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||
const context = useMemo<FloatingButtonContext | null>(() => {
|
||||
if (!note || !noteContext || !parentComponent) return null;
|
||||
|
||||
return {
|
||||
note,
|
||||
noteContext,
|
||||
parentComponent,
|
||||
isDefaultViewMode: noteContext.viewScope?.viewMode === "default",
|
||||
viewType: viewType as ViewTypeOptions,
|
||||
isReadOnly,
|
||||
triggerEvent<T extends EventNames>(name: T, data?: Omit<EventData<T>, "ntxId">) {
|
||||
parentComponent.triggerEvent(name, {
|
||||
ntxId: noteContext.ntxId,
|
||||
...data
|
||||
} as EventData<T>);
|
||||
}
|
||||
};
|
||||
}, [ note, noteContext, parentComponent, viewType, isReadOnly ]);
|
||||
|
||||
// Manage the user-adjustable visibility of the floating buttons.
|
||||
const [ visible, setVisible ] = useState(true);
|
||||
useEffect(() => setVisible(true), [ note ]);
|
||||
|
||||
return (
|
||||
<div className="floating-buttons no-print">
|
||||
<div className={`floating-buttons-children ${!visible ? "temporarily-hidden" : ""}`}>
|
||||
{context && items.map((Component) => (
|
||||
<Component {...context} />
|
||||
))}
|
||||
|
||||
{visible && <CloseFloatingButton setVisible={setVisible} />}
|
||||
</div>
|
||||
|
||||
{!visible && <ShowFloatingButton setVisible={setVisible} /> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show button that displays floating button after click on close button
|
||||
*/
|
||||
function ShowFloatingButton({ setVisible }: { setVisible(visible: boolean): void }) {
|
||||
return (
|
||||
<div className="show-floating-buttons">
|
||||
<ActionButton
|
||||
className="show-floating-buttons-button"
|
||||
icon="bx bx-chevrons-left"
|
||||
text={t("show_floating_buttons_button.button_title")}
|
||||
onClick={() => setVisible(true)}
|
||||
noIconActionClass
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CloseFloatingButton({ setVisible }: { setVisible(visible: boolean): void }) {
|
||||
return (
|
||||
<div className="close-floating-buttons">
|
||||
<ActionButton
|
||||
className="close-floating-buttons-button"
|
||||
icon="bx bx-chevrons-right"
|
||||
text={t("hide_floating_buttons_button.button_title")}
|
||||
onClick={() => setVisible(false)}
|
||||
noIconActionClass
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
398
apps/client/src/widgets/FloatingButtonsDefinitions.tsx
Normal file
398
apps/client/src/widgets/FloatingButtonsDefinitions.tsx
Normal file
@@ -0,0 +1,398 @@
|
||||
import { VNode } from "preact";
|
||||
import appContext, { EventData, EventNames } from "../components/app_context";
|
||||
import Component from "../components/component";
|
||||
import NoteContext from "../components/note_context";
|
||||
import FNote from "../entities/fnote";
|
||||
import ActionButton, { ActionButtonProps } from "./react/ActionButton";
|
||||
import { useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
|
||||
import server from "../services/server";
|
||||
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
|
||||
import toast from "../services/toast";
|
||||
import { t } from "../services/i18n";
|
||||
import { copyImageReferenceToClipboard } from "../services/image";
|
||||
import tree from "../services/tree";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
import options from "../services/options";
|
||||
import { getHelpUrlForNote } from "../services/in_app_help";
|
||||
import froca from "../services/froca";
|
||||
import NoteLink from "./react/NoteLink";
|
||||
import RawHtml from "./react/RawHtml";
|
||||
import { ViewTypeOptions } from "../services/note_list_renderer";
|
||||
|
||||
export interface FloatingButtonContext {
|
||||
parentComponent: Component;
|
||||
note: FNote;
|
||||
noteContext: NoteContext;
|
||||
isDefaultViewMode: boolean;
|
||||
isReadOnly: boolean;
|
||||
/** Shorthand for triggering an event from the parent component. The `ntxId` is automatically handled for convenience. */
|
||||
triggerEvent<T extends EventNames>(name: T, data?: Omit<EventData<T>, "ntxId">): void;
|
||||
viewType?: ViewTypeOptions | null;
|
||||
}
|
||||
|
||||
function FloatingButton({ className, ...props }: ActionButtonProps) {
|
||||
return <ActionButton
|
||||
className={`floating-button ${className ?? ""}`}
|
||||
noIconActionClass
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
|
||||
export type FloatingButtonsList = ((context: FloatingButtonContext) => false | VNode)[];
|
||||
|
||||
export const DESKTOP_FLOATING_BUTTONS: FloatingButtonsList = [
|
||||
RefreshBackendLogButton,
|
||||
SwitchSplitOrientationButton,
|
||||
ToggleReadOnlyButton,
|
||||
EditButton,
|
||||
ShowTocWidgetButton,
|
||||
ShowHighlightsListWidgetButton,
|
||||
RunActiveNoteButton,
|
||||
OpenTriliumApiDocsButton,
|
||||
SaveToNoteButton,
|
||||
RelationMapButtons,
|
||||
GeoMapButtons,
|
||||
CopyImageReferenceButton,
|
||||
ExportImageButtons,
|
||||
InAppHelpButton,
|
||||
Backlinks
|
||||
];
|
||||
|
||||
export const MOBILE_FLOATING_BUTTONS: FloatingButtonsList = [
|
||||
RefreshBackendLogButton,
|
||||
EditButton,
|
||||
RelationMapButtons,
|
||||
ExportImageButtons,
|
||||
Backlinks
|
||||
]
|
||||
|
||||
function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const isEnabled = note.noteId === "_backendLog" && isDefaultViewMode;
|
||||
return isEnabled && <FloatingButton
|
||||
text={t("backend_log.refresh")}
|
||||
icon="bx bx-refresh"
|
||||
onClick={() => parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })}
|
||||
/>
|
||||
}
|
||||
|
||||
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const isEnabled = note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode;
|
||||
const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
|
||||
const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal";
|
||||
|
||||
return isEnabled && <FloatingButton
|
||||
text={upcomingOrientation === "vertical" ? t("switch_layout_button.title_vertical") : t("switch_layout_button.title_horizontal")}
|
||||
icon={upcomingOrientation === "vertical" ? "bx bxs-dock-bottom" : "bx bxs-dock-left"}
|
||||
onClick={() => setSplitEditorOrientation(upcomingOrientation)}
|
||||
/>
|
||||
}
|
||||
|
||||
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||
const isEnabled = (note.type === "mermaid" || viewType === "geoMap")
|
||||
&& note.isContentAvailable() && isDefaultViewMode;
|
||||
|
||||
return isEnabled && <FloatingButton
|
||||
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
|
||||
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
|
||||
onClick={() => setReadOnly(!isReadOnly)}
|
||||
/>
|
||||
}
|
||||
|
||||
function EditButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const [ animationClass, setAnimationClass ] = useState("");
|
||||
const [ isEnabled, setIsEnabled ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
noteContext.isReadOnly().then(isReadOnly => {
|
||||
setIsEnabled(
|
||||
isDefaultViewMode
|
||||
&& (!note.isProtected || protected_session_holder.isProtectedSessionAvailable())
|
||||
&& !options.is("databaseReadonly")
|
||||
&& isReadOnly
|
||||
);
|
||||
});
|
||||
}, [ note ]);
|
||||
|
||||
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
|
||||
if (noteContext?.ntxId === eventNoteContext.ntxId) {
|
||||
setIsEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
// make the edit button stand out on the first display, otherwise
|
||||
// it's difficult to notice that the note is readonly
|
||||
useEffect(() => {
|
||||
if (isEnabled) {
|
||||
setAnimationClass("bx-tada bx-lg");
|
||||
setTimeout(() => {
|
||||
setAnimationClass("");
|
||||
}, 1700);
|
||||
}
|
||||
}, [ isEnabled ]);
|
||||
|
||||
return isEnabled && <FloatingButton
|
||||
text={t("edit_button.edit_this_note")}
|
||||
icon="bx bx-pencil"
|
||||
className={animationClass}
|
||||
onClick={() => {
|
||||
if (noteContext.viewScope) {
|
||||
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
|
||||
appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
function ShowTocWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const [ isEnabled, setIsEnabled ] = useState(false);
|
||||
useTriliumEvent("reEvaluateTocWidgetVisibility", () => {
|
||||
setIsEnabled(note.type === "text" && isDefaultViewMode && !!noteContext.viewScope?.tocTemporarilyHidden);
|
||||
});
|
||||
|
||||
return isEnabled && <FloatingButton
|
||||
text={t("show_toc_widget_button.show_toc")}
|
||||
icon="bx bx-tn-toc"
|
||||
onClick={() => {
|
||||
if (noteContext?.viewScope && noteContext.noteId) {
|
||||
noteContext.viewScope.tocTemporarilyHidden = false;
|
||||
appContext.triggerEvent("showTocWidget", { noteId: noteContext.noteId });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
function ShowHighlightsListWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const [ isEnabled, setIsEnabled ] = useState(false);
|
||||
useTriliumEvent("reEvaluateHighlightsListWidgetVisibility", () => {
|
||||
setIsEnabled(note.type === "text" && isDefaultViewMode && !!noteContext.viewScope?.highlightsListTemporarilyHidden);
|
||||
});
|
||||
|
||||
return isEnabled && <FloatingButton
|
||||
text={t("show_highlights_list_widget_button.show_highlights_list")}
|
||||
icon="bx bx-bookmarks"
|
||||
onClick={() => {
|
||||
if (noteContext?.viewScope && noteContext.noteId) {
|
||||
noteContext.viewScope.highlightsListTemporarilyHidden = false;
|
||||
appContext.triggerEvent("showHighlightsListWidget", { noteId: noteContext.noteId });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
function RunActiveNoteButton({ note }: FloatingButtonContext) {
|
||||
const isEnabled = note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium";
|
||||
return isEnabled && <FloatingButton
|
||||
icon="bx bx-play"
|
||||
text={t("code_buttons.execute_button_title")}
|
||||
triggerCommand="runActiveNote"
|
||||
/>
|
||||
}
|
||||
|
||||
function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) {
|
||||
const isEnabled = note.mime.startsWith("application/javascript;env=");
|
||||
return isEnabled && <FloatingButton
|
||||
icon="bx bx-help-circle"
|
||||
text={t("code_buttons.trilium_api_docs_button_title")}
|
||||
onClick={() => openInAppHelpFromUrl(note.mime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")}
|
||||
/>
|
||||
}
|
||||
|
||||
function SaveToNoteButton({ note }: FloatingButtonContext) {
|
||||
const isEnabled = note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely();
|
||||
return isEnabled && <FloatingButton
|
||||
icon="bx bx-save"
|
||||
text={t("code_buttons.save_to_note_button_title")}
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId });
|
||||
if (notePath) {
|
||||
toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) }));
|
||||
// TODO: This hangs the navigation, for some reason.
|
||||
//await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
function RelationMapButtons({ note, triggerEvent }: FloatingButtonContext) {
|
||||
const isEnabled = (note.type === "relationMap");
|
||||
return isEnabled && (
|
||||
<>
|
||||
<FloatingButton
|
||||
icon="bx bx-folder-plus"
|
||||
text={t("relation_map_buttons.create_child_note_title")}
|
||||
onClick={() => triggerEvent("relationMapCreateChildNote")}
|
||||
/>
|
||||
|
||||
<FloatingButton
|
||||
icon="bx bx-crop"
|
||||
text={t("relation_map_buttons.reset_pan_zoom_title")}
|
||||
onClick={() => triggerEvent("relationMapResetPanZoom")}
|
||||
/>
|
||||
|
||||
<div className="btn-group">
|
||||
<FloatingButton
|
||||
icon="bx bx-zoom-in"
|
||||
text={t("relation_map_buttons.zoom_in_title")}
|
||||
onClick={() => triggerEvent("relationMapResetZoomIn")}
|
||||
/>
|
||||
|
||||
<FloatingButton
|
||||
icon="bx bx-zoom-out"
|
||||
text={t("relation_map_buttons.zoom_out_title")}
|
||||
onClick={() => triggerEvent("relationMapResetZoomOut")}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
|
||||
const isEnabled = viewType === "geoMap" && !isReadOnly;
|
||||
return isEnabled && (
|
||||
<FloatingButton
|
||||
icon="bx bx-plus-circle"
|
||||
text={t("geo-map.create-child-note-title")}
|
||||
onClick={() => triggerEvent("geoMapCreateChildNote")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const hiddenImageCopyRef = useRef<HTMLDivElement>(null);
|
||||
const isEnabled = ["mermaid", "canvas", "mindMap"].includes(note?.type ?? "")
|
||||
&& note?.isContentAvailable() && isDefaultViewMode;
|
||||
|
||||
return isEnabled && (
|
||||
<>
|
||||
<FloatingButton
|
||||
icon="bx bx-copy"
|
||||
text={t("copy_image_reference_button.button_title")}
|
||||
onClick={() => {
|
||||
if (!hiddenImageCopyRef.current) return;
|
||||
const imageEl = document.createElement("img");
|
||||
imageEl.src = createImageSrcUrl(note);
|
||||
hiddenImageCopyRef.current.replaceChildren(imageEl);
|
||||
copyImageReferenceToClipboard($(hiddenImageCopyRef.current));
|
||||
hiddenImageCopyRef.current.removeChild(imageEl);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div ref={hiddenImageCopyRef} className="hidden-image-copy" style={{
|
||||
position: "absolute" // Take out of the the hidden image from flexbox to prevent the layout being affected
|
||||
}} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const isEnabled = ["mermaid", "mindMap"].includes(note?.type ?? "")
|
||||
&& note?.isContentAvailable() && isDefaultViewMode;
|
||||
return isEnabled && (
|
||||
<>
|
||||
<FloatingButton
|
||||
icon="bx bxs-file-image"
|
||||
text={t("svg_export_button.button_title")}
|
||||
onClick={() => triggerEvent("exportSvg")}
|
||||
/>
|
||||
|
||||
<FloatingButton
|
||||
icon="bx bxs-file-png"
|
||||
text={t("png_export_button.button_title")}
|
||||
onClick={() => triggerEvent("exportPng")}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function InAppHelpButton({ note }: FloatingButtonContext) {
|
||||
const helpUrl = getHelpUrlForNote(note);
|
||||
|
||||
return !!helpUrl && (
|
||||
<FloatingButton
|
||||
icon="bx bx-help-circle"
|
||||
text={t("help-button.title")}
|
||||
onClick={() => helpUrl && openInAppHelpFromUrl(helpUrl)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
|
||||
let [ backlinkCount, setBacklinkCount ] = useState(0);
|
||||
let [ popupOpen, setPopupOpen ] = useState(false);
|
||||
const backlinksContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefaultViewMode) return;
|
||||
|
||||
server.get<BacklinkCountResponse>(`note-map/${note.noteId}/backlink-count`).then(resp => {
|
||||
setBacklinkCount(resp.count);
|
||||
});
|
||||
}, [ note ]);
|
||||
|
||||
// Determine the max height of the container.
|
||||
const { windowHeight } = useWindowSize();
|
||||
useLayoutEffect(() => {
|
||||
const el = backlinksContainerRef.current;
|
||||
if (popupOpen && el) {
|
||||
const box = el.getBoundingClientRect();
|
||||
const maxHeight = windowHeight - box.top - 10;
|
||||
el.style.maxHeight = `${maxHeight}px`;
|
||||
}
|
||||
}, [ popupOpen, windowHeight ]);
|
||||
|
||||
const isEnabled = isDefaultViewMode && backlinkCount > 0;
|
||||
return (isEnabled &&
|
||||
<div className="backlinks-widget has-overflow">
|
||||
<div
|
||||
className="backlinks-ticker"
|
||||
onClick={() => setPopupOpen(!popupOpen)}
|
||||
>
|
||||
<span className="backlinks-count">{t("zpetne_odkazy.backlink", { count: backlinkCount })}</span>
|
||||
</div>
|
||||
|
||||
{popupOpen && (
|
||||
<div ref={backlinksContainerRef} className="backlinks-items dropdown-menu" style={{ display: "block" }}>
|
||||
<BacklinksList noteId={note.noteId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BacklinksList({ noteId }: { noteId: string }) {
|
||||
const [ backlinks, setBacklinks ] = useState<BacklinksResponse>([]);
|
||||
|
||||
useEffect(() => {
|
||||
server.get<BacklinksResponse>(`note-map/${noteId}/backlinks`).then(async (backlinks) => {
|
||||
// prefetch all
|
||||
const noteIds = backlinks
|
||||
.filter(bl => "noteId" in bl)
|
||||
.map((bl) => bl.noteId);
|
||||
await froca.getNotes(noteIds);
|
||||
setBacklinks(backlinks);
|
||||
});
|
||||
}, [ noteId ]);
|
||||
|
||||
return backlinks.map(backlink => (
|
||||
<div>
|
||||
<NoteLink
|
||||
notePath={backlink.noteId}
|
||||
showNotePath showNoteIcon
|
||||
noPreview
|
||||
/>
|
||||
|
||||
{"relationName" in backlink ? (
|
||||
<p>{backlink.relationName}</p>
|
||||
) : (
|
||||
backlink.excerpts.map(excerpt => (
|
||||
<RawHtml html={excerpt} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
28
apps/client/src/widgets/api_log.css
Normal file
28
apps/client/src/widgets/api_log.css
Normal file
@@ -0,0 +1,28 @@
|
||||
.api-log-widget {
|
||||
flex-grow: 1;
|
||||
max-height: 40%;
|
||||
position: relative;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
background-color: var(--accented-background-color);
|
||||
}
|
||||
|
||||
.api-log-container {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
font-family: var(--monospace-font-family);
|
||||
font-size: 0.8em;
|
||||
white-space: pre;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.close-api-log-button {
|
||||
padding: 5px;
|
||||
border: 1px solid var(--button-border-color);
|
||||
background-color: var(--button-background-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
color: var(--button-text-color);
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="api-log-widget">
|
||||
<style>
|
||||
.api-log-widget {
|
||||
padding: 15px;
|
||||
flex-grow: 1;
|
||||
max-height: 40%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hidden-api-log {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.api-log-container {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.close-api-log-button {
|
||||
padding: 5px;
|
||||
border: 1px solid var(--button-border-color);
|
||||
background-color: var(--button-background-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
color: var(--button-text-color);
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bx bx-x close-api-log-button" title="${t("api_log.close")}"></div>
|
||||
|
||||
<div class="api-log-container"></div>
|
||||
</div>`;
|
||||
|
||||
export default class ApiLogWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $logContainer!: JQuery<HTMLElement>;
|
||||
private $closeButton!: JQuery<HTMLElement>;
|
||||
|
||||
isEnabled() {
|
||||
return !!this.note && this.note.mime.startsWith("application/javascript;env=") && super.isEnabled();
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.toggle(false);
|
||||
|
||||
this.$logContainer = this.$widget.find(".api-log-container");
|
||||
this.$closeButton = this.$widget.find(".close-api-log-button");
|
||||
this.$closeButton.on("click", () => this.toggle(false));
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.$logContainer.empty();
|
||||
}
|
||||
|
||||
apiLogMessagesEvent({ messages, noteId }: EventData<"apiLogMessages">) {
|
||||
if (!this.isNote(noteId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggle(true);
|
||||
|
||||
for (const message of messages) {
|
||||
this.$logContainer.append(message).append($("<br>"));
|
||||
}
|
||||
}
|
||||
|
||||
toggle(show: boolean) {
|
||||
this.$widget.toggleClass("hidden-api-log", !show);
|
||||
}
|
||||
}
|
||||
41
apps/client/src/widgets/api_log.tsx
Normal file
41
apps/client/src/widgets/api_log.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import "./api_log.css";
|
||||
import { useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||
import ActionButton from "./react/ActionButton";
|
||||
import { t } from "../services/i18n";
|
||||
|
||||
/**
|
||||
* Displays the messages that are logged by the current note via `api.log`, for frontend and backend scripts.
|
||||
*/
|
||||
export default function ApiLog() {
|
||||
const { note, noteId } = useNoteContext();
|
||||
const [ messages, setMessages ] = useState<string[]>();
|
||||
|
||||
useTriliumEvent("apiLogMessages", ({ messages, noteId: eventNoteId }) => {
|
||||
if (eventNoteId !== noteId) return;
|
||||
setMessages(messages);
|
||||
});
|
||||
|
||||
// Clear when navigating away.
|
||||
useEffect(() => setMessages(undefined), [ note ]);
|
||||
|
||||
const isEnabled = note?.mime.startsWith("application/javascript;env=") && messages?.length;
|
||||
return (
|
||||
<div className={`api-log-widget ${!isEnabled ? "hidden-ext" : ""}`}>
|
||||
{isEnabled && (
|
||||
<>
|
||||
<ActionButton
|
||||
icon="bx bx-x"
|
||||
className="close-api-log-button"
|
||||
text={t("api_log.close")}
|
||||
onClick={() => setMessages(undefined)}
|
||||
/>
|
||||
|
||||
<div className="api-log-container">
|
||||
{messages.join("\n")}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,504 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js";
|
||||
import server from "../../services/server.js";
|
||||
import contextMenuService from "../../menus/context_menu.js";
|
||||
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
|
||||
import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5";
|
||||
import froca from "../../services/froca.js";
|
||||
import attributeRenderer from "../../services/attribute_renderer.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import type AttributeDetailWidget from "./attribute_detail.js";
|
||||
import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js";
|
||||
import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { escapeQuotes } from "../../services/utils.js";
|
||||
|
||||
const HELP_TEXT = `
|
||||
<p>${t("attribute_editor.help_text_body1")}</p>
|
||||
|
||||
<p>${t("attribute_editor.help_text_body2")}</p>
|
||||
|
||||
<p>${t("attribute_editor.help_text_body3")}</p>`;
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
|
||||
<style>
|
||||
.attribute-list-editor {
|
||||
border: 0 !important;
|
||||
outline: 0 !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 0 0 5px !important;
|
||||
margin: 0 !important;
|
||||
max-height: 100px;
|
||||
overflow: auto;
|
||||
transition: opacity .1s linear;
|
||||
}
|
||||
|
||||
.attribute-list-editor.ck-content .mention {
|
||||
color: var(--muted-text-color) !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.save-attributes-button {
|
||||
color: var(--muted-text-color);
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
right: 25px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.add-new-attribute-button {
|
||||
color: var(--muted-text-color);
|
||||
position: absolute;
|
||||
bottom: 13px;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.add-new-attribute-button:hover, .save-attributes-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
background: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
}
|
||||
|
||||
.attribute-errors {
|
||||
color: red;
|
||||
padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="attribute-list-editor" tabindex="200"></div>
|
||||
|
||||
<div class="bx bx-save save-attributes-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.save_attributes"))}"></div>
|
||||
<div class="bx bx-plus add-new-attribute-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
|
||||
|
||||
<div class="attribute-errors" style="display: none;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const mentionSetup: MentionFeed[] = [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (_item) => {
|
||||
const item = _item as Suggestion;
|
||||
const itemElement = document.createElement("button");
|
||||
|
||||
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
},
|
||||
{
|
||||
marker: "#",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
|
||||
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `#${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
minimumCharacters: 0
|
||||
},
|
||||
{
|
||||
marker: "~",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
|
||||
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `~${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
];
|
||||
|
||||
const editorConfig: EditorConfig = {
|
||||
toolbar: {
|
||||
items: []
|
||||
},
|
||||
placeholder: t("attribute_editor.placeholder"),
|
||||
mention: {
|
||||
feeds: mentionSetup
|
||||
},
|
||||
licenseKey: "GPL"
|
||||
};
|
||||
|
||||
type AttributeCommandNames = FilteredCommandNames<CommandData>;
|
||||
|
||||
export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> {
|
||||
private attributeDetailWidget: AttributeDetailWidget;
|
||||
private $editor!: JQuery<HTMLElement>;
|
||||
private $addNewAttributeButton!: JQuery<HTMLElement>;
|
||||
private $saveAttributesButton!: JQuery<HTMLElement>;
|
||||
private $errors!: JQuery<HTMLElement>;
|
||||
|
||||
private textEditor!: AttributeEditor;
|
||||
private lastUpdatedNoteId!: string | undefined;
|
||||
private lastSavedContent!: string;
|
||||
|
||||
constructor(attributeDetailWidget: AttributeDetailWidget) {
|
||||
super();
|
||||
|
||||
this.attributeDetailWidget = attributeDetailWidget;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$editor = this.$widget.find(".attribute-list-editor");
|
||||
|
||||
this.initialized = this.initEditor();
|
||||
|
||||
this.$editor.on("keydown", async (e) => {
|
||||
if (e.which === 13) {
|
||||
// allow autocomplete to fill the result textarea
|
||||
setTimeout(() => this.save(), 100);
|
||||
}
|
||||
|
||||
this.attributeDetailWidget.hide();
|
||||
});
|
||||
|
||||
this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160
|
||||
|
||||
this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button");
|
||||
this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e));
|
||||
|
||||
this.$saveAttributesButton = this.$widget.find(".save-attributes-button");
|
||||
this.$saveAttributesButton.on("click", () => this.save());
|
||||
|
||||
this.$errors = this.$widget.find(".attribute-errors");
|
||||
}
|
||||
|
||||
addNewAttribute(e: JQuery.ClickEvent) {
|
||||
contextMenuService.show<AttributeCommandNames>({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
orientation: "left",
|
||||
items: [
|
||||
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
|
||||
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
|
||||
{ title: "----" },
|
||||
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
|
||||
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
|
||||
],
|
||||
selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command)
|
||||
});
|
||||
// Prevent automatic hiding of the context menu due to the button being clicked.
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// triggered from keyboard shortcut
|
||||
async addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) {
|
||||
if (this.isNoteContext(ntxId)) {
|
||||
await this.refresh();
|
||||
|
||||
this.handleAddNewAttributeCommand("addNewLabel");
|
||||
}
|
||||
}
|
||||
|
||||
// triggered from keyboard shortcut
|
||||
async addNewRelationEvent({ ntxId }: EventData<"addNewRelation">) {
|
||||
if (this.isNoteContext(ntxId)) {
|
||||
await this.refresh();
|
||||
|
||||
this.handleAddNewAttributeCommand("addNewRelation");
|
||||
}
|
||||
}
|
||||
|
||||
async handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) {
|
||||
// TODO: Not sure what the relation between FAttribute[] and Attribute[] is.
|
||||
const attrs = this.parseAttributes() as FAttribute[];
|
||||
|
||||
if (!attrs) {
|
||||
return;
|
||||
}
|
||||
|
||||
let type: AttributeType;
|
||||
let name;
|
||||
let value;
|
||||
|
||||
if (command === "addNewLabel") {
|
||||
type = "label";
|
||||
name = "myLabel";
|
||||
value = "";
|
||||
} else if (command === "addNewRelation") {
|
||||
type = "relation";
|
||||
name = "myRelation";
|
||||
value = "";
|
||||
} else if (command === "addNewLabelDefinition") {
|
||||
type = "label";
|
||||
name = "label:myLabel";
|
||||
value = "promoted,single,text";
|
||||
} else if (command === "addNewRelationDefinition") {
|
||||
type = "label";
|
||||
name = "relation:myRelation";
|
||||
value = "promoted,single";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Incomplete type
|
||||
//@ts-ignore
|
||||
attrs.push({
|
||||
type,
|
||||
name,
|
||||
value,
|
||||
isInheritable: false
|
||||
});
|
||||
|
||||
await this.renderOwnedAttributes(attrs, false);
|
||||
|
||||
this.$editor.scrollTop(this.$editor[0].scrollHeight);
|
||||
|
||||
const rect = this.$editor[0].getBoundingClientRect();
|
||||
|
||||
setTimeout(() => {
|
||||
// showing a little bit later because there's a conflict with outside click closing the attr detail
|
||||
this.attributeDetailWidget.showAttributeDetail({
|
||||
allAttributes: attrs,
|
||||
attribute: attrs[attrs.length - 1],
|
||||
isOwned: true,
|
||||
x: (rect.left + rect.right) / 2,
|
||||
y: rect.bottom,
|
||||
focus: "name"
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.lastUpdatedNoteId !== this.noteId) {
|
||||
// https://github.com/zadam/trilium/issues/3090
|
||||
console.warn("Ignoring blur event because a different note is loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = this.parseAttributes();
|
||||
|
||||
if (attributes) {
|
||||
await server.put(`notes/${this.noteId}/attributes`, attributes, this.componentId);
|
||||
|
||||
this.$saveAttributesButton.fadeOut();
|
||||
|
||||
// blink the attribute text to give a visual hint that save has been executed
|
||||
this.$editor.css("opacity", 0);
|
||||
|
||||
// revert back
|
||||
setTimeout(() => this.$editor.css("opacity", 1), 100);
|
||||
}
|
||||
}
|
||||
|
||||
parseAttributes() {
|
||||
try {
|
||||
return attributeParser.lexAndParse(this.getPreprocessedData());
|
||||
} catch (e: any) {
|
||||
this.$errors.text(e.message).slideDown();
|
||||
}
|
||||
}
|
||||
|
||||
getPreprocessedData() {
|
||||
const str = this.textEditor
|
||||
.getData()
|
||||
.replace(/<a[^>]+href="(#[A-Za-z0-9_/]*)"[^>]*>[^<]*<\/a>/g, "$1")
|
||||
.replace(/ /g, " "); // otherwise .text() below outputs non-breaking space in unicode
|
||||
|
||||
return $("<div>").html(str).text();
|
||||
}
|
||||
|
||||
async initEditor() {
|
||||
this.$widget.show();
|
||||
|
||||
this.$editor.on("click", (e) => this.handleEditorClick(e));
|
||||
|
||||
this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig);
|
||||
this.textEditor.model.document.on("change:data", () => this.dataChanged());
|
||||
this.textEditor.editing.view.document.on(
|
||||
"enter",
|
||||
(event, data) => {
|
||||
// disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422
|
||||
data.preventDefault();
|
||||
event.stop();
|
||||
},
|
||||
{ priority: "high" }
|
||||
);
|
||||
|
||||
// disable spellcheck for attribute editor
|
||||
const documentRoot = this.textEditor.editing.view.document.getRoot();
|
||||
if (documentRoot) {
|
||||
this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot));
|
||||
}
|
||||
}
|
||||
|
||||
dataChanged() {
|
||||
this.lastUpdatedNoteId = this.noteId;
|
||||
|
||||
if (this.lastSavedContent === this.textEditor.getData()) {
|
||||
this.$saveAttributesButton.fadeOut();
|
||||
} else {
|
||||
this.$saveAttributesButton.fadeIn();
|
||||
}
|
||||
|
||||
if (this.$errors.is(":visible")) {
|
||||
// using .hide() instead of .slideUp() since this will also hide the error after confirming
|
||||
// mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird
|
||||
this.$errors.hide();
|
||||
}
|
||||
}
|
||||
|
||||
async handleEditorClick(e: JQuery.ClickEvent) {
|
||||
const pos = this.textEditor.model.document.selection.getFirstPosition();
|
||||
|
||||
if (pos && pos.textNode && pos.textNode.data) {
|
||||
const clickIndex = this.getClickIndex(pos);
|
||||
|
||||
let parsedAttrs;
|
||||
|
||||
try {
|
||||
parsedAttrs = attributeParser.lexAndParse(this.getPreprocessedData(), true);
|
||||
} catch (e) {
|
||||
// the input is incorrect because the user messed up with it and now needs to fix it manually
|
||||
return null;
|
||||
}
|
||||
|
||||
let matchedAttr: Attribute | null = null;
|
||||
|
||||
for (const attr of parsedAttrs) {
|
||||
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
|
||||
matchedAttr = attr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (matchedAttr) {
|
||||
this.$editor.tooltip("hide");
|
||||
|
||||
this.attributeDetailWidget.showAttributeDetail({
|
||||
allAttributes: parsedAttrs,
|
||||
attribute: matchedAttr,
|
||||
isOwned: true,
|
||||
x: e.pageX,
|
||||
y: e.pageY
|
||||
});
|
||||
} else {
|
||||
this.showHelpTooltip();
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
this.showHelpTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
showHelpTooltip() {
|
||||
this.attributeDetailWidget.hide();
|
||||
|
||||
this.$editor.tooltip({
|
||||
trigger: "focus",
|
||||
html: true,
|
||||
title: HELP_TEXT,
|
||||
placement: "bottom",
|
||||
offset: "0,30"
|
||||
});
|
||||
|
||||
this.$editor.tooltip("show");
|
||||
}
|
||||
|
||||
getClickIndex(pos: ModelPosition) {
|
||||
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);
|
||||
|
||||
let curNode: ModelNode | Text | ModelElement | null = pos.textNode;
|
||||
|
||||
while (curNode?.previousSibling) {
|
||||
curNode = curNode.previousSibling;
|
||||
|
||||
if ((curNode as ModelElement).name === "reference") {
|
||||
clickIndex += (curNode.getAttribute("href") as string).length + 1;
|
||||
} else if ("data" in curNode) {
|
||||
clickIndex += (curNode.data as string).length;
|
||||
}
|
||||
}
|
||||
|
||||
return clickIndex;
|
||||
}
|
||||
|
||||
async loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string) {
|
||||
const { noteId } = linkService.parseNavigationStateFromUrl(href);
|
||||
const note = noteId ? await froca.getNote(noteId, true) : null;
|
||||
const title = note ? note.title : "[missing]";
|
||||
|
||||
$el.text(title);
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
await this.renderOwnedAttributes(note.getOwnedAttributes(), true);
|
||||
}
|
||||
|
||||
async renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) {
|
||||
// attrs are not resorted if position changes after the initial load
|
||||
ownedAttributes.sort((a, b) => a.position - b.position);
|
||||
|
||||
let htmlAttrs = (await attributeRenderer.renderAttributes(ownedAttributes, true)).html();
|
||||
|
||||
if (htmlAttrs.length > 0) {
|
||||
htmlAttrs += " ";
|
||||
}
|
||||
|
||||
this.textEditor.setData(htmlAttrs);
|
||||
|
||||
if (saved) {
|
||||
this.lastSavedContent = this.textEditor.getData();
|
||||
|
||||
this.$saveAttributesButton.fadeOut(0);
|
||||
}
|
||||
}
|
||||
|
||||
async createNoteForReferenceLink(title: string) {
|
||||
let result;
|
||||
if (this.notePath) {
|
||||
result = await noteCreateService.createNoteWithTypePrompt(this.notePath, {
|
||||
activate: false,
|
||||
title: title
|
||||
});
|
||||
}
|
||||
|
||||
return result?.note?.getBestNotePathString();
|
||||
}
|
||||
|
||||
async updateAttributeList(attributes: FAttribute[]) {
|
||||
await this.renderOwnedAttributes(attributes, false);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.$editor.trigger("focus");
|
||||
|
||||
this.textEditor.model.change((writer) => {
|
||||
const documentRoot = this.textEditor.editing.model.document.getRoot();
|
||||
if (!documentRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const positionAt = writer.createPositionAt(documentRoot, "end");
|
||||
writer.setSelection(positionAt);
|
||||
});
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.getAttributeRows(this.componentId).find((attr) => attributeService.isAffecting(attr, this.note))) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import { isValidElement, VNode } from "preact";
|
||||
import Component, { TypedComponent } from "../components/component.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import { renderReactWidget } from "./react/react_utils.jsx";
|
||||
import { EventNames, EventData } from "../components/app_context.js";
|
||||
import { Handler } from "leaflet";
|
||||
|
||||
export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> {
|
||||
protected attrs: Record<string, string>;
|
||||
@@ -22,11 +26,14 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
this.childPositionCounter = 10;
|
||||
}
|
||||
|
||||
child(...components: T[]) {
|
||||
if (!components) {
|
||||
child(..._components: (T | VNode)[]) {
|
||||
if (!_components) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Convert any React components to legacy wrapped components.
|
||||
const components = wrapReactWidgets(_components);
|
||||
|
||||
super.child(...components);
|
||||
|
||||
for (const component of components) {
|
||||
@@ -48,7 +55,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
* @param components the components to be added as children to this component provided the condition is truthy.
|
||||
* @returns self for chaining.
|
||||
*/
|
||||
optChild(condition: boolean, ...components: T[]) {
|
||||
optChild(condition: boolean, ...components: (T | VNode)[]) {
|
||||
if (condition) {
|
||||
return this.child(...components);
|
||||
} else {
|
||||
@@ -258,3 +265,30 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
* For information on using widgets, see the tutorial {@tutorial widget_basics}.
|
||||
*/
|
||||
export default class BasicWidget extends TypedBasicWidget<Component> {}
|
||||
|
||||
export function wrapReactWidgets<T extends TypedComponent<any>>(components: (T | VNode)[]) {
|
||||
const wrappedResult: T[] = [];
|
||||
for (const component of components) {
|
||||
if (isValidElement(component)) {
|
||||
wrappedResult.push(new ReactWrappedWidget(component) as unknown as T);
|
||||
} else {
|
||||
wrappedResult.push(component);
|
||||
}
|
||||
}
|
||||
return wrappedResult;
|
||||
}
|
||||
|
||||
export class ReactWrappedWidget extends BasicWidget {
|
||||
|
||||
private el: VNode;
|
||||
|
||||
constructor(el: VNode) {
|
||||
super();
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = renderReactWidget(this, this.el);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import SwitchWidget from "./switch.js";
|
||||
import server from "../services/server.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
|
||||
// TODO: Deduplicate
|
||||
type Response = {
|
||||
success: true;
|
||||
} | {
|
||||
success: false;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export default class BookmarkSwitchWidget extends SwitchWidget {
|
||||
isEnabled() {
|
||||
return (
|
||||
super.isEnabled() &&
|
||||
// it's not possible to bookmark root because that would clone it under bookmarks and thus create a cycle
|
||||
!["root", "_hidden"].includes(this.noteId ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.switchOnName = t("bookmark_switch.bookmark");
|
||||
this.switchOnTooltip = t("bookmark_switch.bookmark_this_note");
|
||||
|
||||
this.switchOffName = t("bookmark_switch.bookmark");
|
||||
this.switchOffTooltip = t("bookmark_switch.remove_bookmark");
|
||||
}
|
||||
|
||||
async toggle(state: boolean | null | undefined) {
|
||||
const resp = await server.put<Response>(`notes/${this.noteId}/toggle-in-parent/_lbBookmarks/${!!state}`);
|
||||
|
||||
if (!resp.success && "message" in resp) {
|
||||
toastService.showError(resp.message);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
const isBookmarked = !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks");
|
||||
|
||||
this.isToggled = isBookmarked;
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.getBranchRows().find((b) => b.noteId === this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ComponentChildren } from "preact";
|
||||
import { memo } from "preact/compat";
|
||||
import AbstractBulkAction from "./abstract_bulk_action";
|
||||
import HelpRemoveButtons from "../react/HelpRemoveButtons";
|
||||
|
||||
interface BulkActionProps {
|
||||
label: string | ComponentChildren;
|
||||
@@ -24,19 +25,11 @@ const BulkAction = memo(({ label, children, helpText, bulkAction }: BulkActionPr
|
||||
{children}
|
||||
</div>
|
||||
</td>
|
||||
<td className="button-column">
|
||||
{helpText && <div className="dropdown help-dropdown">
|
||||
<span className="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div className="dropdown-menu dropdown-menu-right p-4">
|
||||
{helpText}
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<span
|
||||
className="bx bx-x icon-action action-conf-del"
|
||||
onClick={() => bulkAction?.deleteAction()}
|
||||
/>
|
||||
</td>
|
||||
<HelpRemoveButtons
|
||||
help={helpText}
|
||||
removeText="Delete"
|
||||
onRemove={() => bulkAction?.deleteAction()}
|
||||
/>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
|
||||
import AbstractBulkAction from "../abstract_bulk_action.js";
|
||||
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
|
||||
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { useSpacedUpdate } from "../../react/hooks.jsx";
|
||||
|
||||
function MoveNoteBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
|
||||
function MoveNoteBulkActionComponent({ bulkAction }: { bulkAction: AbstractBulkAction }) {
|
||||
const [ targetParentNoteId, setTargetParentNoteId ] = useState<string>();
|
||||
const spacedUpdate = useSpacedUpdate(() => {
|
||||
return bulkAction.saveAction({ targetParentNoteId: targetParentNoteId })
|
||||
@@ -45,6 +45,6 @@ export default class MoveNoteBulkAction extends AbstractBulkAction {
|
||||
}
|
||||
|
||||
doRender() {
|
||||
return <MoveNoteBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
|
||||
return <MoveNoteBulkActionComponent bulkAction={this} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import BulkAction from "../BulkAction.jsx";
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
|
||||
import noteAutocompleteService from "../../../services/note_autocomplete.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
|
||||
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
|
||||
|
||||
102
apps/client/src/widgets/buttons/global_menu.css
Normal file
102
apps/client/src/widgets/buttons/global_menu.css
Normal file
@@ -0,0 +1,102 @@
|
||||
.global-menu {
|
||||
width: 53px;
|
||||
height: 53px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.global-menu .dropdown-menu {
|
||||
min-width: 20em;
|
||||
}
|
||||
|
||||
.global-menu-button {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.global-menu-button svg path {
|
||||
fill: var(--launcher-pane-text-color);
|
||||
}
|
||||
|
||||
.global-menu-button:hover { border: 0; }
|
||||
.global-menu-button:hover svg path {
|
||||
transition: 200ms ease-in-out fill;
|
||||
}
|
||||
.global-menu-button:hover svg path.st0 { fill:#95C980; }
|
||||
.global-menu-button:hover svg path.st1 { fill:#72B755; }
|
||||
.global-menu-button:hover svg path.st2 { fill:#4FA52B; }
|
||||
.global-menu-button:hover svg path.st3 { fill:#EE8C89; }
|
||||
.global-menu-button:hover svg path.st4 { fill:#E96562; }
|
||||
.global-menu-button:hover svg path.st5 { fill:#E33F3B; }
|
||||
.global-menu-button:hover svg path.st6 { fill:#EFB075; }
|
||||
.global-menu-button:hover svg path.st7 { fill:#E99547; }
|
||||
.global-menu-button:hover svg path.st8 { fill:#E47B19; }
|
||||
|
||||
.global-menu-button-update-available {
|
||||
position: absolute;
|
||||
right: -30px;
|
||||
bottom: -30px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a {
|
||||
display: inline-block;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
color: var(--button-text-color);
|
||||
background-color: var(--button-background-color);
|
||||
padding: 3px;
|
||||
margin-left: 3px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-state {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.global-menu .dropdown-item .bx {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 120%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* #region Update available */
|
||||
.global-menu-button-update-available-button {
|
||||
width: 21px !important;
|
||||
height: 21px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
border-radius: var(--button-border-radius);
|
||||
transform: scale(0.9);
|
||||
border: none;
|
||||
opacity: 0.8;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.global-menu-button-wrapper:hover .global-menu-button-update-available-button {
|
||||
opacity: 1;
|
||||
}
|
||||
/* #endregion */
|
||||
@@ -1,436 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import UpdateAvailableWidget from "./update_available.js";
|
||||
import options from "../../services/options.js";
|
||||
import { Tooltip, Dropdown } from "bootstrap";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown global-menu">
|
||||
<style>
|
||||
.global-menu {
|
||||
width: 53px;
|
||||
height: 53px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.global-menu .dropdown-menu {
|
||||
min-width: 20em;
|
||||
}
|
||||
|
||||
.global-menu-button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.global-menu-button > svg path {
|
||||
fill: var(--launcher-pane-text-color);
|
||||
}
|
||||
|
||||
.global-menu-button:hover { border: 0; }
|
||||
.global-menu-button:hover > svg path {
|
||||
transition: 200ms ease-in-out fill;
|
||||
}
|
||||
.global-menu-button:hover > svg path.st0 { fill:#95C980; }
|
||||
.global-menu-button:hover > svg path.st1 { fill:#72B755; }
|
||||
.global-menu-button:hover > svg path.st2 { fill:#4FA52B; }
|
||||
.global-menu-button:hover > svg path.st3 { fill:#EE8C89; }
|
||||
.global-menu-button:hover > svg path.st4 { fill:#E96562; }
|
||||
.global-menu-button:hover > svg path.st5 { fill:#E33F3B; }
|
||||
.global-menu-button:hover > svg path.st6 { fill:#EFB075; }
|
||||
.global-menu-button:hover > svg path.st7 { fill:#E99547; }
|
||||
.global-menu-button:hover > svg path.st8 { fill:#E47B19; }
|
||||
|
||||
.global-menu-button-update-available {
|
||||
position: absolute;
|
||||
right: -30px;
|
||||
bottom: -30px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a {
|
||||
display: inline-block;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
color: var(--button-text-color);
|
||||
background-color: var(--button-background-color);
|
||||
padding: 3px;
|
||||
margin-left: 3px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-state {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.global-menu .dropdown-item .bx {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 120%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action global-menu-button">
|
||||
<div class="global-menu-button-update-available"></div>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li class="dropdown-item" data-trigger-command="openNewWindow">
|
||||
<span class="bx bx-window-open"></span>
|
||||
${t("global_menu.open_new_window")}
|
||||
<kbd data-command="openNewWindow"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showShareSubtree">
|
||||
<span class="bx bx-share-alt"></span>
|
||||
${t("global_menu.show_shared_notes_subtree")}
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<span class="zoom-container dropdown-item dropdown-item-container">
|
||||
<div>
|
||||
<span class="bx bx-empty"></span>
|
||||
${t("global_menu.zoom")}
|
||||
</div>
|
||||
|
||||
<div class="zoom-buttons">
|
||||
<a data-trigger-command="toggleFullscreen" title="${t("global_menu.toggle_fullscreen")}" class="bx bx-expand-alt"></a>
|
||||
|
||||
|
||||
|
||||
<a data-trigger-command="zoomOut" title="${t("global_menu.zoom_out")}" class="bx bx-minus"></a>
|
||||
|
||||
<span data-trigger-command="zoomReset" title="${t("global_menu.reset_zoom_level")}" class="zoom-state"></span>
|
||||
|
||||
<a data-trigger-command="zoomIn" title="${t("global_menu.zoom_in")}" class="bx bx-plus"></a>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<li class="dropdown-item toggle-pin">
|
||||
<span class="bx bx-pin"></span>
|
||||
${t("title_bar_buttons.window-on-top")}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="toggleZenMode">
|
||||
<span class="bx bxs-yin-yang"></span>
|
||||
${t("global_menu.toggle-zen-mode")}
|
||||
<kbd data-command="toggleZenMode"></kbd>
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider desktop-only"></div>
|
||||
|
||||
<li class="dropdown-item switch-to-mobile-version-button" data-trigger-command="switchToMobileVersion">
|
||||
<span class="bx bx-mobile"></span>
|
||||
${t("global_menu.switch_to_mobile_version")}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item switch-to-desktop-version-button" data-trigger-command="switchToDesktopVersion">
|
||||
<span class="bx bx-desktop"></span>
|
||||
${t("global_menu.switch_to_desktop_version")}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showLaunchBarSubtree">
|
||||
<span class="bx ${utils.isMobile() ? "bx-mobile" : "bx-sidebar"}"></span>
|
||||
${t("global_menu.configure_launchbar")}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item dropdown-submenu">
|
||||
<span class="dropdown-toggle">
|
||||
<span class="bx bx-chip"></span>${t("global_menu.advanced")}
|
||||
</span>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-item" data-trigger-command="showHiddenSubtree">
|
||||
<span class="bx bx-hide"></span>
|
||||
${t("global_menu.show_hidden_subtree")}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showSearchHistory">
|
||||
<span class="bx bx-search-alt"></span>
|
||||
${t("global_menu.open_search_history")}
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showBackendLog">
|
||||
<span class="bx bx-detail"></span>
|
||||
${t("global_menu.show_backend_log")}
|
||||
<kbd data-command="showBackendLog"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showSQLConsole">
|
||||
<span class="bx bx-data"></span>
|
||||
${t("global_menu.open_sql_console")}
|
||||
<kbd data-command="showSQLConsole"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
|
||||
<span class="bx bx-data"></span>
|
||||
${t("global_menu.open_sql_console_history")}
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<li class="dropdown-item open-dev-tools-button" data-trigger-command="openDevTools">
|
||||
<span class="bx bx-bug-alt"></span>
|
||||
${t("global_menu.open_dev_tools")}
|
||||
<kbd data-command="openDevTools"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
|
||||
title="${t("global_menu.reload_hint")}">
|
||||
<span class="bx bx-refresh"></span>
|
||||
${t("global_menu.reload_frontend")}
|
||||
<kbd data-command="reloadFrontendApp"></kbd>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showOptions">
|
||||
<span class="bx bx-cog"></span>
|
||||
${t("global_menu.options")}
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider desktop-only"></div>
|
||||
|
||||
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
|
||||
<span class="bx bx-help-circle"></span>
|
||||
${t("global_menu.show_help")}
|
||||
<kbd data-command="showHelp"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-help-button" data-trigger-command="showCheatsheet">
|
||||
<span class="bx bxs-keyboard"></span>
|
||||
${t("global_menu.show-cheatsheet")}
|
||||
<kbd data-command="showCheatsheet"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-about-dialog-button">
|
||||
<span class="bx bx-info-circle"></span>
|
||||
${t("global_menu.about")}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item update-to-latest-version-button" style="display: none;" data-trigger-command="downloadLatestVersion">
|
||||
<span class="bx bx-sync"></span>
|
||||
|
||||
<span class="version-text"></span>
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider logout-button-separator"></div>
|
||||
|
||||
<li class="dropdown-item logout-button" data-trigger-command="logout">
|
||||
<span class="bx bx-log-out"></span>
|
||||
${t("global_menu.logout")}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class GlobalMenuWidget extends BasicWidget {
|
||||
private updateAvailableWidget: UpdateAvailableWidget;
|
||||
private isHorizontalLayout: boolean;
|
||||
private tooltip!: Tooltip;
|
||||
private dropdown!: Dropdown;
|
||||
|
||||
private $updateToLatestVersionButton!: JQuery<HTMLElement>;
|
||||
private $zoomState!: JQuery<HTMLElement>;
|
||||
private $toggleZenMode!: JQuery<HTMLElement>;
|
||||
|
||||
constructor(isHorizontalLayout: boolean) {
|
||||
super();
|
||||
|
||||
this.updateAvailableWidget = new UpdateAvailableWidget();
|
||||
this.isHorizontalLayout = isHorizontalLayout;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
if (!this.isHorizontalLayout) {
|
||||
this.$widget.addClass("dropend");
|
||||
}
|
||||
|
||||
const $globalMenuButton = this.$widget.find(".global-menu-button");
|
||||
if (!this.isHorizontalLayout) {
|
||||
$globalMenuButton.prepend(
|
||||
$(`\
|
||||
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t("global_menu.menu")}">
|
||||
<g>
|
||||
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
|
||||
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
|
||||
<path class="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/>
|
||||
|
||||
<path class="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/>
|
||||
<path class="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/>
|
||||
<path class="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/>
|
||||
|
||||
<path class="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/>
|
||||
<path class="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/>
|
||||
<path class="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
|
||||
</g>
|
||||
</svg>`)
|
||||
);
|
||||
|
||||
this.tooltip = new Tooltip(this.$widget.find("[data-bs-toggle='tooltip']")[0], { trigger: "hover" });
|
||||
} else {
|
||||
$globalMenuButton.toggleClass("bx bx-menu");
|
||||
}
|
||||
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0], {
|
||||
popperConfig: {
|
||||
placement: "bottom"
|
||||
}
|
||||
});
|
||||
|
||||
this.$widget.find(".show-about-dialog-button").on("click", () => this.triggerCommand("openAboutDialog"));
|
||||
|
||||
const isElectron = utils.isElectron();
|
||||
|
||||
this.$widget.find(".toggle-pin").toggle(isElectron);
|
||||
if (isElectron) {
|
||||
this.$widget.on("click", ".toggle-pin", (e) => {
|
||||
const $el = $(e.target);
|
||||
const remote = utils.dynamicRequire("@electron/remote");
|
||||
const focusedWindow = remote.BrowserWindow.getFocusedWindow();
|
||||
const isAlwaysOnTop = focusedWindow.isAlwaysOnTop();
|
||||
if (isAlwaysOnTop) {
|
||||
focusedWindow.setAlwaysOnTop(false);
|
||||
$el.removeClass("active");
|
||||
} else {
|
||||
focusedWindow.setAlwaysOnTop(true);
|
||||
$el.addClass("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.$widget.find(".logout-button").toggle(!isElectron);
|
||||
this.$widget.find(".logout-button-separator").toggle(!isElectron);
|
||||
|
||||
this.$widget.find(".open-dev-tools-button").toggle(isElectron);
|
||||
this.$widget.find(".switch-to-mobile-version-button").toggle(!isElectron && utils.isDesktop());
|
||||
this.$widget.find(".switch-to-desktop-version-button").toggle(!isElectron && utils.isMobile());
|
||||
|
||||
this.$widget.on("click", ".dropdown-item", (e) => {
|
||||
if ($(e.target).parent(".zoom-buttons")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dropdown.toggle();
|
||||
});
|
||||
if (utils.isMobile()) {
|
||||
this.$widget.on("click", ".dropdown-submenu .dropdown-toggle", (e) => {
|
||||
const $submenu = $(e.target).closest(".dropdown-item");
|
||||
$submenu.toggleClass("submenu-open");
|
||||
$submenu.find("ul.dropdown-menu").toggleClass("show");
|
||||
e.stopPropagation();
|
||||
return;
|
||||
});
|
||||
}
|
||||
this.$widget.on("click", ".dropdown-submenu", (e) => {
|
||||
if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass("dropdown-toggle")) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
this.$widget.find(".global-menu-button-update-available").append(this.updateAvailableWidget.render());
|
||||
|
||||
this.$updateToLatestVersionButton = this.$widget.find(".update-to-latest-version-button");
|
||||
|
||||
if (!utils.isElectron()) {
|
||||
this.$widget.find(".zoom-container").hide();
|
||||
}
|
||||
|
||||
this.$zoomState = this.$widget.find(".zoom-state");
|
||||
this.$toggleZenMode = this.$widget.find('[data-trigger-command="toggleZenMode"');
|
||||
this.$widget.on("show.bs.dropdown", () => this.#onShown());
|
||||
if (this.tooltip) {
|
||||
this.$widget.on("hide.bs.dropdown", () => this.tooltip.enable());
|
||||
}
|
||||
|
||||
this.$widget.find(".zoom-buttons").on(
|
||||
"click",
|
||||
// delay to wait for the actual zoom change
|
||||
() => setTimeout(() => this.updateZoomState(), 300)
|
||||
);
|
||||
|
||||
this.updateVersionStatus();
|
||||
|
||||
setInterval(() => this.updateVersionStatus(), 8 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
#onShown() {
|
||||
this.$toggleZenMode.toggleClass("active", $("body").hasClass("zen"));
|
||||
this.updateZoomState();
|
||||
if (this.tooltip) {
|
||||
this.tooltip.hide();
|
||||
this.tooltip.disable();
|
||||
}
|
||||
}
|
||||
|
||||
updateZoomState() {
|
||||
if (!utils.isElectron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const zoomFactor = utils.dynamicRequire("electron").webFrame.getZoomFactor();
|
||||
const zoomPercent = Math.round(zoomFactor * 100);
|
||||
|
||||
this.$zoomState.text(`${zoomPercent}%`);
|
||||
}
|
||||
|
||||
async updateVersionStatus() {
|
||||
await options.initializedPromise;
|
||||
|
||||
if (options.get("checkForUpdates") !== "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
const latestVersion = await this.fetchLatestVersion();
|
||||
this.updateAvailableWidget.updateVersionStatus(latestVersion);
|
||||
// Show "click to download" button in options menu if there's a new version available
|
||||
this.$updateToLatestVersionButton.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
|
||||
this.$updateToLatestVersionButton.find(".version-text").text(`Version ${latestVersion} is available, click to download.`);
|
||||
}
|
||||
|
||||
async fetchLatestVersion() {
|
||||
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
|
||||
|
||||
const resp = await fetch(RELEASES_API_URL);
|
||||
const data = await resp.json();
|
||||
|
||||
return data?.tag_name?.substring(1);
|
||||
}
|
||||
|
||||
downloadLatestVersionCommand() {
|
||||
window.open("https://github.com/TriliumNext/Trilium/releases/latest");
|
||||
}
|
||||
|
||||
activeContextChangedEvent() {
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
noteSwitchedEvent() {
|
||||
this.dropdown.hide();
|
||||
}
|
||||
}
|
||||
239
apps/client/src/widgets/buttons/global_menu.tsx
Normal file
239
apps/client/src/widgets/buttons/global_menu.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import "./global_menu.css";
|
||||
import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool } from "../react/hooks";
|
||||
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList";
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import KeyboardShortcut from "../react/KeyboardShortcut";
|
||||
import { KeyboardActionNames } from "@triliumnext/commons";
|
||||
import { ComponentChildren } from "preact";
|
||||
import Component from "../../components/component";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import utils, { dynamicRequire, isElectron, isMobile } from "../../services/utils";
|
||||
|
||||
interface MenuItemProps<T> {
|
||||
icon: string,
|
||||
text: ComponentChildren,
|
||||
title?: string,
|
||||
command: T,
|
||||
disabled?: boolean
|
||||
active?: boolean;
|
||||
outsideChildren?: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: boolean }) {
|
||||
const isVerticalLayout = !isHorizontalLayout;
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus();
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="global-menu"
|
||||
buttonClassName={`global-menu-button ${isHorizontalLayout ? "bx bx-menu" : ""}`} noSelectButtonStyle iconAction hideToggleArrow
|
||||
text={<>
|
||||
{isVerticalLayout && <VerticalLayoutIcon />}
|
||||
{isUpdateAvailable && <div class="global-menu-button-update-available">
|
||||
<span className="bx bx-sync global-menu-button-update-available-button" title={t("update_available.update_available")}></span>
|
||||
</div>}
|
||||
</>}
|
||||
>
|
||||
|
||||
<MenuItem command="openNewWindow" icon="bx bx-window-open" text={t("global_menu.open_new_window")} />
|
||||
<MenuItem command="showShareSubtree" icon="bx bx-share-alt" text={t("global_menu.show_shared_notes_subtree")} />
|
||||
<FormDropdownDivider />
|
||||
|
||||
<ZoomControls parentComponent={parentComponent} />
|
||||
<ToggleWindowOnTop />
|
||||
<KeyboardActionMenuItem command="toggleZenMode" icon="bx bxs-yin-yang" text={t("global_menu.toggle-zen-mode")} />
|
||||
<FormDropdownDivider />
|
||||
|
||||
<SwitchToOptions />
|
||||
<MenuItem command="showLaunchBarSubtree" icon={`bx ${isMobile() ? "bx-mobile" : "bx-sidebar"}`} text={t("global_menu.configure_launchbar")} />
|
||||
<AdvancedMenu />
|
||||
<MenuItem command="showOptions" icon="bx bx-cog" text={t("global_menu.options")} />
|
||||
<FormDropdownDivider />
|
||||
|
||||
<KeyboardActionMenuItem command="showHelp" icon="bx bx-help-circle" text={t("global_menu.show_help")} />
|
||||
<KeyboardActionMenuItem command="showCheatsheet" icon="bx bxs-keyboard" text={t("global_menu.show-cheatsheet")} />
|
||||
<MenuItem command="openAboutDialog" icon="bx bx-info-circle" text={t("global_menu.about")} />
|
||||
{isUpdateAvailable && <MenuItem command={() => window.open("https://github.com/TriliumNext/Trilium/releases/latest")} icon="bx bx-sync" text={`Version ${latestVersion} is available, click to download.`} /> }
|
||||
{!isElectron() && <BrowserOnlyOptions />}
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
function AdvancedMenu() {
|
||||
return (
|
||||
<FormDropdownSubmenu icon="bx bx-chip" title={t("global_menu.advanced")}>
|
||||
<MenuItem command="showHiddenSubtree" icon="bx bx-hide" text={t("global_menu.show_hidden_subtree")} />
|
||||
<MenuItem command="showSearchHistory" icon="bx bx-search-alt" text={t("global_menu.open_search_history")} />
|
||||
<FormDropdownDivider />
|
||||
|
||||
<KeyboardActionMenuItem command="showBackendLog" icon="bx bx-detail" text={t("global_menu.show_backend_log")} />
|
||||
<KeyboardActionMenuItem command="showSQLConsole" icon="bx bx-data" text={t("global_menu.open_sql_console")} />
|
||||
<MenuItem command="showSQLConsoleHistory" icon="bx bx-data" text={t("global_menu.open_sql_console_history")} />
|
||||
<FormDropdownDivider />
|
||||
|
||||
{isElectron() && <MenuItem command="openDevTools" icon="bx bx-bug-alt" text={t("global_menu.open_dev_tools")} />}
|
||||
<KeyboardActionMenuItem command="reloadFrontendApp" icon="bx bx-refresh" text={t("global_menu.reload_frontend")} title={t("global_menu.reload_hint")} />
|
||||
</FormDropdownSubmenu>
|
||||
)
|
||||
}
|
||||
|
||||
function BrowserOnlyOptions() {
|
||||
return <>
|
||||
<FormDropdownDivider />
|
||||
<MenuItem command="logout" icon="bx bx-log-out" text={t("global_menu.logout")} />
|
||||
</>;
|
||||
}
|
||||
|
||||
function SwitchToOptions() {
|
||||
if (isElectron()) {
|
||||
return;
|
||||
} else if (!isMobile()) {
|
||||
return <MenuItem command="switchToMobileVersion" icon="bx bx-mobile" text={t("global_menu.switch_to_mobile_version")} />
|
||||
} else {
|
||||
return <MenuItem command="switchToDesktopVersion" icon="bx bx-desktop" text={t("global_menu.switch_to_desktop_version")} />
|
||||
}
|
||||
}
|
||||
|
||||
function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProps<KeyboardActionNames | CommandNames | (() => void)>) {
|
||||
return <FormListItem
|
||||
icon={icon}
|
||||
title={title}
|
||||
triggerCommand={typeof command === "string" ? command : undefined}
|
||||
onClick={typeof command === "function" ? command : undefined}
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
>{text}</FormListItem>
|
||||
}
|
||||
|
||||
function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<KeyboardActionNames>) {
|
||||
return <MenuItem
|
||||
{...props}
|
||||
command={command}
|
||||
text={<>{text} <KeyboardShortcut actionName={command as KeyboardActionNames} /></>}
|
||||
/>
|
||||
}
|
||||
|
||||
function VerticalLayoutIcon() {
|
||||
const logoRef = useRef<SVGSVGElement>(null);
|
||||
useStaticTooltip(logoRef);
|
||||
|
||||
return (
|
||||
<svg ref={logoRef} viewBox="0 0 256 256" title={t("global_menu.menu")}>
|
||||
<g>
|
||||
<path className="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
|
||||
<path className="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
|
||||
<path className="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/>
|
||||
|
||||
<path className="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/>
|
||||
<path className="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/>
|
||||
<path className="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/>
|
||||
|
||||
<path className="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/>
|
||||
<path className="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/>
|
||||
<path className="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) {
|
||||
const [ zoomLevel, setZoomLevel ] = useState(100);
|
||||
|
||||
function updateZoomState() {
|
||||
if (!isElectron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const zoomFactor = dynamicRequire("electron").webFrame.getZoomFactor();
|
||||
setZoomLevel(Math.round(zoomFactor * 100));
|
||||
}
|
||||
|
||||
useEffect(updateZoomState, []);
|
||||
|
||||
function ZoomControlButton({ command, title, icon, children }: { command: KeyboardActionNames, title: string, icon?: string, children?: ComponentChildren }) {
|
||||
const linkRef = useRef<HTMLAnchorElement>(null);
|
||||
useStaticTooltipWithKeyboardShortcut(linkRef, title, command);
|
||||
return (
|
||||
<a
|
||||
ref={linkRef}
|
||||
onClick={(e) => {
|
||||
parentComponent?.triggerCommand(command);
|
||||
setTimeout(() => updateZoomState(), 300)
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className={icon}
|
||||
>{children}</a>
|
||||
)
|
||||
}
|
||||
|
||||
return isElectron() ? (
|
||||
<FormListItem
|
||||
icon="bx bx-empty"
|
||||
className="zoom-container"
|
||||
>
|
||||
{t("global_menu.zoom")}
|
||||
<>
|
||||
<div className="zoom-buttons">
|
||||
<ZoomControlButton command="toggleFullscreen" title={t("global_menu.toggle_fullscreen")} icon="bx bx-expand-alt" />
|
||||
|
||||
<ZoomControlButton command="zoomOut" title={t("global_menu.zoom_out")} icon="bx bx-minus" />
|
||||
<ZoomControlButton command="zoomReset" title={t("global_menu.reset_zoom_level")}>{zoomLevel}{t("units.percentage")}</ZoomControlButton>
|
||||
<ZoomControlButton command="zoomIn" title={t("global_menu.zoom_in")} icon="bx bx-plus" />
|
||||
</div>
|
||||
</>
|
||||
</FormListItem>
|
||||
) : (
|
||||
<MenuItem icon="bx bx-expand-alt" command="toggleFullscreen" text={t("global_menu.toggle_fullscreen")} />
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleWindowOnTop() {
|
||||
const focusedWindow = isElectron() ? dynamicRequire("@electron/remote").BrowserWindow.getFocusedWindow() : null;
|
||||
const [ isAlwaysOnTop, setIsAlwaysOnTop ] = useState(focusedWindow?.isAlwaysOnTop());
|
||||
|
||||
return (isElectron() &&
|
||||
<MenuItem
|
||||
icon="bx bx-pin"
|
||||
text={t("title_bar_buttons.window-on-top")}
|
||||
active={isAlwaysOnTop}
|
||||
command={() => {
|
||||
const newState = !isAlwaysOnTop;
|
||||
focusedWindow?.setAlwaysOnTop(newState);
|
||||
setIsAlwaysOnTop(newState);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function useTriliumUpdateStatus() {
|
||||
const [ latestVersion, setLatestVersion ] = useState<string>();
|
||||
const [ checkForUpdates ] = useTriliumOptionBool("checkForUpdates");
|
||||
const isUpdateAvailable = utils.isUpdateAvailable(latestVersion, glob.triliumVersion);
|
||||
|
||||
async function updateVersionStatus() {
|
||||
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
|
||||
|
||||
const resp = await fetch(RELEASES_API_URL);
|
||||
const data = await resp.json();
|
||||
const latestVersion = data?.tag_name?.substring(1);
|
||||
setLatestVersion(latestVersion);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!checkForUpdates) {
|
||||
setLatestVersion(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
updateVersionStatus();
|
||||
|
||||
const interval = setInterval(() => updateVersionStatus(), 8 * 60 * 60 * 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [ checkForUpdates ]);
|
||||
|
||||
return { isUpdateAvailable, latestVersion };
|
||||
}
|
||||
@@ -1,24 +1,11 @@
|
||||
import utils from "../../services/utils.js";
|
||||
import contextMenu from "../../menus/context_menu.js";
|
||||
import contextMenu, { MenuCommandItem } from "../../menus/context_menu.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import ButtonFromNoteWidget from "./button_from_note.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { CommandNames } from "../../components/app_context.js";
|
||||
|
||||
interface WebContents {
|
||||
history: string[];
|
||||
getActiveIndex(): number;
|
||||
clearHistory(): void;
|
||||
canGoBack(): boolean;
|
||||
canGoForward(): boolean;
|
||||
goToIndex(index: string): void;
|
||||
}
|
||||
|
||||
interface ContextMenuItem {
|
||||
title: string;
|
||||
idx: string;
|
||||
uiIcon: string;
|
||||
}
|
||||
import type { WebContents } from "electron";
|
||||
import link from "../../services/link.js";
|
||||
|
||||
export default class HistoryNavigationButton extends ButtonFromNoteWidget {
|
||||
private webContents?: WebContents;
|
||||
@@ -51,28 +38,24 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
|
||||
async showContextMenu(e: JQuery.ContextMenuEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.webContents || this.webContents.history.length < 2) {
|
||||
if (!this.webContents || this.webContents.navigationHistory.length() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
let items: ContextMenuItem[] = [];
|
||||
let items: MenuCommandItem<string>[] = [];
|
||||
|
||||
const activeIndex = this.webContents.getActiveIndex();
|
||||
const history = this.webContents.history;
|
||||
const history = this.webContents.navigationHistory.getAllEntries();
|
||||
const activeIndex = this.webContents.navigationHistory.getActiveIndex();
|
||||
|
||||
for (const idx in history) {
|
||||
const url = history[idx];
|
||||
const parts = url.split("#");
|
||||
if (parts.length < 2) continue;
|
||||
|
||||
const notePathWithTab = parts[1];
|
||||
const notePath = notePathWithTab.split("-")[0];
|
||||
const { notePath } = link.parseNavigationStateFromUrl(history[idx].url);
|
||||
if (!notePath) continue;
|
||||
|
||||
const title = await treeService.getNotePathTitle(notePath);
|
||||
|
||||
items.push({
|
||||
title,
|
||||
idx,
|
||||
command: idx,
|
||||
uiIcon:
|
||||
parseInt(idx) === activeIndex
|
||||
? "bx bx-radio-circle-marked" // compare with type coercion!
|
||||
@@ -92,9 +75,10 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items,
|
||||
selectMenuItemHandler: (item: any) => {
|
||||
if (item && item.idx && this.webContents) {
|
||||
this.webContents.goToIndex(item.idx);
|
||||
selectMenuItemHandler: (item: MenuCommandItem<string>) => {
|
||||
if (item && item.command && this.webContents) {
|
||||
const idx = parseInt(item.command, 10);
|
||||
this.webContents.navigationHistory.goToIndex(idx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import options from "../../services/options.js";
|
||||
import splitService from "../../services/resizer.js";
|
||||
import CommandButtonWidget from "./command_button.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
|
||||
export default class LeftPaneToggleWidget extends CommandButtonWidget {
|
||||
private currentLeftPaneVisible: boolean;
|
||||
|
||||
constructor(isHorizontalLayout: boolean) {
|
||||
super();
|
||||
|
||||
this.currentLeftPaneVisible = options.is("leftPaneVisible");
|
||||
|
||||
this.class(isHorizontalLayout ? "toggle-button" : "launcher-button");
|
||||
|
||||
this.settings.icon = () => {
|
||||
if (options.get("layoutOrientation") === "horizontal") {
|
||||
return "bx-sidebar";
|
||||
}
|
||||
|
||||
return this.currentLeftPaneVisible ? "bx-chevrons-left" : "bx-chevrons-right";
|
||||
};
|
||||
|
||||
this.settings.title = () => (this.currentLeftPaneVisible ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel"));
|
||||
|
||||
this.settings.command = () => (this.currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane");
|
||||
|
||||
if (isHorizontalLayout) {
|
||||
this.settings.titlePlacement = "bottom";
|
||||
}
|
||||
}
|
||||
|
||||
refreshIcon() {
|
||||
super.refreshIcon();
|
||||
splitService.setupLeftPaneResizer(this.currentLeftPaneVisible);
|
||||
}
|
||||
|
||||
setLeftPaneVisibilityEvent({ leftPaneVisible }: EventData<"setLeftPaneVisibility">) {
|
||||
this.currentLeftPaneVisible = leftPaneVisible ?? !this.currentLeftPaneVisible;
|
||||
this.refreshIcon();
|
||||
}
|
||||
}
|
||||
29
apps/client/src/widgets/buttons/left_pane_toggle.tsx
Normal file
29
apps/client/src/widgets/buttons/left_pane_toggle.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import options from "../../services/options";
|
||||
import { t } from "../../services/i18n";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import resizer from "../../services/resizer";
|
||||
|
||||
export default function LeftPaneToggle({ isHorizontalLayout }: { isHorizontalLayout: boolean }) {
|
||||
const [ currentLeftPaneVisible, setCurrentLeftPaneVisible ] = useState(options.is("leftPaneVisible"));
|
||||
|
||||
useTriliumEvent("setLeftPaneVisibility", ({ leftPaneVisible }) => {
|
||||
setCurrentLeftPaneVisible(leftPaneVisible ?? !currentLeftPaneVisible);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
resizer.setupLeftPaneResizer(currentLeftPaneVisible);
|
||||
}, [ currentLeftPaneVisible ]);
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className={`${isHorizontalLayout ? "toggle-button" : "launcher-button"}`}
|
||||
text={currentLeftPaneVisible ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel")}
|
||||
triggerCommand={currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane"}
|
||||
icon={isHorizontalLayout
|
||||
? "bx bx-sidebar"
|
||||
: (currentLeftPaneVisible ? "bx bx-chevrons-left" : "bx bx-chevrons-right" )}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { FAttachmentRow } from "../../entities/fattachment.js";
|
||||
|
||||
// TODO: Deduplicate with server
|
||||
interface ConvertToAttachmentResponse {
|
||||
attachment: FAttachmentRow;
|
||||
}
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown note-actions">
|
||||
<style>
|
||||
.note-actions {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-menu {
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-item .bx {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 120%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
|
||||
color: var(--muted-text-color) !important;
|
||||
background-color: transparent !important;
|
||||
pointer-events: none; /* makes it unclickable */
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
class="icon-action bx bx-dots-vertical-rounded"></button>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<li data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">
|
||||
<span class="bx bx-paperclip"></span> ${t("note_actions.convert_into_attachment")}
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="renderActiveNote" class="dropdown-item render-note-button">
|
||||
<span class="bx bx-extension"></span> ${t("note_actions.re_render_note")}<kbd data-command="renderActiveNote"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="findInText" class="dropdown-item find-in-text-button">
|
||||
<span class='bx bx-search'></span> ${t("note_actions.search_in_note")}<kbd data-command="findInText"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button">
|
||||
<span class="bx bx-printer"></span> ${t("note_actions.print_note")}<kbd data-command="printActiveNote"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="exportAsPdf" class="dropdown-item export-as-pdf-button">
|
||||
<span class="bx bxs-file-pdf"></span> ${t("note_actions.print_pdf")}<kbd data-command="exportAsPdf"></kbd>
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
<li class="dropdown-item import-files-button"><span class="bx bx-import"></span> ${t("note_actions.import_files")}</li>
|
||||
|
||||
<li class="dropdown-item export-note-button"><span class="bx bx-export"></span> ${t("note_actions.export_note")}</li>
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
|
||||
<li data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button" title="${t("note_actions.open_note_externally_title")}">
|
||||
<span class="bx bx-file-find"></span> ${t("note_actions.open_note_externally")}<kbd data-command="openNoteExternally"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button">
|
||||
<span class="bx bx-customize"></span> ${t("note_actions.open_note_custom")}<kbd data-command="openNoteCustom"></kbd>
|
||||
</li>
|
||||
|
||||
<li data-trigger-command="showNoteSource" class="dropdown-item show-source-button">
|
||||
<span class="bx bx-code"></span> ${t("note_actions.note_source")}<kbd data-command="showNoteSource"></kbd>
|
||||
</li>
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
<li data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button">
|
||||
<span class="bx bx-save"></span> ${t("note_actions.save_revision")}<kbd data-command="forceSaveRevision"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item delete-note-button"><span class="bx bx-trash destructive-action-icon"></span> ${t("note_actions.delete_note")}</li>
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
<li data-trigger-command="showAttachments" class="dropdown-item show-attachments-button">
|
||||
<span class="bx bx-paperclip"></span> ${t("note_actions.note_attachments")}<kbd data-command="showAttachments"></kbd>
|
||||
</li>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $convertNoteIntoAttachmentButton!: JQuery<HTMLElement>;
|
||||
private $findInTextButton!: JQuery<HTMLElement>;
|
||||
private $printActiveNoteButton!: JQuery<HTMLElement>;
|
||||
private $exportAsPdfButton!: JQuery<HTMLElement>;
|
||||
private $showSourceButton!: JQuery<HTMLElement>;
|
||||
private $showAttachmentsButton!: JQuery<HTMLElement>;
|
||||
private $renderNoteButton!: JQuery<HTMLElement>;
|
||||
private $saveRevisionButton!: JQuery<HTMLElement>;
|
||||
private $exportNoteButton!: JQuery<HTMLElement>;
|
||||
private $importNoteButton!: JQuery<HTMLElement>;
|
||||
private $openNoteExternallyButton!: JQuery<HTMLElement>;
|
||||
private $openNoteCustomButton!: JQuery<HTMLElement>;
|
||||
private $deleteNoteButton!: JQuery<HTMLElement>;
|
||||
|
||||
isEnabled() {
|
||||
return this.note?.type !== "launcher";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("show.bs.dropdown", () => {
|
||||
if (this.note) {
|
||||
this.refreshVisibility(this.note);
|
||||
}
|
||||
});
|
||||
|
||||
this.$convertNoteIntoAttachmentButton = this.$widget.find("[data-trigger-command='convertNoteIntoAttachment']");
|
||||
this.$findInTextButton = this.$widget.find(".find-in-text-button");
|
||||
this.$printActiveNoteButton = this.$widget.find(".print-active-note-button");
|
||||
this.$exportAsPdfButton = this.$widget.find(".export-as-pdf-button");
|
||||
this.$showSourceButton = this.$widget.find(".show-source-button");
|
||||
this.$showAttachmentsButton = this.$widget.find(".show-attachments-button");
|
||||
this.$renderNoteButton = this.$widget.find(".render-note-button");
|
||||
this.$saveRevisionButton = this.$widget.find(".save-revision-button");
|
||||
|
||||
this.$exportNoteButton = this.$widget.find(".export-note-button");
|
||||
this.$exportNoteButton.on("click", () => {
|
||||
if (this.$exportNoteButton.hasClass("disabled") || !this.noteContext?.notePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerCommand("showExportDialog", {
|
||||
notePath: this.noteContext.notePath,
|
||||
defaultType: "single"
|
||||
});
|
||||
});
|
||||
|
||||
this.$importNoteButton = this.$widget.find(".import-files-button");
|
||||
this.$importNoteButton.on("click", () => {
|
||||
if (this.noteId) {
|
||||
this.triggerCommand("showImportDialog", { noteId: this.noteId });
|
||||
}
|
||||
});
|
||||
|
||||
this.$widget.on("click", ".dropdown-item", () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown("toggle"));
|
||||
|
||||
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
|
||||
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");
|
||||
|
||||
this.$deleteNoteButton = this.$widget.find(".delete-note-button");
|
||||
this.$deleteNoteButton.on("click", () => {
|
||||
if (!this.note || this.note.noteId === "root") {
|
||||
return;
|
||||
}
|
||||
|
||||
branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshVisibility(note: FNote) {
|
||||
const isInOptions = note.noteId.startsWith("_options");
|
||||
|
||||
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
|
||||
|
||||
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap", "doc"].includes(note.type));
|
||||
|
||||
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
|
||||
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type));
|
||||
|
||||
const canPrint = ["text", "code"].includes(note.type);
|
||||
this.toggleDisabled(this.$printActiveNoteButton, canPrint);
|
||||
this.toggleDisabled(this.$exportAsPdfButton, canPrint);
|
||||
this.$exportAsPdfButton.toggleClass("hidden-ext", !utils.isElectron());
|
||||
|
||||
this.$renderNoteButton.toggle(note.type === "render");
|
||||
|
||||
this.toggleDisabled(this.$openNoteExternallyButton, utils.isElectron() && !["search", "book"].includes(note.type));
|
||||
this.toggleDisabled(
|
||||
this.$openNoteCustomButton,
|
||||
utils.isElectron() &&
|
||||
!utils.isMac() && // no implementation for Mac yet
|
||||
!["search", "book"].includes(note.type)
|
||||
);
|
||||
|
||||
// I don't want to handle all special notes like this, but intuitively user might want to export content of backend log
|
||||
this.toggleDisabled(this.$exportNoteButton, !["_backendLog"].includes(note.noteId) && !isInOptions);
|
||||
|
||||
this.toggleDisabled(this.$importNoteButton, !["search"].includes(note.type) && !isInOptions);
|
||||
this.toggleDisabled(this.$deleteNoteButton, !isInOptions);
|
||||
this.toggleDisabled(this.$saveRevisionButton, !isInOptions);
|
||||
}
|
||||
|
||||
async convertNoteIntoAttachmentCommand() {
|
||||
if (!this.note || !(await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title })))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { attachment: newAttachment } = await server.post<ConvertToAttachmentResponse>(`notes/${this.noteId}/convert-to-attachment`);
|
||||
|
||||
if (!newAttachment) {
|
||||
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(newAttachment.ownerId, {
|
||||
viewScope: {
|
||||
viewMode: "attachments",
|
||||
attachmentId: newAttachment.attachmentId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleDisabled($el: JQuery<HTMLElement>, enable: boolean) {
|
||||
if (enable) {
|
||||
$el.removeAttr("disabled");
|
||||
} else {
|
||||
$el.attr("disabled", "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import CommandButtonWidget from "./command_button.js";
|
||||
|
||||
export default class RevisionsButton extends CommandButtonWidget {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-history").title(t("revisions_button.note_revisions")).command("showRevisions").titlePlacement("bottom").class("icon-action");
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && !["launcher", "doc"].includes(this.note?.type ?? "");
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import OnClickButtonWidget from "./onclick_button.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import LoadResults from "../../services/load_results.js";
|
||||
import type { AttributeRow } from "../../services/load_results.js";
|
||||
|
||||
export default class ShowHighlightsListWidgetButton extends OnClickButtonWidget {
|
||||
isEnabled(): boolean {
|
||||
return Boolean(super.isEnabled() && this.note && this.note.type === "text" && this.noteContext?.viewScope?.viewMode === "default");
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-bookmarks")
|
||||
.title(t("show_highlights_list_widget_button.show_highlights_list"))
|
||||
.titlePlacement("bottom")
|
||||
.onClick(() => {
|
||||
if (this.noteContext?.viewScope && this.noteId) {
|
||||
this.noteContext.viewScope.highlightsListTemporarilyHidden = false;
|
||||
appContext.triggerEvent("showHighlightsListWidget", { noteId: this.noteId });
|
||||
}
|
||||
this.toggleInt(false);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(): Promise<void> {
|
||||
if (this.noteContext?.viewScope) {
|
||||
this.toggleInt(this.noteContext.viewScope.highlightsListTemporarilyHidden);
|
||||
}
|
||||
}
|
||||
|
||||
async reEvaluateHighlightsListWidgetVisibilityEvent({ noteId }: { noteId: string }): Promise<void> {
|
||||
if (noteId === this.noteId) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: { loadResults: LoadResults }): Promise<void> {
|
||||
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
await this.refresh();
|
||||
} else if (
|
||||
loadResults
|
||||
.getAttributeRows()
|
||||
.find((attr: AttributeRow) =>
|
||||
attr.type === "label" &&
|
||||
(attr.name?.toLowerCase().includes("readonly") || attr.name === "hideHighlightWidget") &&
|
||||
this.note &&
|
||||
attributeService.isAffecting(attr, this.note)
|
||||
)
|
||||
) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }: { noteId: string }): Promise<void> {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import OnClickButtonWidget from "./onclick_button.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import LoadResults from "../../services/load_results.js";
|
||||
import type { AttributeRow } from "../../services/load_results.js";
|
||||
|
||||
export default class ShowTocWidgetButton extends OnClickButtonWidget {
|
||||
isEnabled(): boolean {
|
||||
return Boolean(super.isEnabled() && this.note && this.note.type === "text" && this.noteContext?.viewScope?.viewMode === "default");
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-tn-toc")
|
||||
.title(t("show_toc_widget_button.show_toc"))
|
||||
.titlePlacement("bottom")
|
||||
.onClick(() => {
|
||||
if (this.noteContext?.viewScope && this.noteId) {
|
||||
this.noteContext.viewScope.tocTemporarilyHidden = false;
|
||||
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
|
||||
}
|
||||
this.toggleInt(false);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(): Promise<void> {
|
||||
if (this.noteContext?.viewScope) {
|
||||
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
|
||||
}
|
||||
}
|
||||
|
||||
async reEvaluateTocWidgetVisibilityEvent({ noteId }: { noteId: string }): Promise<void> {
|
||||
if (noteId === this.noteId) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: { loadResults: LoadResults }): Promise<void> {
|
||||
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
await this.refresh();
|
||||
} else if (
|
||||
loadResults
|
||||
.getAttributeRows()
|
||||
.find((attr: AttributeRow) =>
|
||||
attr.type === "label" &&
|
||||
(attr.name?.toLowerCase().includes("readonly") || attr.name === "toc") &&
|
||||
this.note &&
|
||||
attributeService.isAffecting(attr, this.note)
|
||||
)
|
||||
) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }: { noteId: string }): Promise<void> {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div style="display: none;">
|
||||
<style>
|
||||
.global-menu-button-update-available-button {
|
||||
width: 21px !important;
|
||||
height: 21px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
border-radius: var(--button-border-radius);
|
||||
transform: scale(0.9);
|
||||
border: none;
|
||||
opacity: 0.8;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.global-menu-button-wrapper:hover .global-menu-button-update-available-button {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<span class="bx bx-sync global-menu-button-update-available-button" title="${t("update_available.update_available")}"></span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class UpdateAvailableWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
}
|
||||
|
||||
updateVersionStatus(latestVersion: string) {
|
||||
this.$widget.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
|
||||
}
|
||||
}
|
||||
26
apps/client/src/widgets/close_zen_button.css
Normal file
26
apps/client/src/widgets/close_zen_button.css
Normal file
@@ -0,0 +1,26 @@
|
||||
:root {
|
||||
--zen-button-size: 32px;
|
||||
}
|
||||
|
||||
.close-zen-container {
|
||||
width: var(--zen-button-size);
|
||||
height: var(--zen-button-size);
|
||||
}
|
||||
|
||||
body.zen .close-zen-container {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
z-index: 9999;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
body.zen.mobile .close-zen-container {
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
body.zen.electron:not(.platform-darwin):not(.native-titlebar) .close-zen-container {
|
||||
left: calc(env(titlebar-area-width) - var(--zen-button-size) - 2px);
|
||||
right: unset;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="close-zen-container">
|
||||
<button class="button-widget bx icon-action bxs-yin-yang"
|
||||
data-trigger-command="toggleZenMode"
|
||||
title="${t("zen_mode.button_exit")}"
|
||||
/>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--zen-button-size: 32px;
|
||||
}
|
||||
|
||||
.close-zen-container {
|
||||
display: none;
|
||||
width: var(--zen-button-size);
|
||||
height: var(--zen-button-size);
|
||||
}
|
||||
|
||||
body.zen .close-zen-container {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
z-index: 9999;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
body.zen.mobile .close-zen-container {
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
body.zen.electron:not(.platform-darwin):not(.native-titlebar) .close-zen-container {
|
||||
left: calc(env(titlebar-area-width) - var(--zen-button-size) - 2px);
|
||||
right: unset;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class CloseZenButton extends BasicWidget {
|
||||
|
||||
doRender(): void {
|
||||
this.$widget = $(TPL);
|
||||
}
|
||||
|
||||
zenChangedEvent() {
|
||||
this.toggleInt(true);
|
||||
}
|
||||
|
||||
}
|
||||
25
apps/client/src/widgets/close_zen_button.tsx
Normal file
25
apps/client/src/widgets/close_zen_button.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { t } from "../services/i18n";
|
||||
import ActionButton from "./react/ActionButton";
|
||||
import { useTriliumEvent } from "./react/hooks";
|
||||
import "./close_zen_button.css";
|
||||
|
||||
export default function CloseZenModeButton() {
|
||||
const [ zenModeEnabled, setZenModeEnabled ] = useState(false);
|
||||
|
||||
useTriliumEvent("zenModeChanged", ({ isEnabled }) => {
|
||||
setZenModeEnabled(isEnabled);
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={`close-zen-container ${!zenModeEnabled ? "hidden-ext" : ""}`}>
|
||||
{zenModeEnabled && (
|
||||
<ActionButton
|
||||
icon="bx bxs-yin-yang"
|
||||
triggerCommand="toggleZenMode"
|
||||
text={t("zen_mode.button_exit")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import keyboardActionsService from "../../services/keyboard_actions.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import type CommandButtonWidget from "../buttons/command_button.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { NoteType } from "../../entities/fnote.js";
|
||||
import type { EventData, EventNames } from "../../components/app_context.js";
|
||||
import type NoteActionsWidget from "../buttons/note_actions.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="ribbon-container">
|
||||
<style>
|
||||
.ribbon-container {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.ribbon-top-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ribbon-tab-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
flex-grow: 1;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.ribbon-tab-title {
|
||||
color: var(--muted-text-color);
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
min-width: 24px;
|
||||
flex-basis: 24px;
|
||||
max-width: max-content;
|
||||
flex-grow: 10;
|
||||
}
|
||||
|
||||
.ribbon-tab-title .bx {
|
||||
font-size: 150%;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.ribbon-tab-title.active {
|
||||
color: var(--main-text-color);
|
||||
border-bottom: 3px solid var(--main-text-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ribbon-tab-title:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ribbon-tab-title:hover {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.ribbon-tab-title:first-of-type {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.ribbon-tab-spacer {
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
max-width: 35px;
|
||||
flex-grow: 1;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.ribbon-tab-spacer:last-of-type {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
max-width: 10000px;
|
||||
}
|
||||
|
||||
.ribbon-button-container {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ribbon-button-container > * {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.ribbon-body {
|
||||
display: none;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
margin-left: 10px;
|
||||
margin-right: 5px; /* needs to have this value so that the bottom border is the same width as the top one */
|
||||
}
|
||||
|
||||
.ribbon-body.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ribbon-tab-title-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ribbon-tab-title.active .ribbon-tab-title-label {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="ribbon-top-row">
|
||||
<div class="ribbon-tab-container"></div>
|
||||
<div class="ribbon-button-container"></div>
|
||||
</div>
|
||||
|
||||
<div class="ribbon-body-container"></div>
|
||||
</div>`;
|
||||
|
||||
type ButtonWidget = (CommandButtonWidget | NoteActionsWidget);
|
||||
|
||||
export default class RibbonContainer extends NoteContextAwareWidget {
|
||||
|
||||
private lastActiveComponentId?: string | null;
|
||||
private lastNoteType?: NoteType;
|
||||
|
||||
private ribbonWidgets: NoteContextAwareWidget[];
|
||||
private buttonWidgets: ButtonWidget[];
|
||||
private $tabContainer!: JQuery<HTMLElement>;
|
||||
private $buttonContainer!: JQuery<HTMLElement>;
|
||||
private $bodyContainer!: JQuery<HTMLElement>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.contentSized();
|
||||
this.ribbonWidgets = [];
|
||||
this.buttonWidgets = [];
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
ribbon(widget: NoteContextAwareWidget) {
|
||||
// TODO: Base class
|
||||
super.child(widget);
|
||||
|
||||
this.ribbonWidgets.push(widget);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
button(widget: ButtonWidget) {
|
||||
super.child(widget);
|
||||
|
||||
this.buttonWidgets.push(widget);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$tabContainer = this.$widget.find(".ribbon-tab-container");
|
||||
this.$buttonContainer = this.$widget.find(".ribbon-button-container");
|
||||
this.$bodyContainer = this.$widget.find(".ribbon-body-container");
|
||||
|
||||
for (const ribbonWidget of this.ribbonWidgets) {
|
||||
this.$bodyContainer.append($('<div class="ribbon-body">').attr("data-ribbon-component-id", ribbonWidget.componentId).append(ribbonWidget.render()));
|
||||
}
|
||||
|
||||
for (const buttonWidget of this.buttonWidgets) {
|
||||
this.$buttonContainer.append(buttonWidget.render());
|
||||
}
|
||||
|
||||
this.$tabContainer.on("click", ".ribbon-tab-title", (e) => {
|
||||
const $ribbonTitle = $(e.target).closest(".ribbon-tab-title");
|
||||
|
||||
this.toggleRibbonTab($ribbonTitle);
|
||||
});
|
||||
}
|
||||
|
||||
toggleRibbonTab($ribbonTitle: JQuery<HTMLElement>, refreshActiveTab = true) {
|
||||
const activate = !$ribbonTitle.hasClass("active");
|
||||
|
||||
this.$tabContainer.find(".ribbon-tab-title").removeClass("active");
|
||||
this.$bodyContainer.find(".ribbon-body").removeClass("active");
|
||||
|
||||
if (activate) {
|
||||
const ribbonComponendId = $ribbonTitle.attr("data-ribbon-component-id");
|
||||
|
||||
const wasAlreadyActive = this.lastActiveComponentId === ribbonComponendId;
|
||||
|
||||
this.lastActiveComponentId = ribbonComponendId;
|
||||
|
||||
this.$tabContainer.find(`.ribbon-tab-title[data-ribbon-component-id="${ribbonComponendId}"]`).addClass("active");
|
||||
this.$bodyContainer.find(`.ribbon-body[data-ribbon-component-id="${ribbonComponendId}"]`).addClass("active");
|
||||
|
||||
const activeChild = this.getActiveRibbonWidget();
|
||||
|
||||
if (activeChild && (refreshActiveTab || !wasAlreadyActive) && this.noteContext && this.notePath) {
|
||||
const handleEventPromise = activeChild.handleEvent("noteSwitched", { noteContext: this.noteContext, notePath: this.notePath });
|
||||
|
||||
if (refreshActiveTab) {
|
||||
if (handleEventPromise) {
|
||||
handleEventPromise.then(() => (activeChild as any).focus?.()); // TODO: Base class
|
||||
} else {
|
||||
// TODO: Base class
|
||||
(activeChild as any).focus?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.lastActiveComponentId = null;
|
||||
}
|
||||
}
|
||||
|
||||
async noteSwitched() {
|
||||
this.lastActiveComponentId = null;
|
||||
|
||||
await super.noteSwitched();
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote, noExplicitActivation = false) {
|
||||
this.lastNoteType = note.type;
|
||||
|
||||
let $ribbonTabToActivate, $lastActiveRibbon;
|
||||
|
||||
this.$tabContainer.empty();
|
||||
|
||||
for (const ribbonWidget of this.ribbonWidgets) {
|
||||
// TODO: Base class for ribbon widget
|
||||
const ret = await (ribbonWidget as any).getTitle(note);
|
||||
|
||||
if (!ret.show) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $ribbonTitle = $('<div class="ribbon-tab-title">')
|
||||
.attr("data-ribbon-component-id", ribbonWidget.componentId)
|
||||
.attr("data-ribbon-component-name", (ribbonWidget as any).name as string) // TODO: base class for ribbon widgets
|
||||
.append(
|
||||
$('<span class="ribbon-tab-title-icon">')
|
||||
.addClass(ret.icon)
|
||||
.attr("title", ret.title)
|
||||
.attr("data-toggle-command", (ribbonWidget as any).toggleCommand)
|
||||
) // TODO: base class
|
||||
.append(" ")
|
||||
.append($('<span class="ribbon-tab-title-label">').text(ret.title));
|
||||
|
||||
this.$tabContainer.append($ribbonTitle);
|
||||
this.$tabContainer.append('<div class="ribbon-tab-spacer">');
|
||||
|
||||
if (ret.activate && !this.lastActiveComponentId && !$ribbonTabToActivate && !noExplicitActivation) {
|
||||
$ribbonTabToActivate = $ribbonTitle;
|
||||
}
|
||||
|
||||
if (this.lastActiveComponentId === ribbonWidget.componentId) {
|
||||
$lastActiveRibbon = $ribbonTitle;
|
||||
}
|
||||
}
|
||||
|
||||
keyboardActionsService.getActions().then((actions) => {
|
||||
this.$tabContainer.find(".ribbon-tab-title-icon").tooltip({
|
||||
title: () => {
|
||||
const toggleCommandName = $(this).attr("data-toggle-command");
|
||||
const action = actions.find((act) => act.actionName === toggleCommandName);
|
||||
const title = $(this).attr("data-title");
|
||||
|
||||
if (action?.effectiveShortcuts && action.effectiveShortcuts.length > 0) {
|
||||
return `${title} (${action.effectiveShortcuts.join(", ")})`;
|
||||
} else {
|
||||
return title ?? "";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!$ribbonTabToActivate) {
|
||||
$ribbonTabToActivate = $lastActiveRibbon;
|
||||
}
|
||||
|
||||
if ($ribbonTabToActivate) {
|
||||
this.toggleRibbonTab($ribbonTabToActivate, false);
|
||||
} else {
|
||||
this.$bodyContainer.find(".ribbon-body").removeClass("active");
|
||||
}
|
||||
}
|
||||
|
||||
isRibbonTabActive(name: string) {
|
||||
const $ribbonComponent = this.$widget.find(`.ribbon-tab-title[data-ribbon-component-name='${name}']`);
|
||||
|
||||
return $ribbonComponent.hasClass("active");
|
||||
}
|
||||
|
||||
ensureOwnedAttributesAreOpen(ntxId: string | null | undefined) {
|
||||
if (ntxId && this.isNoteContext(ntxId) && !this.isRibbonTabActive("ownedAttributes")) {
|
||||
this.toggleRibbonTabWithName("ownedAttributes", ntxId);
|
||||
}
|
||||
}
|
||||
|
||||
addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) {
|
||||
this.ensureOwnedAttributesAreOpen(ntxId);
|
||||
}
|
||||
|
||||
addNewRelationEvent({ ntxId }: EventData<"addNewRelation">) {
|
||||
this.ensureOwnedAttributesAreOpen(ntxId);
|
||||
}
|
||||
|
||||
toggleRibbonTabWithName(name: string, ntxId?: string) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const $ribbonComponent = this.$widget.find(`.ribbon-tab-title[data-ribbon-component-name='${name}']`);
|
||||
|
||||
if ($ribbonComponent) {
|
||||
this.toggleRibbonTab($ribbonComponent);
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent<T extends EventNames>(name: T, data: EventData<T>) {
|
||||
const PREFIX = "toggleRibbonTab";
|
||||
|
||||
if (name.startsWith(PREFIX)) {
|
||||
let componentName = name.substr(PREFIX.length);
|
||||
componentName = componentName[0].toLowerCase() + componentName.substr(1);
|
||||
|
||||
this.toggleRibbonTabWithName(componentName, (data as any).ntxId);
|
||||
} else {
|
||||
return super.handleEvent(name, data);
|
||||
}
|
||||
}
|
||||
|
||||
async handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>) {
|
||||
if (["activeContextChanged", "setNoteContext"].includes(name)) {
|
||||
// won't trigger .refresh();
|
||||
await super.handleEventInChildren("setNoteContext", data as EventData<"activeContextChanged" | "setNoteContext">);
|
||||
} else if (this.isEnabled() || name === "initialRenderComplete") {
|
||||
const activeRibbonWidget = this.getActiveRibbonWidget();
|
||||
|
||||
// forward events only to active ribbon tab, inactive ones don't need to be updated
|
||||
if (activeRibbonWidget) {
|
||||
await activeRibbonWidget.handleEvent(name, data);
|
||||
}
|
||||
|
||||
for (const buttonWidget of this.buttonWidgets) {
|
||||
await buttonWidget.handleEvent(name, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.noteId && loadResults.isNoteReloaded(this.noteId) && this.lastNoteType !== this.note.type) {
|
||||
// note type influences the list of available ribbon tabs the most
|
||||
// check for the type is so that we don't update on each title rename
|
||||
this.lastNoteType = this.note.type;
|
||||
|
||||
this.refresh();
|
||||
} else if (loadResults.getAttributeRows(this.componentId).find((attr) => attributeService.isAffecting(attr, this.note))) {
|
||||
this.refreshWithNote(this.note, true);
|
||||
}
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent() {
|
||||
// We are ignoring the event which triggers a refresh since it is usually already done by a different
|
||||
// event and causing a race condition in which the items appear twice.
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed as soon as the user presses the "Edit" floating button in a read-only text note.
|
||||
*
|
||||
* <p>
|
||||
* We need to refresh the ribbon for cases such as the classic editor which relies on the read-only state.
|
||||
*/
|
||||
readOnlyTemporarilyDisabledEvent() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
getActiveRibbonWidget() {
|
||||
return this.ribbonWidgets.find((ch) => ch.componentId === this.lastActiveComponentId);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { formatDateTime } from "../../utils/formatters.js";
|
||||
@@ -8,11 +7,11 @@ import openService from "../../services/open.js";
|
||||
import { useState } from "preact/hooks";
|
||||
import type { CSSProperties } from "preact/compat";
|
||||
import type { AppInfo } from "@triliumnext/commons";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
function AboutDialogComponent() {
|
||||
let [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
||||
let [shown, setShown] = useState(false);
|
||||
export default function AboutDialog() {
|
||||
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
||||
const [shown, setShown] = useState(false);
|
||||
const forceWordBreak: CSSProperties = { wordBreak: "break-all" };
|
||||
|
||||
useTriliumEvent("openAboutDialog", () => setShown(true));
|
||||
@@ -82,11 +81,3 @@ function DirectoryLink({ directory, style }: { directory: string, style?: CSSPro
|
||||
return <span style={style}>{directory}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
export default class AboutDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <AboutDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { t } from "../../services/i18n";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Button from "../react/Button";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
@@ -11,11 +10,11 @@ import { default as TextTypeWidget } from "../type_widgets/editable_text.js";
|
||||
import { logError } from "../../services/ws";
|
||||
import FormGroup from "../react/FormGroup.js";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
type LinkType = "reference-link" | "external-link" | "hyper-link";
|
||||
|
||||
function AddLinkDialogComponent() {
|
||||
export default function AddLinkDialog() {
|
||||
const [ textTypeWidget, setTextTypeWidget ] = useState<TextTypeWidget>();
|
||||
const initialText = useRef<string>();
|
||||
const [ linkTitle, setLinkTitle ] = useState("");
|
||||
@@ -160,11 +159,3 @@ function AddLinkDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class AddLinkDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <AddLinkDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@ import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import toast from "../../services/toast.js";
|
||||
import Modal from "../react/Modal.jsx";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import tree from "../../services/tree.js";
|
||||
import Button from "../react/Button.jsx";
|
||||
import FormGroup from "../react/FormGroup.js";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
import FBranch from "../../entities/fbranch.js";
|
||||
|
||||
function BranchPrefixDialogComponent() {
|
||||
export default function BranchPrefixDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const [ branch, setBranch ] = useState<FBranch>();
|
||||
const [ prefix, setPrefix ] = useState(branch?.prefix ?? "");
|
||||
@@ -75,14 +74,6 @@ function BranchPrefixDialogComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default class BranchPrefixDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <BranchPrefixDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function savePrefix(branchId: string, prefix: string) {
|
||||
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
|
||||
toast.showMessage(t("branch_prefix.branch_prefix_saved"));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState, useCallback } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import "./bulk_actions.css";
|
||||
import { BulkActionAffectedNotes } from "@triliumnext/commons";
|
||||
import server from "../../services/server";
|
||||
@@ -12,9 +11,9 @@ import toast from "../../services/toast";
|
||||
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
|
||||
import FNote from "../../entities/fnote";
|
||||
import froca from "../../services/froca";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function BulkActionComponent() {
|
||||
export default function BulkActionsDialog() {
|
||||
const [ selectedOrActiveNoteIds, setSelectedOrActiveNoteIds ] = useState<string[]>();
|
||||
const [ bulkActionNote, setBulkActionNote ] = useState<FNote | null>();
|
||||
const [ includeDescendants, setIncludeDescendants ] = useState(false);
|
||||
@@ -51,7 +50,7 @@ function BulkActionComponent() {
|
||||
row.type === "label" && row.name === "action" && row.noteId === "_bulkAction")) {
|
||||
refreshExistingActions();
|
||||
}
|
||||
}, shown);
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -117,11 +116,3 @@ function ExistingActionsList({ existingActions }: { existingActions?: RenameNote
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export default class BulkActionsDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <BulkActionComponent />
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { useMemo, useState } from "preact/hooks";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import { CallToAction, dismissCallToAction, getCallToActions } from "./call_to_action_definitions";
|
||||
import { dismissCallToAction, getCallToActions } from "./call_to_action_definitions";
|
||||
import { t } from "../../services/i18n";
|
||||
|
||||
function CallToActionDialogComponent({ activeCallToActions }: { activeCallToActions: CallToAction[] }) {
|
||||
if (!activeCallToActions.length) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default function CallToActionDialog() {
|
||||
const activeCallToActions = useMemo(() => getCallToActions(), []);
|
||||
const [ activeIndex, setActiveIndex ] = useState(0);
|
||||
const [ shown, setShown ] = useState(true);
|
||||
const activeItem = activeCallToActions[activeIndex];
|
||||
@@ -22,7 +18,7 @@ function CallToActionDialogComponent({ activeCallToActions }: { activeCallToActi
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
return (activeCallToActions.length &&
|
||||
<Modal
|
||||
className="call-to-action"
|
||||
size="md"
|
||||
@@ -48,11 +44,3 @@ function CallToActionDialogComponent({ activeCallToActions }: { activeCallToActi
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export class CallToActionDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <CallToActionDialogComponent activeCallToActions={getCallToActions()} />
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { useRef, useState } from "preact/hooks";
|
||||
import appContext from "../../components/app_context";
|
||||
import { t } from "../../services/i18n";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
import froca from "../../services/froca";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
@@ -14,9 +13,9 @@ import tree from "../../services/tree";
|
||||
import branches from "../../services/branches";
|
||||
import toast from "../../services/toast";
|
||||
import NoteList from "../react/NoteList";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function CloneToDialogComponent() {
|
||||
export default function CloneToDialog() {
|
||||
const [ clonedNoteIds, setClonedNoteIds ] = useState<string[]>();
|
||||
const [ prefix, setPrefix ] = useState("");
|
||||
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
|
||||
@@ -83,14 +82,6 @@ function CloneToDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
export default class CloneToDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <CloneToDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function cloneNotesTo(notePath: string, clonedNoteIds: string[], prefix?: string) {
|
||||
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
|
||||
if (!noteId || !parentNoteId) {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import Button from "../react/Button";
|
||||
import { t } from "../../services/i18n";
|
||||
import { useState } from "preact/hooks";
|
||||
import FormCheckbox from "../react/FormCheckbox";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
title?: string;
|
||||
@@ -13,7 +12,7 @@ interface ConfirmDialogProps {
|
||||
isConfirmDeleteNoteBox?: boolean;
|
||||
}
|
||||
|
||||
function ConfirmDialogComponent() {
|
||||
export default function ConfirmDialog() {
|
||||
const [ opts, setOpts ] = useState<ConfirmDialogProps>();
|
||||
const [ isDeleteNoteChecked, setIsDeleteNoteChecked ] = useState(false);
|
||||
const [ shown, setShown ] = useState(false);
|
||||
@@ -92,11 +91,3 @@ export interface ConfirmWithTitleOptions {
|
||||
title: string;
|
||||
callback: ConfirmDialogCallback;
|
||||
}
|
||||
|
||||
export default class ConfirmDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ConfirmDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { useRef, useState, useEffect } from "preact/hooks";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import FormCheckbox from "../react/FormCheckbox.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import type { DeleteNotesPreview } from "@triliumnext/commons";
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
@@ -10,7 +9,7 @@ import FNote from "../../entities/fnote.js";
|
||||
import link from "../../services/link.js";
|
||||
import Button from "../react/Button.jsx";
|
||||
import Alert from "../react/Alert.jsx";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
export interface ResolveOptions {
|
||||
proceed: boolean;
|
||||
@@ -30,7 +29,7 @@ interface BrokenRelationData {
|
||||
source: string;
|
||||
}
|
||||
|
||||
function DeleteNotesDialogComponent() {
|
||||
export default function DeleteNotesDialog() {
|
||||
const [ opts, setOpts ] = useState<ShowDeleteNotesDialogOpts>({});
|
||||
const [ deleteAllClones, setDeleteAllClones ] = useState(false);
|
||||
const [ eraseNotes, setEraseNotes ] = useState(!!opts.forceDeleteAllClones);
|
||||
@@ -114,10 +113,10 @@ function DeletedNotes({ noteIdsToBeDeleted }: { noteIdsToBeDeleted: DeleteNotesP
|
||||
|
||||
if (noteIdsToBeDeleted.length) {
|
||||
return (
|
||||
<div className="delete-notes-list-wrapper">
|
||||
<div className="delete-notes-list-wrapper" style={{paddingTop: "16px"}}>
|
||||
<h4>{t("delete_notes.notes_to_be_deleted", { notesCount: noteIdsToBeDeleted.length })}</h4>
|
||||
|
||||
<ul className="delete-notes-list" style={{ maxHeight: "200px", overflow: "auto" }}>
|
||||
<ul className="delete-notes-list" style={{ maxHeight: "200px", overflow: "auto"}}>
|
||||
{noteLinks.map((link, index) => (
|
||||
<li key={index} dangerouslySetInnerHTML={{ __html: link }} />
|
||||
))}
|
||||
@@ -140,7 +139,7 @@ function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPrev
|
||||
const noteIds = brokenRelations
|
||||
.map(relation => relation.noteId)
|
||||
.filter(noteId => noteId) as string[];
|
||||
froca.getNotes(noteIds).then(async (notes) => {
|
||||
froca.getNotes(noteIds).then(async () => {
|
||||
const notesWithBrokenRelations: BrokenRelationData[] = [];
|
||||
for (const attr of brokenRelations) {
|
||||
notesWithBrokenRelations.push({
|
||||
@@ -171,11 +170,3 @@ function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPrev
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
export default class DeleteNotesDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <DeleteNotesDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,14 +4,13 @@ import tree from "../../services/tree";
|
||||
import Button from "../react/Button";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import "./export.css";
|
||||
import ws from "../../services/ws";
|
||||
import toastService, { ToastOptions } from "../../services/toast";
|
||||
import utils from "../../services/utils";
|
||||
import open from "../../services/open";
|
||||
import froca from "../../services/froca";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
interface ExportDialogProps {
|
||||
branchId?: string | null;
|
||||
@@ -19,7 +18,7 @@ interface ExportDialogProps {
|
||||
defaultType?: "subtree" | "single";
|
||||
}
|
||||
|
||||
function ExportDialogComponent() {
|
||||
export default function ExportDialog() {
|
||||
const [ opts, setOpts ] = useState<ExportDialogProps>();
|
||||
const [ exportType, setExportType ] = useState<string>(opts?.defaultType ?? "subtree");
|
||||
const [ subtreeFormat, setSubtreeFormat ] = useState("html");
|
||||
@@ -125,14 +124,6 @@ function ExportDialogComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default class ExportDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ExportDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function exportBranch(branchId: string, type: string, format: string, version: string) {
|
||||
const taskId = utils.randomString(10);
|
||||
const url = open.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${taskId}`);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import Modal from "../react/Modal.jsx";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { ComponentChildren } from "preact";
|
||||
@@ -6,9 +5,9 @@ import { CommandNames } from "../../components/app_context.js";
|
||||
import RawHtml from "../react/RawHtml.jsx";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import keyboard_actions from "../../services/keyboard_actions.js";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
function HelpDialogComponent() {
|
||||
export default function HelpDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
useTriliumEvent("showCheatsheet", () => setShown(true));
|
||||
|
||||
@@ -161,11 +160,3 @@ function Card({ title, children }: { title: string, children: ComponentChildren
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default class HelpDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <HelpDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,11 +7,10 @@ import FormFileUpload from "../react/FormFileUpload";
|
||||
import FormGroup, { FormMultiGroup } from "../react/FormGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import RawHtml from "../react/RawHtml";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import importService, { UploadFilesOptions } from "../../services/import";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function ImportDialogComponent() {
|
||||
export default function ImportDialog() {
|
||||
const [ parentNoteId, setParentNoteId ] = useState<string>();
|
||||
const [ noteTitle, setNoteTitle ] = useState<string>();
|
||||
const [ files, setFiles ] = useState<FileList | null>(null);
|
||||
@@ -89,14 +88,6 @@ function ImportDialogComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default class ImportDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ImportDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function boolToString(value: boolean) {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
@@ -4,15 +4,14 @@ import FormGroup from "../react/FormGroup";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Button from "../react/Button";
|
||||
import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete";
|
||||
import tree from "../../services/tree";
|
||||
import froca from "../../services/froca";
|
||||
import EditableTextTypeWidget from "../type_widgets/editable_text";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function IncludeNoteDialogComponent() {
|
||||
export default function IncludeNoteDialog() {
|
||||
const [textTypeWidget, setTextTypeWidget] = useState<EditableTextTypeWidget>();
|
||||
const [suggestion, setSuggestion] = useState<Suggestion | null>(null);
|
||||
const [boxSize, setBoxSize] = useState("medium");
|
||||
@@ -70,14 +69,6 @@ function IncludeNoteDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
export default class IncludeNoteDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <IncludeNoteDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function includeNote(notePath: string, textTypeWidget: EditableTextTypeWidget) {
|
||||
const noteId = tree.getNoteIdFromUrl(notePath);
|
||||
if (!noteId) {
|
||||
|
||||
@@ -3,11 +3,10 @@ import { t } from "../../services/i18n.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import Button from "../react/Button.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import { useState } from "preact/hooks";
|
||||
import useTriliumEvent from "../react/hooks.jsx";
|
||||
import { useTriliumEvent } from "../react/hooks.jsx";
|
||||
|
||||
function IncorrectCpuArchDialogComponent() {
|
||||
export default function IncorrectCpuArchDialogComponent() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const downloadButtonRef = useRef<HTMLButtonElement>(null);
|
||||
useTriliumEvent("showCpuArchWarning", () => setShown(true));
|
||||
@@ -44,11 +43,3 @@ function IncorrectCpuArchDialogComponent() {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class IncorrectCpuArchDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <IncorrectCpuArchDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { EventData } from "../../components/app_context";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import { useRef, useState } from "preact/hooks";
|
||||
import { RawHtmlBlock } from "../react/RawHtml";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function ShowInfoDialogComponent() {
|
||||
export default function InfoDialog() {
|
||||
const [ opts, setOpts ] = useState<EventData<"showInfoDialog">>();
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const okButtonRef = useRef<HTMLButtonElement>(null);
|
||||
@@ -37,11 +36,3 @@ function ShowInfoDialogComponent() {
|
||||
<RawHtmlBlock className="info-dialog-content" html={opts?.message ?? ""} />
|
||||
</Modal>);
|
||||
}
|
||||
|
||||
export default class InfoDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ShowInfoDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import Button from "../react/Button";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
@@ -8,14 +7,14 @@ import note_autocomplete, { Suggestion } from "../../services/note_autocomplete"
|
||||
import appContext from "../../components/app_context";
|
||||
import commandRegistry from "../../services/command_registry";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import shortcutService from "../../services/shortcuts";
|
||||
|
||||
const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120;
|
||||
|
||||
type Mode = "last-search" | "recent-notes" | "commands";
|
||||
|
||||
function JumpToNoteDialogComponent() {
|
||||
export default function JumpToNoteDialogComponent() {
|
||||
const [ mode, setMode ] = useState<Mode>();
|
||||
const [ lastOpenedTs, setLastOpenedTs ] = useState<number>(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -27,12 +26,12 @@ function JumpToNoteDialogComponent() {
|
||||
|
||||
async function openDialog(commandMode: boolean) {
|
||||
let newMode: Mode;
|
||||
let initialText: string = "";
|
||||
let initialText = "";
|
||||
|
||||
if (commandMode) {
|
||||
newMode = "commands";
|
||||
initialText = ">";
|
||||
} else if (Date.now() - lastOpenedTs <= KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000 && actualText) {
|
||||
} else if (Date.now() - lastOpenedTs <= KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000 && actualText.current) {
|
||||
// if you open the Jump To dialog soon after using it previously, it can often mean that you
|
||||
// actually want to search for the same thing (e.g., you opened the wrong note at first try)
|
||||
// so we'll keep the content.
|
||||
@@ -142,11 +141,3 @@ function JumpToNoteDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class JumpToNoteDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <JumpToNoteDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,18 +5,17 @@ import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import utils from "../../services/utils";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Button from "../react/Button";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
interface RenderMarkdownResponse {
|
||||
htmlContent: string;
|
||||
}
|
||||
|
||||
function MarkdownImportDialogComponent() {
|
||||
export default function MarkdownImportDialog() {
|
||||
const markdownImportTextArea = useRef<HTMLTextAreaElement>(null);
|
||||
let [ text, setText ] = useState("");
|
||||
let [ shown, setShown ] = useState(false);
|
||||
const [ text, setText ] = useState("");
|
||||
const [ shown, setShown ] = useState(false);
|
||||
|
||||
const triggerImport = useCallback(() => {
|
||||
if (appContext.tabManager.getActiveContextNoteType() !== "text") {
|
||||
@@ -64,14 +63,6 @@ function MarkdownImportDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
export default class MarkdownImportDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <MarkdownImportDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function convertMarkdownToHtml(markdownContent: string) {
|
||||
const { htmlContent } = await server.post<RenderMarkdownResponse>("other/render-markdown", { markdownContent });
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import NoteList from "../react/NoteList";
|
||||
@@ -11,9 +10,9 @@ import tree from "../../services/tree";
|
||||
import froca from "../../services/froca";
|
||||
import branches from "../../services/branches";
|
||||
import toast from "../../services/toast";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function MoveToDialogComponent() {
|
||||
export default function MoveToDialog() {
|
||||
const [ movedBranchIds, setMovedBranchIds ] = useState<string[]>();
|
||||
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
|
||||
const [ shown, setShown ] = useState(false);
|
||||
@@ -67,14 +66,6 @@ function MoveToDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
export default class MoveToDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <MoveToDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function moveNotesTo(movedBranchIds: string[] | undefined, parentBranchId: string) {
|
||||
if (movedBranchIds) {
|
||||
await branches.moveToParentNote(movedBranchIds, parentBranchId);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
@@ -10,7 +9,7 @@ import { MenuCommandItem, MenuItem } from "../../menus/context_menu";
|
||||
import { TreeCommandNames } from "../../menus/tree_context_menu";
|
||||
import { Suggestion } from "../../services/note_autocomplete";
|
||||
import Badge from "../react/Badge";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
export interface ChooseNoteTypeResponse {
|
||||
success: boolean;
|
||||
@@ -26,7 +25,7 @@ const SEPARATOR_TITLE_REPLACEMENTS = [
|
||||
t("note_type_chooser.templates")
|
||||
];
|
||||
|
||||
function NoteTypeChooserDialogComponent() {
|
||||
export default function NoteTypeChooserDialogComponent() {
|
||||
const [ callback, setCallback ] = useState<ChooseNoteTypeCallback>();
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const [ parentNote, setParentNote ] = useState<Suggestion | null>();
|
||||
@@ -37,25 +36,23 @@ function NoteTypeChooserDialogComponent() {
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
if (!noteTypes.length) {
|
||||
useEffect(() => {
|
||||
note_types.getNoteTypeItems().then(noteTypes => {
|
||||
let index = -1;
|
||||
useEffect(() => {
|
||||
note_types.getNoteTypeItems().then(noteTypes => {
|
||||
let index = -1;
|
||||
|
||||
setNoteTypes((noteTypes ?? []).map((item, _index) => {
|
||||
if (item.title === "----") {
|
||||
index++;
|
||||
return {
|
||||
title: SEPARATOR_TITLE_REPLACEMENTS[index],
|
||||
enabled: false
|
||||
}
|
||||
setNoteTypes((noteTypes ?? []).map((item) => {
|
||||
if (item.title === "----") {
|
||||
index++;
|
||||
return {
|
||||
title: SEPARATOR_TITLE_REPLACEMENTS[index],
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}));
|
||||
});
|
||||
return item;
|
||||
}));
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
function onNoteTypeSelected(value: string) {
|
||||
const [ noteType, templateNoteId ] = value.split(",");
|
||||
@@ -120,11 +117,3 @@ function NoteTypeChooserDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class NoteTypeChooserDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <NoteTypeChooserDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import Modal from "../react/Modal";
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import appContext from "../../components/app_context";
|
||||
import { useState } from "preact/hooks";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function PasswordNotSetDialogComponent() {
|
||||
export default function PasswordNotSetDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
useTriliumEvent("showPasswordNotSet", () => setShown(true));
|
||||
|
||||
@@ -27,10 +26,3 @@ function PasswordNotSetDialogComponent() {
|
||||
);
|
||||
}
|
||||
|
||||
export default class PasswordNotSetDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <PasswordNotSetDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ import { useRef, useState } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import { Modal as BootstrapModal } from "bootstrap";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
// JQuery here is maintained for compatibility with existing code.
|
||||
interface ShownCallbackData {
|
||||
@@ -28,7 +26,7 @@ export interface PromptDialogOptions {
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
function PromptDialogComponent() {
|
||||
export default function PromptDialog() {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const labelRef = useRef<HTMLLabelElement>(null);
|
||||
@@ -84,11 +82,3 @@ function PromptDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class PromptDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <PromptDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import protected_session from "../../services/protected_session";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function ProtectedSessionPasswordDialogComponent() {
|
||||
export default function ProtectedSessionPasswordDialog() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const [ password, setPassword ] = useState("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -38,11 +37,3 @@ function ProtectedSessionPasswordDialogComponent() {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class ProtectedSessionPasswordDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <ProtectedSessionPasswordDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import hoisted_note from "../../services/hoisted_note";
|
||||
import type { RecentChangeRow } from "@triliumnext/commons";
|
||||
import froca from "../../services/froca";
|
||||
@@ -14,39 +13,32 @@ import { formatDateTime } from "../../utils/formatters";
|
||||
import link from "../../services/link";
|
||||
import RawHtml from "../react/RawHtml";
|
||||
import ws from "../../services/ws";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function RecentChangesDialogComponent() {
|
||||
export default function RecentChangesDialog() {
|
||||
const [ ancestorNoteId, setAncestorNoteId ] = useState<string>();
|
||||
const [ groupedByDate, setGroupedByDate ] = useState<Map<String, RecentChangeRow[]>>();
|
||||
const [ needsRefresh, setNeedsRefresh ] = useState(false);
|
||||
const [ groupedByDate, setGroupedByDate ] = useState<Map<string, RecentChangeRow[]>>();
|
||||
const [ refreshCounter, setRefreshCounter ] = useState(0);
|
||||
const [ shown, setShown ] = useState(false);
|
||||
|
||||
useTriliumEvent("showRecentChanges", ({ ancestorNoteId }) => {
|
||||
setNeedsRefresh(true);
|
||||
useTriliumEvent("showRecentChanges", ({ ancestorNoteId }) => {
|
||||
setAncestorNoteId(ancestorNoteId ?? hoisted_note.getHoistedNoteId());
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
if (!groupedByDate || needsRefresh) {
|
||||
useEffect(() => {
|
||||
if (needsRefresh) {
|
||||
setNeedsRefresh(false);
|
||||
}
|
||||
useEffect(() => {
|
||||
server.get<RecentChangeRow[]>(`recent-changes/${ancestorNoteId}`)
|
||||
.then(async (recentChanges) => {
|
||||
// preload all notes into cache
|
||||
await froca.getNotes(
|
||||
recentChanges.map((r) => r.noteId),
|
||||
true
|
||||
);
|
||||
|
||||
server.get<RecentChangeRow[]>(`recent-changes/${ancestorNoteId}`)
|
||||
.then(async (recentChanges) => {
|
||||
// preload all notes into cache
|
||||
await froca.getNotes(
|
||||
recentChanges.map((r) => r.noteId),
|
||||
true
|
||||
);
|
||||
|
||||
const groupedByDate = groupByDate(recentChanges);
|
||||
setGroupedByDate(groupedByDate);
|
||||
});
|
||||
})
|
||||
}
|
||||
const groupedByDate = groupByDate(recentChanges);
|
||||
setGroupedByDate(groupedByDate);
|
||||
});
|
||||
}, [ shown, refreshCounter ])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -61,7 +53,7 @@ function RecentChangesDialogComponent() {
|
||||
style={{ padding: "0 10px" }}
|
||||
onClick={() => {
|
||||
server.post("notes/erase-deleted-notes-now").then(() => {
|
||||
setNeedsRefresh(true);
|
||||
setRefreshCounter(refreshCounter + 1);
|
||||
toast.showMessage(t("recent_changes.deleted_notes_message"));
|
||||
});
|
||||
}}
|
||||
@@ -79,7 +71,7 @@ function RecentChangesDialogComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
function RecentChangesTimeline({ groupedByDate, setShown }: { groupedByDate: Map<String, RecentChangeRow[]>, setShown: Dispatch<StateUpdater<boolean>> }) {
|
||||
function RecentChangesTimeline({ groupedByDate, setShown }: { groupedByDate: Map<string, RecentChangeRow[]>, setShown: Dispatch<StateUpdater<boolean>> }) {
|
||||
return (
|
||||
<>
|
||||
{ Array.from(groupedByDate.entries()).map(([dateDay, dayChanges]) => {
|
||||
@@ -114,10 +106,6 @@ function RecentChangesTimeline({ groupedByDate, setShown }: { groupedByDate: Map
|
||||
}
|
||||
|
||||
function NoteLink({ notePath, title }: { notePath: string, title: string }) {
|
||||
if (!notePath || !title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [ noteLink, setNoteLink ] = useState<JQuery<HTMLElement> | null>(null);
|
||||
useEffect(() => {
|
||||
link.createLink(notePath, {
|
||||
@@ -156,25 +144,19 @@ function DeletedNoteLink({ change, setShown }: { change: RecentChangeRow, setSho
|
||||
);
|
||||
}
|
||||
|
||||
export default class RecentChangesDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <RecentChangesDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function groupByDate(rows: RecentChangeRow[]) {
|
||||
const groupedByDate = new Map<String, RecentChangeRow[]>();
|
||||
const groupedByDate = new Map<string, RecentChangeRow[]>();
|
||||
|
||||
for (const row of rows) {
|
||||
const dateDay = row.date.substr(0, 10);
|
||||
|
||||
if (!groupedByDate.has(dateDay)) {
|
||||
groupedByDate.set(dateDay, []);
|
||||
let dateDayArray = groupedByDate.get(dateDay);
|
||||
if (!dateDayArray) {
|
||||
dateDayArray = [];
|
||||
groupedByDate.set(dateDay, dateDayArray);
|
||||
}
|
||||
|
||||
groupedByDate.get(dateDay)!.push(row);
|
||||
dateDayArray.push(row);
|
||||
}
|
||||
|
||||
return groupedByDate;
|
||||
|
||||
@@ -8,7 +8,6 @@ import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import Button from "../react/Button";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import FormList, { FormListItem } from "../react/FormList";
|
||||
import utils from "../../services/utils";
|
||||
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
@@ -18,9 +17,9 @@ import type { CSSProperties } from "preact/compat";
|
||||
import open from "../../services/open";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import options from "../../services/options";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function RevisionsDialogComponent() {
|
||||
export default function RevisionsDialog() {
|
||||
const [ note, setNote ] = useState<FNote>();
|
||||
const [ revisions, setRevisions ] = useState<RevisionItem[]>();
|
||||
const [ currentRevision, setCurrentRevision ] = useState<RevisionItem>();
|
||||
@@ -72,6 +71,8 @@ function RevisionsDialogComponent() {
|
||||
onHidden={() => {
|
||||
setShown(false);
|
||||
setNote(undefined);
|
||||
setCurrentRevision(undefined);
|
||||
setRevisions(undefined);
|
||||
}}
|
||||
show={shown}
|
||||
>
|
||||
@@ -202,17 +203,9 @@ function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: Revisi
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
||||
switch (revisionItem.type) {
|
||||
case "text": {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (contentRef.current?.querySelector("span.math-tex")) {
|
||||
renderMathInElement(contentRef.current, { trust: true });
|
||||
}
|
||||
});
|
||||
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
||||
}
|
||||
case "text":
|
||||
return <RevisionContentText content={content} />
|
||||
case "code":
|
||||
return <pre style={CODE_STYLE}>{content}</pre>;
|
||||
case "image":
|
||||
@@ -264,6 +257,16 @@ function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: Revisi
|
||||
}
|
||||
}
|
||||
|
||||
function RevisionContentText({ content }: { content: string | Buffer<ArrayBufferLike> | undefined }) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (contentRef.current?.querySelector("span.math-tex")) {
|
||||
renderMathInElement(contentRef.current, { trust: true });
|
||||
}
|
||||
}, [content]);
|
||||
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
||||
}
|
||||
|
||||
function RevisionFooter({ note }: { note?: FNote }) {
|
||||
if (!note) {
|
||||
return <></>;
|
||||
@@ -291,14 +294,6 @@ function RevisionFooter({ note }: { note?: FNote }) {
|
||||
</>;
|
||||
}
|
||||
|
||||
export default class RevisionsDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <RevisionsDialogComponent />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getNote(noteId?: string | null) {
|
||||
if (noteId) {
|
||||
return await froca.getNote(noteId);
|
||||
|
||||
@@ -5,12 +5,11 @@ import FormCheckbox from "../react/FormCheckbox";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import server from "../../services/server";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function SortChildNotesDialogComponent() {
|
||||
export default function SortChildNotesDialog() {
|
||||
const [ parentNoteId, setParentNoteId ] = useState<string>();
|
||||
const [ sortBy, setSortBy ] = useState("title");
|
||||
const [ sortDirection, setSortDirection ] = useState("asc");
|
||||
@@ -89,11 +88,3 @@ function SortChildNotesDialogComponent() {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class SortChildNotesDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <SortChildNotesDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,13 +5,12 @@ import FormCheckbox from "../react/FormCheckbox";
|
||||
import FormFileUpload from "../react/FormFileUpload";
|
||||
import FormGroup from "../react/FormGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import options from "../../services/options";
|
||||
import importService from "../../services/import.js";
|
||||
import tree from "../../services/tree";
|
||||
import useTriliumEvent from "../react/hooks";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
function UploadAttachmentsDialogComponent() {
|
||||
export default function UploadAttachmentsDialog() {
|
||||
const [ parentNoteId, setParentNoteId ] = useState<string>();
|
||||
const [ files, setFiles ] = useState<FileList | null>(null);
|
||||
const [ shrinkImages, setShrinkImages ] = useState(options.is("compressImages"));
|
||||
@@ -24,12 +23,12 @@ function UploadAttachmentsDialogComponent() {
|
||||
setShown(true);
|
||||
});
|
||||
|
||||
if (parentNoteId) {
|
||||
useEffect(() => {
|
||||
tree.getNoteTitle(parentNoteId).then((noteTitle) =>
|
||||
setDescription(t("upload_attachments.files_will_be_uploaded", { noteTitle })));
|
||||
}, [parentNoteId]);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!parentNoteId) return;
|
||||
|
||||
tree.getNoteTitle(parentNoteId).then((noteTitle) =>
|
||||
setDescription(t("upload_attachments.files_will_be_uploaded", { noteTitle })));
|
||||
}, [parentNoteId]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -64,11 +63,3 @@ function UploadAttachmentsDialogComponent() {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class UploadAttachmentsDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <UploadAttachmentsDialogComponent />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import attributeService from "../services/attributes.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
|
||||
type Editability = "auto" | "readOnly" | "autoReadOnlyDisabled";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown editability-select-widget">
|
||||
<style>
|
||||
.editability-dropdown {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.editability-dropdown .dropdown-item {
|
||||
display: flex !importamt;
|
||||
}
|
||||
|
||||
.editability-dropdown .dropdown-item > div {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.editability-dropdown .description {
|
||||
font-size: small;
|
||||
color: var(--muted-text-color);
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm select-button dropdown-toggle editability-button">
|
||||
<span class="editability-active-desc">${t("editability_select.auto")}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="editability-dropdown dropdown-menu dropdown-menu-right tn-dropdown-list">
|
||||
<a class="dropdown-item" href="#" data-editability="auto">
|
||||
<span class="check">✓</span>
|
||||
<div>
|
||||
${t("editability_select.auto")}
|
||||
<div class="description">${t("editability_select.note_is_editable")}</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" data-editability="readOnly">
|
||||
<span class="check">✓</span>
|
||||
<div>
|
||||
${t("editability_select.read_only")}
|
||||
<div class="description">${t("editability_select.note_is_read_only")}</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" data-editability="autoReadOnlyDisabled">
|
||||
<span class="check">✓</span>
|
||||
<div>
|
||||
${t("editability_select.always_editable")}
|
||||
<div class="description">${t("editability_select.note_is_always_editable")}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class EditabilitySelectWidget extends NoteContextAwareWidget {
|
||||
|
||||
private dropdown!: Dropdown;
|
||||
private $editabilityActiveDesc!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]);
|
||||
|
||||
this.$editabilityActiveDesc = this.$widget.find(".editability-active-desc");
|
||||
|
||||
this.$widget.on("click", ".dropdown-item", async (e) => {
|
||||
this.dropdown.toggle();
|
||||
|
||||
const editability = $(e.target).closest("[data-editability]").attr("data-editability");
|
||||
|
||||
if (!this.note || !this.noteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const ownedAttr of this.note.getOwnedLabels()) {
|
||||
if (["readOnly", "autoReadOnlyDisabled"].includes(ownedAttr.name)) {
|
||||
await attributeService.removeAttributeById(this.noteId, ownedAttr.attributeId);
|
||||
}
|
||||
}
|
||||
|
||||
if (editability && editability !== "auto") {
|
||||
await attributeService.addLabel(this.noteId, editability);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
let editability: Editability = "auto";
|
||||
|
||||
if (this.note?.isLabelTruthy("readOnly")) {
|
||||
editability = "readOnly";
|
||||
} else if (this.note?.isLabelTruthy("autoReadOnlyDisabled")) {
|
||||
editability = "autoReadOnlyDisabled";
|
||||
}
|
||||
|
||||
const labels = {
|
||||
auto: t("editability_select.auto"),
|
||||
readOnly: t("editability_select.read_only"),
|
||||
autoReadOnlyDisabled: t("editability_select.always_editable")
|
||||
};
|
||||
|
||||
this.$widget.find(".dropdown-item").removeClass("selected");
|
||||
this.$widget.find(`.dropdown-item[data-editability='${editability}']`).addClass("selected");
|
||||
|
||||
this.$editabilityActiveDesc.text(labels[editability]);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.getAttributeRows().find((attr) => attr.noteId === this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ const TPL = /*html*/`
|
||||
|
||||
<div class="find-widget-spacer"></div>
|
||||
|
||||
<div class="find-widget-close-button"><button class="btn icon-action bx bx-x"></button></div>
|
||||
<div class="find-widget-close-button"><button class="icon-action bx bx-x"></button></div>
|
||||
</div>
|
||||
|
||||
<div class="replace-widget-box" style='display: none'>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="code-buttons-widget">
|
||||
<style>
|
||||
.code-buttons-widget {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button data-trigger-command="runActiveNote" class="execute-button floating-button btn" title="${t("code_buttons.execute_button_title")}">
|
||||
<span class="bx bx-play"></span>
|
||||
</button>
|
||||
|
||||
<button class="trilium-api-docs-button floating-button btn" title="${t("code_buttons.trilium_api_docs_button_title")}">
|
||||
<span class="bx bx-help-circle"></span>
|
||||
</button>
|
||||
|
||||
<button class="save-to-note-button floating-button btn" title="${t("code_buttons.save_to_note_button_title")}">
|
||||
<span class="bx bx-save"></span>
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
interface SaveSqlConsoleResponse {
|
||||
notePath: string;
|
||||
}
|
||||
|
||||
export default class CodeButtonsWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $openTriliumApiDocsButton!: JQuery<HTMLElement>;
|
||||
private $executeButton!: JQuery<HTMLElement>;
|
||||
private $saveToNoteButton!: JQuery<HTMLElement>;
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.note && (this.note.mime.startsWith("application/javascript") || this.note.mime === "text/x-sqlite;schema=trilium");
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$openTriliumApiDocsButton = this.$widget.find(".trilium-api-docs-button");
|
||||
this.$openTriliumApiDocsButton.on("click", () => {
|
||||
toastService.showMessage(t("code_buttons.opening_api_docs_message"));
|
||||
|
||||
if (this.note?.mime.endsWith("frontend")) {
|
||||
window.open("https://triliumnext.github.io/Notes/Script%20API/interfaces/Frontend_Script_API.Api.html", "_blank");
|
||||
} else {
|
||||
window.open("https://triliumnext.github.io/Notes/Script%20API/interfaces/Backend_Script_API.Api.html", "_blank");
|
||||
}
|
||||
});
|
||||
|
||||
this.$executeButton = this.$widget.find(".execute-button");
|
||||
this.$saveToNoteButton = this.$widget.find(".save-to-note-button");
|
||||
this.$saveToNoteButton.on("click", async () => {
|
||||
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: this.noteId });
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
|
||||
toastService.showMessage(t("code_buttons.sql_console_saved_message", { notePath: await treeService.getNotePathTitle(notePath) }));
|
||||
});
|
||||
|
||||
keyboardActionService.updateDisplayedShortcuts(this.$widget);
|
||||
|
||||
this.contentSized();
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.$executeButton.toggle(note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium");
|
||||
|
||||
this.$saveToNoteButton.toggle(note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely());
|
||||
|
||||
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith("application/javascript;env="));
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }: EventData<"noteTypeMimeChanged">) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user