mirror of
https://github.com/zadam/trilium.git
synced 2026-02-06 14:39:29 +01:00
Compare commits
192 Commits
week-note
...
renovate/d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5690409a72 | ||
|
|
f0b30c5e91 | ||
|
|
73e3196124 | ||
|
|
8a7bcc316e | ||
|
|
a2921cb982 | ||
|
|
29ce004974 | ||
|
|
026ba5ddce | ||
|
|
ab0585609a | ||
|
|
14a8bdb0c0 | ||
|
|
397d04dd88 | ||
|
|
fbb0bb7491 | ||
|
|
ee987dae99 | ||
|
|
720281a8db | ||
|
|
ff0c89e5a3 | ||
|
|
442937f540 | ||
|
|
dc01b787c1 | ||
|
|
c709b5d34c | ||
|
|
f8b386e42d | ||
|
|
a82f8ce3ad | ||
|
|
529d45b762 | ||
|
|
d31135bf21 | ||
|
|
75a77acefe | ||
|
|
199bfc8a37 | ||
|
|
e82ae762f0 | ||
|
|
f90cc9aff7 | ||
|
|
ec915177ad | ||
|
|
5adee3e217 | ||
|
|
278d82645e | ||
|
|
e66f13b471 | ||
|
|
0e2955b57e | ||
|
|
2a4d5ec1ec | ||
|
|
07ce63de69 | ||
|
|
b539862eef | ||
|
|
ac4be3f8a8 | ||
|
|
0dc2d07b58 | ||
|
|
b097f9dc21 | ||
|
|
8aa4a97480 | ||
|
|
3e54d0ceae | ||
|
|
e1cec3404a | ||
|
|
9a2b7fbda1 | ||
|
|
e0b4ebed93 | ||
|
|
e00e3999c5 | ||
|
|
2cbe96d815 | ||
|
|
ac3b289c9e | ||
|
|
62534e0e93 | ||
|
|
b802c3174c | ||
|
|
aee1a6e1f0 | ||
|
|
46f1cd38e0 | ||
|
|
7f891ef523 | ||
|
|
06af5e15cd | ||
|
|
af89a0a883 | ||
|
|
fa72eb2edb | ||
|
|
c1ea94423b | ||
|
|
1e70d066bd | ||
|
|
ab89f16e7c | ||
|
|
8c848a4cb5 | ||
|
|
e021a54d2d | ||
|
|
70523574b0 | ||
|
|
66e0f1ab19 | ||
|
|
671a05470e | ||
|
|
99eec0c41e | ||
|
|
48d06dcb06 | ||
|
|
e38df0c731 | ||
|
|
0f3f49915e | ||
|
|
416144265b | ||
|
|
2e7ced8e60 | ||
|
|
fe02871e91 | ||
|
|
bb05aeeaf7 | ||
|
|
846358ccb0 | ||
|
|
dabc779727 | ||
|
|
08fd2ec64b | ||
|
|
c42c06d048 | ||
|
|
e951d60800 | ||
|
|
0d8453f6a7 | ||
|
|
52b41b1bb0 | ||
|
|
c7265017b3 | ||
|
|
634e0b6d30 | ||
|
|
1c260f5890 | ||
|
|
177aedeaae | ||
|
|
3f6f3d2565 | ||
|
|
38489dbfeb | ||
|
|
ff2a3d6a28 | ||
|
|
2c74697fb1 | ||
|
|
110e9200b9 | ||
|
|
6e792f9735 | ||
|
|
51d8b13a81 | ||
|
|
a05691fd07 | ||
|
|
d25e7915d9 | ||
|
|
1a025dfef3 | ||
|
|
84cc4194aa | ||
|
|
331a56277c | ||
|
|
bc2915adb9 | ||
|
|
703fe9a71b | ||
|
|
6c50664046 | ||
|
|
673cbc97e1 | ||
|
|
3e83766099 | ||
|
|
b453589077 | ||
|
|
654fa18ab1 | ||
|
|
7b0d91534c | ||
|
|
8d4801bb6f | ||
|
|
9e36f4f625 | ||
|
|
d83a824812 | ||
|
|
ca128f2fa9 | ||
|
|
c02642d0f9 | ||
|
|
7340709111 | ||
|
|
56d0383372 | ||
|
|
49d33ea19a | ||
|
|
979fa0359a | ||
|
|
c7381d058a | ||
|
|
5db298f031 | ||
|
|
171d948a00 | ||
|
|
facd56cdf4 | ||
|
|
ba17ec4be4 | ||
|
|
6c163b5479 | ||
|
|
79649805b8 | ||
|
|
220ca8a570 | ||
|
|
348c00f86d | ||
|
|
12b641b522 | ||
|
|
6855bc1de6 | ||
|
|
a9c5b99ae8 | ||
|
|
76f36e2fd3 | ||
|
|
afe710321c | ||
|
|
0d444daaca | ||
|
|
c8a0c9fd23 | ||
|
|
79f07ae923 | ||
|
|
c77e7a568b | ||
|
|
ff9ec2057b | ||
|
|
6a313b99e4 | ||
|
|
8a92370042 | ||
|
|
411a59ec54 | ||
|
|
2d4022044d | ||
|
|
bbc5ebd76b | ||
|
|
e9c90fcde8 | ||
|
|
911f78867f | ||
|
|
5507cc5abc | ||
|
|
0e5aa401ef | ||
|
|
d48473ab87 | ||
|
|
734efaf40c | ||
|
|
f89718d88a | ||
|
|
aa4942a0da | ||
|
|
90bb162a88 | ||
|
|
0382a4b30e | ||
|
|
490d940cd1 | ||
|
|
b090eb9359 | ||
|
|
cb9e67ce84 | ||
|
|
c4d131dd23 | ||
|
|
2667f266bf | ||
|
|
8258936d6c | ||
|
|
e4e7449078 | ||
|
|
11b020e859 | ||
|
|
72d6b83ec5 | ||
|
|
fbe5152cb3 | ||
|
|
6bef01f755 | ||
|
|
f0d4b4a6d9 | ||
|
|
a54fe62643 | ||
|
|
eb4bbd49fb | ||
|
|
ab4f1bd4f4 | ||
|
|
f8b414c354 | ||
|
|
82a21624c3 | ||
|
|
1b212ac720 | ||
|
|
c36ce3ea14 | ||
|
|
841fab77a8 | ||
|
|
fd6f910824 | ||
|
|
ce9ca1917d | ||
|
|
c702fb273c | ||
|
|
4c72d8691b | ||
|
|
35ac5fc514 | ||
|
|
92991cc03c | ||
|
|
76492475e3 | ||
|
|
e2363d860c | ||
|
|
c06a90913a | ||
|
|
e88c0f7326 | ||
|
|
2a4280b5bf | ||
|
|
d3c733c57f | ||
|
|
f91add3cd4 | ||
|
|
90e3f7508a | ||
|
|
5e981da4df | ||
|
|
2ddd5d75fc | ||
|
|
88f509cbb6 | ||
|
|
ca161bc881 | ||
|
|
676ca44cdf | ||
|
|
5d0c91202f | ||
|
|
a166f049d5 | ||
|
|
0dc4692dfc | ||
|
|
996607d096 | ||
|
|
bd907ea008 | ||
|
|
b1573b1f3b | ||
|
|
246849ce94 | ||
|
|
2c2c68261a | ||
|
|
e38a0361bf | ||
|
|
cbf879bd32 | ||
|
|
808625e564 |
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -42,5 +42,8 @@
|
||||
},
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "*", "severity": "warn" }
|
||||
],
|
||||
"cSpell.words": [
|
||||
"Trilium"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.28.2",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.15.0",
|
||||
"@redocly/cli": "2.15.1",
|
||||
"archiver": "7.0.1",
|
||||
"fs-extra": "11.3.3",
|
||||
"react": "19.2.4",
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"debounce": "3.0.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
"globals": "17.2.0",
|
||||
"globals": "17.3.0",
|
||||
"i18next": "25.8.0",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "4.0.0",
|
||||
@@ -56,7 +56,7 @@
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.1",
|
||||
"mermaid": "11.12.2",
|
||||
"mind-elixir": "5.6.1",
|
||||
"mind-elixir": "5.7.1",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.28.3",
|
||||
@@ -78,7 +78,7 @@
|
||||
"@types/reveal.js": "5.2.2",
|
||||
"@types/tabulator-tables": "6.3.1",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"happy-dom": "20.4.0",
|
||||
"happy-dom": "20.5.0",
|
||||
"lightningcss": "1.31.1",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.2.0"
|
||||
|
||||
@@ -46,10 +46,6 @@ if (utils.isElectron()) {
|
||||
electronContextMenu.setupContextMenu();
|
||||
}
|
||||
|
||||
if (utils.isPWA()) {
|
||||
initPWATopbarColor();
|
||||
}
|
||||
|
||||
function initOnElectron() {
|
||||
const electron: typeof Electron = utils.dynamicRequire("electron");
|
||||
electron.ipcRenderer.on("globalShortcut", async (event, actionName) => appContext.triggerCommand(actionName));
|
||||
@@ -134,20 +130,3 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) {
|
||||
const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote;
|
||||
nativeTheme.themeSource = themeSource;
|
||||
}
|
||||
|
||||
function initPWATopbarColor() {
|
||||
const tracker = $("#background-color-tracker");
|
||||
|
||||
if (tracker.length) {
|
||||
const applyThemeColor = () => {
|
||||
let meta = $("meta[name='theme-color']");
|
||||
if (!meta.length) {
|
||||
meta = $(`<meta name="theme-color">`).appendTo($("head"));
|
||||
}
|
||||
meta.attr("content", tracker.css("color"));
|
||||
};
|
||||
|
||||
tracker.on("transitionend", applyThemeColor);
|
||||
applyThemeColor();
|
||||
}
|
||||
}
|
||||
|
||||
76
apps/client/src/layouts/mobile_layout.css
Normal file
76
apps/client/src/layouts/mobile_layout.css
Normal file
@@ -0,0 +1,76 @@
|
||||
#background-color-tracker {
|
||||
color: var(--main-background-color) !important;
|
||||
}
|
||||
|
||||
span.keyboard-shortcut,
|
||||
kbd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.25em;
|
||||
padding-inline-start: 0.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
.quick-search {
|
||||
margin: 0;
|
||||
}
|
||||
.quick-search .dropdown-menu {
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
/* #region Tree */
|
||||
.tree-wrapper {
|
||||
max-height: 100%;
|
||||
margin-top: 0px;
|
||||
overflow-y: auto;
|
||||
contain: content;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.fancytree-title {
|
||||
margin-inline-start: 0.6em !important;
|
||||
}
|
||||
|
||||
.fancytree-node {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-expander {
|
||||
width: 24px !important;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander {
|
||||
width: 24px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander:after {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 4px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tree-wrapper .collapse-tree-button,
|
||||
.tree-wrapper .scroll-to-active-note-button,
|
||||
.tree-wrapper .tree-settings-button {
|
||||
position: fixed;
|
||||
margin-inline-end: 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-wrapper .unhoist-button {
|
||||
font-size: 200%;
|
||||
}
|
||||
/* #endregion */
|
||||
@@ -1,128 +1,41 @@
|
||||
import "./mobile_layout.css";
|
||||
|
||||
import type AppContext from "../components/app_context.js";
|
||||
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
|
||||
import CloseZenModeButton from "../widgets/close_zen_button.js";
|
||||
import NoteList from "../widgets/collections/NoteList.jsx";
|
||||
import ContentHeader from "../widgets/containers/content_header.js";
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import RootContainer from "../widgets/containers/root_container.js";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
|
||||
import FloatingButtons from "../widgets/FloatingButtons.jsx";
|
||||
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
||||
import FindWidget from "../widgets/find.js";
|
||||
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
|
||||
import InlineTitle from "../widgets/layout/InlineTitle.jsx";
|
||||
import NoteBadges from "../widgets/layout/NoteBadges.jsx";
|
||||
import NoteTitleActions from "../widgets/layout/NoteTitleActions.jsx";
|
||||
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
||||
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
|
||||
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
|
||||
import NoteIconWidget from "../widgets/note_icon.jsx";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import { useNoteContext } from "../widgets/react/hooks.jsx";
|
||||
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
|
||||
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
|
||||
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
|
||||
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
|
||||
import SearchResult from "../widgets/search_result.jsx";
|
||||
import SharedInfoWidget from "../widgets/shared_info.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
|
||||
const MOBILE_CSS = `
|
||||
<style>
|
||||
span.keyboard-shortcut,
|
||||
kbd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.25em;
|
||||
padding-inline-start: 0.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
.quick-search {
|
||||
margin: 0;
|
||||
}
|
||||
.quick-search .dropdown-menu {
|
||||
max-width: 350px;
|
||||
}
|
||||
</style>`;
|
||||
|
||||
const FANCYTREE_CSS = `
|
||||
<style>
|
||||
.tree-wrapper {
|
||||
max-height: 100%;
|
||||
margin-top: 0px;
|
||||
overflow-y: auto;
|
||||
contain: content;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.fancytree-custom-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.fancytree-title {
|
||||
font-size: 1.5em;
|
||||
margin-inline-start: 0.6em !important;
|
||||
}
|
||||
|
||||
.fancytree-node {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.fancytree-node .fancytree-expander:before {
|
||||
font-size: 2em !important;
|
||||
}
|
||||
|
||||
span.fancytree-expander {
|
||||
width: 24px !important;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander {
|
||||
width: 24px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander:after {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 4px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tree-wrapper .collapse-tree-button,
|
||||
.tree-wrapper .scroll-to-active-note-button,
|
||||
.tree-wrapper .tree-settings-button {
|
||||
position: fixed;
|
||||
margin-inline-end: 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-wrapper .unhoist-button {
|
||||
font-size: 200%;
|
||||
}
|
||||
</style>`;
|
||||
|
||||
export default class MobileLayout {
|
||||
getRootWidget(appContext: typeof AppContext) {
|
||||
const rootContainer = new RootContainer(true)
|
||||
.setParent(appContext)
|
||||
.class("horizontal-layout")
|
||||
.cssBlock(MOBILE_CSS)
|
||||
.child(new FlexContainer("column").id("mobile-sidebar-container"))
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
@@ -136,7 +49,7 @@ export default class MobileLayout {
|
||||
.css("padding-inline-start", "0")
|
||||
.css("padding-inline-end", "0")
|
||||
.css("contain", "content")
|
||||
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
||||
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget()))
|
||||
)
|
||||
.child(
|
||||
new ScreenContainer("detail", "row")
|
||||
@@ -147,30 +60,28 @@ export default class MobileLayout {
|
||||
new NoteWrapperWidget()
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
.class("title-row note-split-title")
|
||||
.contentSized()
|
||||
.css("font-size", "larger")
|
||||
.css("align-items", "center")
|
||||
.child(<ToggleSidebarButton />)
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(<NoteBadges />)
|
||||
.child(<MobileDetailMenu />)
|
||||
)
|
||||
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
|
||||
.child(<PromotedAttributes />)
|
||||
.child(
|
||||
new ScrollingContainer()
|
||||
.filling()
|
||||
.contentSized()
|
||||
.child(new ContentHeader()
|
||||
.child(<ReadOnlyNoteInfoBar />)
|
||||
.child(<SharedInfoWidget />)
|
||||
)
|
||||
.child(<InlineTitle />)
|
||||
.child(<NoteTitleActions />)
|
||||
.child(<NoteDetail />)
|
||||
.child(<NoteList media="screen" />)
|
||||
.child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />)
|
||||
.child(<SearchResult />)
|
||||
.child(<FilePropertiesWrapper />)
|
||||
)
|
||||
.child(<MobileEditorToolbar />)
|
||||
.child(new FindWidget())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -248,7 +248,7 @@ class ContextMenu {
|
||||
if ("uiIcon" in item || "checked" in item) {
|
||||
const icon = (item.checked ? "bx bx-check" : item.uiIcon);
|
||||
if (icon) {
|
||||
$icon.addClass(icon);
|
||||
$icon.addClass([icon, "tn-icon"]);
|
||||
} else {
|
||||
$icon.append(" ");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import treeService from "../services/tree.js";
|
||||
import froca from "../services/froca.js";
|
||||
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import server from "../services/server.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type { ContextMenuCommandData,FilteredCommandNames } from "../components/app_context.js";
|
||||
import type { SelectMenuItemEventListener } from "../components/events.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import server from "../services/server.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import type { FilteredCommandNames, ContextMenuCommandData } from "../components/app_context.js";
|
||||
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
||||
|
||||
type LauncherCommandNames = FilteredCommandNames<ContextMenuCommandData>;
|
||||
|
||||
@@ -32,8 +32,8 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener<
|
||||
const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null;
|
||||
const parentNoteId = this.node.getParent().data.noteId;
|
||||
|
||||
const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers";
|
||||
const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers";
|
||||
const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers" || note?.noteId === "_lbMobileVisibleLaunchers";
|
||||
const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers" || note?.noteId === "_lbMobileAvailableLaunchers";
|
||||
const isVisibleItem = parentNoteId === "_lbVisibleLaunchers" || parentNoteId === "_lbMobileVisibleLaunchers";
|
||||
const isAvailableItem = parentNoteId === "_lbAvailableLaunchers" || parentNoteId === "_lbMobileAvailableLaunchers";
|
||||
const isItem = isVisibleItem || isAvailableItem;
|
||||
|
||||
9
apps/client/src/services/content_renderer.css
Normal file
9
apps/client/src/services/content_renderer.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.rendered-content.no-preview > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
font-size: 500%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./content_renderer.css";
|
||||
|
||||
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||
import WheelZoom from 'vanilla-js-wheel-zoom';
|
||||
|
||||
@@ -71,18 +73,9 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo
|
||||
|
||||
$renderedContent.append($("<div>").append("<div>This note is protected and to access it you need to enter password.</div>").append("<br/>").append($button));
|
||||
} else if (entity instanceof FNote) {
|
||||
$renderedContent
|
||||
.css("display", "flex")
|
||||
.css("flex-direction", "column");
|
||||
$renderedContent.addClass("no-preview");
|
||||
$renderedContent.append(
|
||||
$("<div>")
|
||||
.css("display", "flex")
|
||||
.css("justify-content", "space-around")
|
||||
.css("align-items", "center")
|
||||
.css("height", "100%")
|
||||
.css("font-size", "500%")
|
||||
.css("flex-grow", "1")
|
||||
.append($("<span>").addClass(entity.getIcon()))
|
||||
$("<div>").append($("<span>").addClass(entity.getIcon()))
|
||||
);
|
||||
|
||||
if (entity.type === "webView" && entity.hasLabel("webViewSrc")) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { t } from "./i18n";
|
||||
import options from "./options";
|
||||
import { isMobile } from "./utils";
|
||||
|
||||
export interface ExperimentalFeature {
|
||||
id: string;
|
||||
@@ -21,7 +22,7 @@ let enabledFeatures: Set<ExperimentalFeatureId> | null = null;
|
||||
|
||||
export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): boolean {
|
||||
if (featureId === "new-layout") {
|
||||
return options.is("newLayout");
|
||||
return (isMobile() || options.is("newLayout"));
|
||||
}
|
||||
|
||||
return getEnabledFeatures().has(featureId);
|
||||
@@ -29,7 +30,7 @@ export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId):
|
||||
|
||||
export function getEnabledExperimentalFeatureIds() {
|
||||
const values = [ ...getEnabledFeatures().values() ];
|
||||
if (options.is("newLayout")) {
|
||||
if (isMobile() || options.is("newLayout")) {
|
||||
values.push("new-layout");
|
||||
}
|
||||
return values;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
--bs-body-color: var(--main-text-color) !important;
|
||||
--bs-body-bg: var(--main-background-color) !important;
|
||||
--ck-mention-list-max-height: 500px;
|
||||
--tn-modal-max-height: 90vh;
|
||||
--tn-modal-max-height: 90svh;
|
||||
|
||||
--tree-item-light-theme-max-color-lightness: 50;
|
||||
--tree-item-dark-theme-min-color-lightness: 75;
|
||||
@@ -111,6 +111,7 @@ body.mobile #root-widget.virtual-keyboard-opened #mobile-bottom-bar {
|
||||
}
|
||||
|
||||
#mobile-bottom-bar {
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
padding-bottom: var(--mobile-bottom-offset);
|
||||
}
|
||||
|
||||
@@ -409,6 +410,7 @@ body.desktop .tabulator-popup-container,
|
||||
|
||||
.dropdown-menu.static {
|
||||
box-shadow: unset;
|
||||
backdrop-filter: unset !important;
|
||||
}
|
||||
|
||||
.dropend .dropdown-toggle::after {
|
||||
@@ -454,7 +456,7 @@ body.desktop .tabulator-popup-container,
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu:not(#context-menu-container) .dropdown-item,
|
||||
.dropdown-menu:not(#context-menu-container) .dropdown-item,
|
||||
body.desktop .dropdown-menu .dropdown-toggle,
|
||||
body #context-menu-container .dropdown-item > span,
|
||||
body.mobile .dropdown .dropdown-submenu > span {
|
||||
@@ -462,6 +464,15 @@ body.mobile .dropdown .dropdown-submenu > span {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
body.mobile .dropdown .dropdown-submenu {
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item span.keyboard-shortcut,
|
||||
.dropdown-item *:not(.keyboard-shortcut) > kbd {
|
||||
flex-grow: 1;
|
||||
@@ -1530,7 +1541,8 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
|
||||
@media (max-width: 991px) {
|
||||
body.mobile #launcher-pane .dropdown.global-menu > .dropdown-menu.show,
|
||||
body.mobile #launcher-container .dropdown > .dropdown-menu.show {
|
||||
body.mobile #launcher-container .dropdown > .dropdown-menu.show,
|
||||
body.mobile .dropdown-menu.mobile-bottom-menu.show {
|
||||
--dropdown-bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size));
|
||||
position: fixed !important;
|
||||
bottom: var(--dropdown-bottom) !important;
|
||||
@@ -1542,6 +1554,16 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
max-height: calc(var(--tn-modal-max-height) - var(--dropdown-bottom));
|
||||
}
|
||||
|
||||
body.mobile #launcher-container .dropdown > .dropdown-menu.show {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-menu.mobile-bottom-menu.show {
|
||||
--dropdown-bottom: 0px;
|
||||
padding-bottom: calc(max(var(--menu-padding-size), env(safe-area-inset-bottom))) !important;
|
||||
}
|
||||
|
||||
#mobile-sidebar-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -1667,47 +1689,15 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
background: var(--main-background-color);
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
margin: var(--bs-modal-margin);
|
||||
max-width: 80%;
|
||||
}
|
||||
body.mobile {
|
||||
.modal-dialog {
|
||||
margin: var(--bs-modal-margin);
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
body.mobile.force-fixed-tree #mobile-sidebar-wrapper {
|
||||
padding-top: 0;
|
||||
position: static;
|
||||
height: 40vh;
|
||||
width: 100vw;
|
||||
transform: none !important;
|
||||
background-color: var(--left-pane-background-color) !important;
|
||||
border-bottom: 0.5px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #mobile-sidebar-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #mobile-sidebar-wrapper .quick-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree .component > button.bx-sidebar {
|
||||
visibility: hidden;
|
||||
padding: 0;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #mobile-rest-container {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #detail-container {
|
||||
flex-grow: 1;
|
||||
.modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2623,14 +2613,14 @@ iframe.print-iframe {
|
||||
}
|
||||
}
|
||||
|
||||
#root-widget.virtual-keyboard-opened .note-split:not(:focus-within) {
|
||||
#root-widget.virtual-keyboard-opened .note-split:not(.active) {
|
||||
max-height: 80px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop .title-row {
|
||||
.title-row {
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
align-items: center;
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
--left-pane-collapsed-border-color: #0009;
|
||||
--left-pane-background-color: #1f1f1f;
|
||||
--left-pane-text-color: #aaaaaa;
|
||||
--left-pane-icon-color: #c5c5c5;
|
||||
--left-pane-item-hover-background: #ffffff0d;
|
||||
--left-pane-item-selected-background: #ffffff25;
|
||||
--left-pane-item-selected-color: #dfdfdf;
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
--left-pane-collapsed-border-color: #0000000d;
|
||||
--left-pane-background-color: #f2f2f2;
|
||||
--left-pane-text-color: #383838;
|
||||
--left-pane-icon-color: currentColor;
|
||||
--left-pane-item-hover-background: rgba(0, 0, 0, 0.032);
|
||||
--left-pane-item-selected-background: white;
|
||||
--left-pane-item-selected-color: black;
|
||||
|
||||
@@ -47,9 +47,14 @@
|
||||
}
|
||||
|
||||
/* The toolbar show / hide button for the current text block */
|
||||
.ck.ck-block-toolbar-button {
|
||||
:root .ck.ck-block-toolbar-button {
|
||||
--ck-color-block-toolbar-button: var(--muted-text-color);
|
||||
--ck-color-button-on-background: transparent;
|
||||
--ck-color-button-on-color: currentColor;
|
||||
--ck-color-button-on-color: var(--ck-editor-toolbar-button-on-color);
|
||||
translate: -40% 0;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
z-index: 1600;
|
||||
}
|
||||
|
||||
:root .ck.ck-toolbar .ck-button:not(.ck-disabled):active,
|
||||
@@ -517,6 +522,10 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck
|
||||
* EDITOR'S CONTENT
|
||||
*/
|
||||
|
||||
.note-detail-editable-text-editor > .ck-placeholder {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
/*
|
||||
* Code Blocks
|
||||
*/
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* SEARCH PAGE
|
||||
*/
|
||||
|
||||
/* Button bar */
|
||||
.search-definition-widget .search-setting-table tbody:last-child div {
|
||||
.search-definition-widget .search-setting-table .search-actions-container {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -143,7 +143,7 @@
|
||||
/*
|
||||
* OPTIONS PAGES
|
||||
*/
|
||||
|
||||
|
||||
:root {
|
||||
--options-card-min-width: 500px;
|
||||
--options-card-max-width: 900px;
|
||||
@@ -156,6 +156,10 @@
|
||||
--preferred-max-content-width: var(--options-card-max-width);
|
||||
}
|
||||
|
||||
.note-split.options .collection-properties {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Create a gap at the top of the option pages */
|
||||
.note-detail-content-widget-content.options>*:first-child {
|
||||
margin-top: var(--options-first-item-top-margin, 1em);
|
||||
@@ -331,4 +335,4 @@ nav.options-section-tabs + .options-section {
|
||||
|
||||
.etapi-options-section div {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,18 +739,12 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
|
||||
transform: translateX(-25%);
|
||||
}
|
||||
|
||||
body.mobile .fancytree-expander::before,
|
||||
body.mobile .fancytree-title,
|
||||
body.mobile .fancytree-node > span {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
body.mobile #mobile-sidebar-container {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
body.mobile:not(.force-fixed-tree) #mobile-sidebar-wrapper {
|
||||
body.mobile #mobile-sidebar-wrapper {
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
border-inline-end: 1px solid var(--subtle-border-color);
|
||||
@@ -769,9 +763,9 @@ body.mobile .fancytree-node > span {
|
||||
|
||||
#left-pane .fancytree-custom-icon {
|
||||
margin-top: 0; /* Use this to align the icon with the tree view item's caption */
|
||||
color: var(--custom-color, var(--left-pane-icon-color));
|
||||
}
|
||||
|
||||
|
||||
#left-pane span.fancytree-active .fancytree-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
@@ -1272,7 +1266,7 @@ body.layout-horizontal #rest-pane > .classic-toolbar-widget {
|
||||
#center-pane .note-split {
|
||||
padding-top: 2px;
|
||||
background-color: var(--note-split-background-color, var(--main-background-color));
|
||||
transition: border-color 250ms ease-in;
|
||||
transition: border-color 150ms ease-out;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
@@ -1322,7 +1316,7 @@ body.mobile .note-title {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
body.desktop .title-row {
|
||||
/* Aligns the "Create new split" button with the note menu button (the three dots button) */
|
||||
padding-inline-end: 3px;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"widget-render-error": {
|
||||
"title": "فشل عرض عنصر واجهة مستخدم React مخصص"
|
||||
},
|
||||
"widget-missing-parent": "لا تحتوي الأداة المخصصة على خاصية إلزامية '{{property}}'.\n\nإذا كان من المفترض تشغيل هذا البرنامج النصي بدون عنصر واجهة مستخدم، فاستخدم '#run=frontendStartup' بدلاً من ذلك."
|
||||
"widget-missing-parent": "لا تحتوي الأداة المخصصة على خاصية إلزامية '{{property}}'.\n\nإذا كان من المفترض تشغيل هذا البرنامج النصي بدون عنصر واجهة مستخدم، فاستخدم '#run=frontendStartup' بدلاً من ذلك.",
|
||||
"open-script-note": "فتح ملاحظة برمجية",
|
||||
"scripting-error": "خطأ في النص البرمجي المخصص: {{title}}"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "أضافة رابط",
|
||||
@@ -37,7 +39,9 @@
|
||||
"search_note": "البحث عن الملاحظة بالاسم",
|
||||
"link_title": "عنوان الرابط",
|
||||
"button_add_link": "اضافة رابط",
|
||||
"help_on_links": "مساعدة حول الارتباطات التشعبية"
|
||||
"help_on_links": "مساعدة حول الارتباطات التشعبية",
|
||||
"link_title_mirrors": "عنوان الرابط يعكس العنوان الحالي للملاحظة",
|
||||
"link_title_arbitrary": "يمكن تغيير عنوان الرابط حسب الرغبة"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix": "تعديل بادئة الفرع",
|
||||
|
||||
@@ -1782,8 +1782,8 @@
|
||||
"desktop-application": "桌面应用程序",
|
||||
"native-title-bar": "原生标题栏",
|
||||
"native-title-bar-description": "对于 Windows 和 macOS,关闭原生标题栏可使应用程序看起来更紧凑。在 Linux 上,保留原生标题栏可以更好地与系统集成。",
|
||||
"background-effects": "启用背景效果(仅适用于 Windows 11)",
|
||||
"background-effects-description": "Mica 效果为应用窗口添加模糊且时尚的背景,营造出深度感和现代外观。「原生标题栏」必須被禁用。",
|
||||
"background-effects": "启用背景效果",
|
||||
"background-effects-description": "为应用窗口添加模糊且时尚的背景,营造出深度感和现代外观。「原生标题栏」必須被禁用。",
|
||||
"restart-app-button": "重启应用程序以查看更改",
|
||||
"zoom-factor": "缩放系数"
|
||||
},
|
||||
@@ -1802,7 +1802,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "创建一个新的子笔记并将其添加到地图中",
|
||||
"create-child-note-instruction": "单击地图以在该位置创建新笔记,或按 Escape 以取消。",
|
||||
"unable-to-load-map": "无法加载地图。"
|
||||
"unable-to-load-map": "无法加载地图。",
|
||||
"create-child-note-text": "添加标记"
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "打开位置",
|
||||
@@ -2117,7 +2118,7 @@
|
||||
},
|
||||
"call_to_action": {
|
||||
"background_effects_title": "背景效果现已推出稳定版本",
|
||||
"background_effects_message": "在 Windows 装置上,背景效果现在已完全稳定。背景效果通过模糊背后的背景,为使用者界面增添一抹色彩。此技术也用于其他应用程序,例如 Windows 资源管理器。",
|
||||
"background_effects_message": "在 Windows 和 macOS 设备上,背景效果现在已稳定。背景效果通过模糊背后的背景,为使用者界面增添一抹色彩。",
|
||||
"background_effects_button": "启用背景效果",
|
||||
"next_theme_title": "试用新 Trilium 主题",
|
||||
"next_theme_message": "当前使用旧版主题,要试用新主题吗?",
|
||||
@@ -2253,5 +2254,12 @@
|
||||
"pages_alt": "第{{pageNumber}}页",
|
||||
"pages_loading": "加载中...",
|
||||
"layers_other": "{{count}} 层"
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "在 {{platform}} 上可用"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_other": "{{count}} 选项卡",
|
||||
"more_options": "更多选项"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1771,7 +1771,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Neue Unternotiz anlegen und zur Karte hinzufügen",
|
||||
"create-child-note-instruction": "Auf die Karte klicken, um eine neue Notiz an der Stelle zu erstellen oder Escape drücken um abzubrechen.",
|
||||
"unable-to-load-map": "Karte konnte nicht geladen werden."
|
||||
"unable-to-load-map": "Karte konnte nicht geladen werden.",
|
||||
"create-child-note-text": "Marker hinzufügen"
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Ort öffnen",
|
||||
@@ -2270,5 +2271,10 @@
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "Verfügbar auf {{platform}}"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_one": "{{count}} Tab",
|
||||
"title_other": "{{count}} Tabs",
|
||||
"more_options": "Weitere Optionen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +662,8 @@
|
||||
"show-cheatsheet": "Show Cheatsheet",
|
||||
"toggle-zen-mode": "Zen Mode",
|
||||
"new-version-available": "New Update Available",
|
||||
"download-update": "Get Version {{latestVersion}}"
|
||||
"download-update": "Get Version {{latestVersion}}",
|
||||
"search_notes": "Search notes"
|
||||
},
|
||||
"zen_mode": {
|
||||
"button_exit": "Exit Zen Mode"
|
||||
@@ -745,7 +746,7 @@
|
||||
"button_title": "Export diagram as SVG"
|
||||
},
|
||||
"relation_map_buttons": {
|
||||
"create_child_note_title": "Create new child note and add it into this relation map",
|
||||
"create_child_note_title": "Create child note and add it to map",
|
||||
"reset_pan_zoom_title": "Reset pan & zoom to initial coordinates and magnification",
|
||||
"zoom_in_title": "Zoom In",
|
||||
"zoom_out_title": "Zoom Out"
|
||||
@@ -760,7 +761,9 @@
|
||||
"delete_this_note": "Delete this note",
|
||||
"note_revisions": "Note revisions",
|
||||
"error_cannot_get_branch_id": "Cannot get branchId for notePath '{{notePath}}'",
|
||||
"error_unrecognized_command": "Unrecognized command {{command}}"
|
||||
"error_unrecognized_command": "Unrecognized command {{command}}",
|
||||
"backlinks": "Backlinks",
|
||||
"content_language_switcher": "Content language: {{language}}"
|
||||
},
|
||||
"note_icon": {
|
||||
"change_note_icon": "Change note icon",
|
||||
@@ -905,6 +908,7 @@
|
||||
"debug": "debug",
|
||||
"debug_description": "Debug will print extra debugging information into the console to aid in debugging complex queries",
|
||||
"action": "action",
|
||||
"option": "option",
|
||||
"search_button": "Search",
|
||||
"search_execute": "Search & Execute actions",
|
||||
"save_to_note": "Save to note",
|
||||
@@ -2276,5 +2280,8 @@
|
||||
"title_one": "{{count}} tab",
|
||||
"title_other": "{{count}} tabs",
|
||||
"more_options": "More options"
|
||||
},
|
||||
"bookmark_buttons": {
|
||||
"bookmarks": "Bookmarks"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2285,5 +2285,11 @@
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "Disponible en {{platform}}"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_one": "{{count}} pestaña",
|
||||
"title_many": "{{count}} pestañas",
|
||||
"title_other": "{{count}} pestañas",
|
||||
"more_options": "Más opciones"
|
||||
}
|
||||
}
|
||||
|
||||
5
apps/client/src/translations/ga/translation.json
Normal file
5
apps/client/src/translations/ga/translation.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"global_menu": {
|
||||
"about": "Maidir le Trilium Notes"
|
||||
}
|
||||
}
|
||||
@@ -2008,7 +2008,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "新しい子ノートを作成し、マップに追加する",
|
||||
"create-child-note-instruction": "地図をクリックしてその場所に新しいノートを作成するか、Esc キーを押して閉じます。",
|
||||
"unable-to-load-map": "マップを読み込めません。"
|
||||
"unable-to-load-map": "マップを読み込めません。",
|
||||
"create-child-note-text": "マーカーを追加"
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "現在位置を表示",
|
||||
@@ -2256,5 +2257,9 @@
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "{{platform}} で利用可能"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_other": "{{count}} タブ",
|
||||
"more_options": "その他のオプション"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1761,8 +1761,8 @@
|
||||
"show-recent-notes": "Afișează notițele recente"
|
||||
},
|
||||
"electron_integration": {
|
||||
"background-effects": "Activează efectele de fundal (doar pentru Windows 11)",
|
||||
"background-effects-description": "Efectul Mica adaugă un fundal estompat și elegant ferestrelor aplicațiilor, creând profunzime și un aspect modern. Opțiunea „Bară de titlu nativă” trebuie să fie dezactivată.",
|
||||
"background-effects": "Activează efectele de fundal",
|
||||
"background-effects-description": "Adaugă un fundal estompat și elegant ferestrelor aplicațiilor, creând profunzime și un aspect modern. Opțiunea „Bară de titlu nativă” trebuie să fie dezactivată.",
|
||||
"desktop-application": "Aplicația desktop",
|
||||
"native-title-bar": "Bară de titlu nativă",
|
||||
"native-title-bar-description": "Pentru Windows și macOS, dezactivarea bării de titlu native face aplicația să pară mai compactă. Pe Linux, păstrarea bării integrează mai bine aplicația cu restul sistemului de operare.",
|
||||
@@ -1781,7 +1781,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Crează o notiță nouă și adaug-o pe hartă",
|
||||
"unable-to-load-map": "Nu s-a putut încărca harta.",
|
||||
"create-child-note-instruction": "Click pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a anula."
|
||||
"create-child-note-instruction": "Click pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a anula.",
|
||||
"create-child-note-text": "Adaugă marcaj"
|
||||
},
|
||||
"duration": {
|
||||
"days": "zile",
|
||||
@@ -2127,7 +2128,7 @@
|
||||
},
|
||||
"call_to_action": {
|
||||
"background_effects_title": "Efectele de fundal sunt acum stabile",
|
||||
"background_effects_message": "Pe dispozitive cu Windows, efectele de fundal sunt complet stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei. Această tehnică este folosită și în alte aplicații precum Windows Explorer.",
|
||||
"background_effects_message": "Pe dispozitive cu Windows și macOS, efectele de fundal sunt stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei.",
|
||||
"background_effects_button": "Activează efectele de fundal",
|
||||
"next_theme_title": "Încercați noua temă Trilium",
|
||||
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
|
||||
@@ -2281,5 +2282,14 @@
|
||||
"pages_other": "{{count}} de pagini",
|
||||
"pages_alt": "Pagina {{pageNumber}}",
|
||||
"pages_loading": "Încărcare..."
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "Disponibil pe {{platform}}"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_one": "{{count}} tab",
|
||||
"title_few": "{{count}} taburi",
|
||||
"title_other": "{{count}} de taburi",
|
||||
"more_options": "Mai multe opțiuni"
|
||||
}
|
||||
}
|
||||
|
||||
83
apps/client/src/widgets/Backlinks.css
Normal file
83
apps/client/src/widgets/Backlinks.css
Normal file
@@ -0,0 +1,83 @@
|
||||
.tn-backlinks-widget .backlinks-items {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: static;
|
||||
width: unset;
|
||||
|
||||
> li {
|
||||
--border-radius: 8px;
|
||||
|
||||
max-width: 600px;
|
||||
padding: 10px 20px;
|
||||
background: var(--card-background-color);
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
/* Card header */
|
||||
& > span:first-child {
|
||||
display: block;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
/* Note path */
|
||||
> small {
|
||||
flex: 100%;
|
||||
order: -1;
|
||||
font-size: .65rem;
|
||||
|
||||
.note-path {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note icon */
|
||||
> .tn-icon {
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
|
||||
/* Note title */
|
||||
> a {
|
||||
margin-inline-start: 4px;
|
||||
color: currentColor;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Card content - excerpt */
|
||||
.backlink-excerpt {
|
||||
all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */
|
||||
display: block;
|
||||
|
||||
margin: 8px 0;
|
||||
border-radius: 4px;
|
||||
background: var(--quick-search-result-content-background);
|
||||
padding: 8px;
|
||||
font-size: .75rem;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
color: var(--quick-search-result-highlight-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./Backlinks.css";
|
||||
|
||||
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
|
||||
import { VNode } from "preact";
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
@@ -60,14 +62,6 @@ export const DESKTOP_FLOATING_BUTTONS: FloatingButtonsList = [
|
||||
Backlinks
|
||||
];
|
||||
|
||||
export const MOBILE_FLOATING_BUTTONS: FloatingButtonsList = [
|
||||
RefreshBackendLogButton,
|
||||
EditButton,
|
||||
RelationMapButtons,
|
||||
ExportImageButtons,
|
||||
Backlinks
|
||||
];
|
||||
|
||||
/**
|
||||
* Floating buttons that should be hidden in popup editor (Quick edit).
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
font-family: var(--detail-font-family);
|
||||
font-size: var(--detail-font-size);
|
||||
contain: none;
|
||||
|
||||
&.fixed-tree {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.fixed-note-tree-container {
|
||||
height: 60%;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
overflow: auto;
|
||||
|
||||
.tree-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tree {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.prefers-centered-content .note-detail {
|
||||
@@ -12,4 +36,4 @@ body.prefers-centered-content .note-detail {
|
||||
|
||||
.note-detail > * {
|
||||
contain: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "./NoteDetail.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { isValidElement, VNode } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -12,8 +13,9 @@ import { t } from "../services/i18n";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
import toast from "../services/toast.js";
|
||||
import { dynamicRequire, isElectron, isMobile } from "../services/utils";
|
||||
import NoteTreeWidget from "./note_tree";
|
||||
import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types";
|
||||
import { useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||
import { useLegacyWidget, useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||
import { NoteListWithLinks } from "./react/NoteList";
|
||||
import { TypeWidgetProps } from "./type_widgets/type_widget";
|
||||
|
||||
@@ -36,6 +38,7 @@ export default function NoteDetail() {
|
||||
const [ noteTypesToRender, setNoteTypesToRender ] = useState<{ [ key in ExtendedNoteType ]?: (props: TypeWidgetProps) => VNode }>({});
|
||||
const [ activeNoteType, setActiveNoteType ] = useState<ExtendedNoteType>();
|
||||
const widgetRequestId = useRef(0);
|
||||
const hasFixedTree = note && noteContext?.hoistedNoteId === "_lbMobileRoot" && isMobile() && note.noteId.startsWith("_lbMobile");
|
||||
|
||||
const props: TypeWidgetProps = {
|
||||
note: note!,
|
||||
@@ -119,13 +122,6 @@ export default function NoteDetail() {
|
||||
}
|
||||
});
|
||||
|
||||
// Fixed tree for launch bar config on mobile.
|
||||
useEffect(() => {
|
||||
if (!isMobile) return;
|
||||
const hasFixedTree = noteContext?.hoistedNoteId === "_lbMobileRoot";
|
||||
document.body.classList.toggle("force-fixed-tree", hasFixedTree);
|
||||
}, [ note ]);
|
||||
|
||||
// Handle toast notifications.
|
||||
useEffect(() => {
|
||||
if (!isElectron()) return;
|
||||
@@ -215,8 +211,13 @@ export default function NoteDetail() {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
class={`component note-detail ${isFullHeight ? "full-height" : ""}`}
|
||||
class={clsx("component note-detail", {
|
||||
"full-height": isFullHeight,
|
||||
"fixed-tree": hasFixedTree
|
||||
})}
|
||||
>
|
||||
{hasFixedTree && <FixedTree noteContext={noteContext} />}
|
||||
|
||||
{Object.entries(noteTypesToRender).map(([ itemType, Element ]) => {
|
||||
return <NoteDetailWrapper
|
||||
Element={Element}
|
||||
@@ -231,6 +232,11 @@ export default function NoteDetail() {
|
||||
);
|
||||
}
|
||||
|
||||
function FixedTree({ noteContext }: { noteContext: NoteContext }) {
|
||||
const [ treeEl ] = useLegacyWidget(() => new NoteTreeWidget(), { noteContext });
|
||||
return <div class="fixed-note-tree-container">{treeEl}</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a single note type widget, in order to keep it in the DOM even after the user has switched away to another note type. This allows faster loading of the same note type again. The properties are cached, so that they are updated only
|
||||
* while the widget is visible, to avoid rendering in the background. When not visible, the DOM element is simply hidden.
|
||||
|
||||
@@ -5,6 +5,7 @@ import clsx from "clsx";
|
||||
import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact";
|
||||
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../components/note_context";
|
||||
import FAttribute from "../entities/fattribute";
|
||||
import FNote from "../entities/fnote";
|
||||
import { Attribute } from "../services/attribute_parser";
|
||||
@@ -40,8 +41,8 @@ type OnChangeEventData = TargetedEvent<HTMLInputElement, Event> | InputEvent | J
|
||||
type OnChangeListener = (e: OnChangeEventData) => Promise<void>;
|
||||
|
||||
export default function PromotedAttributes() {
|
||||
const { note, componentId } = useNoteContext();
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
const { note, componentId, noteContext } = useNoteContext();
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext);
|
||||
return <PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />;
|
||||
}
|
||||
|
||||
@@ -74,12 +75,12 @@ export function PromotedAttributesContent({ note, componentId, cells, setCells }
|
||||
*
|
||||
* The cells are returned as a state since they can also be altered internally if needed, for example to add a new empty cell.
|
||||
*/
|
||||
export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch<StateUpdater<Cell[] | undefined>> ] {
|
||||
export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string, noteContext: NoteContext | undefined): [ Cell[] | undefined, Dispatch<StateUpdater<Cell[] | undefined>> ] {
|
||||
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||
const [ cells, setCells ] = useState<Cell[]>();
|
||||
|
||||
function refresh() {
|
||||
if (!note || viewType === "table") {
|
||||
if (!note || viewType === "table" || noteContext?.viewScope?.viewMode !== "default") {
|
||||
setCells([]);
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +125,7 @@ export function usePromotedAttributeData(note: FNote | null | undefined, compone
|
||||
setCells(cells);
|
||||
}
|
||||
|
||||
useEffect(refresh, [ note, viewType ]);
|
||||
useEffect(refresh, [ note, viewType, noteContext ]);
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) {
|
||||
refresh();
|
||||
|
||||
@@ -29,7 +29,6 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
const isVerticalLayout = !isHorizontalLayout;
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus();
|
||||
const isMobileLocal = isMobile();
|
||||
const logoRef = useRef<SVGSVGElement>(null);
|
||||
useStaticTooltip(logoRef);
|
||||
|
||||
@@ -44,9 +43,12 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
</div>}
|
||||
</>}
|
||||
noDropdownListStyle
|
||||
onShown={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover") : undefined}
|
||||
onHidden={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover") : undefined}
|
||||
mobileBackdrop
|
||||
>
|
||||
{isMobile() && <>
|
||||
<MenuItem command="searchNotes" icon="bx bx-search" text={t("global_menu.search_notes")} />
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
|
||||
<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")} />
|
||||
@@ -107,8 +109,7 @@ function BrowserOnlyOptions() {
|
||||
|
||||
function DevelopmentOptions({ dropStart }: { dropStart: boolean }) {
|
||||
return <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem disabled>Development Options</FormListItem>
|
||||
<FormListHeader text="Development Options" />
|
||||
<FormDropdownSubmenu icon="bx bx-test-tube" title="Experimental features" dropStart={dropStart}>
|
||||
{experimentalFeatures.map((feature) => (
|
||||
<ExperimentalFeatureToggle key={feature.id} experimentalFeature={feature as ExperimentalFeature} />
|
||||
|
||||
@@ -10,12 +10,6 @@
|
||||
--card-padding: 0.6em;
|
||||
}
|
||||
|
||||
body.mobile .board-view {
|
||||
scroll-snap-type: x mandatory;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.board-view-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -26,6 +20,12 @@ body.mobile .board-view {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
body.mobile .board-view-container {
|
||||
scroll-snap-type: x mandatory;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.board-view-container .board-column {
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { EventData } from "../../components/app_context.js";
|
||||
import { LOCALES } from "@triliumnext/commons";
|
||||
import { readCssVar } from "../../utils/css-var.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import options from "../../services/options.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
import { EventData } from "../../components/app_context.js";
|
||||
import { getEnabledExperimentalFeatureIds } from "../../services/experimental_features.js";
|
||||
import options from "../../services/options.js";
|
||||
import utils, { isMobile } from "../../services/utils.js";
|
||||
import { readCssVar } from "../../utils/css-var.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
|
||||
/**
|
||||
* The root container is the top-most widget/container, from which the entire layout derives.
|
||||
@@ -17,14 +18,12 @@ import { getEnabledExperimentalFeatureIds } from "../../services/experimental_fe
|
||||
* - `#root-container.vertical-layout`, if the current layout is horizontal.
|
||||
*/
|
||||
export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
private originalViewportHeight: number;
|
||||
|
||||
constructor(isHorizontalLayout: boolean) {
|
||||
super(isHorizontalLayout ? "column" : "row");
|
||||
|
||||
this.id("root-widget");
|
||||
this.css("height", "100dvh");
|
||||
this.originalViewportHeight = getViewportHeight();
|
||||
}
|
||||
|
||||
render(): JQuery<HTMLElement> {
|
||||
@@ -39,6 +38,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
this.#setThemeCapabilities();
|
||||
this.#setLocaleAndDirection(options.get("locale"));
|
||||
this.#setExperimentalFeatures();
|
||||
this.#initPWATopbarColor();
|
||||
|
||||
return super.render();
|
||||
}
|
||||
@@ -64,8 +64,12 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
}
|
||||
|
||||
#onMobileResize() {
|
||||
const currentViewportHeight = getViewportHeight();
|
||||
const isKeyboardOpened = (currentViewportHeight < this.originalViewportHeight);
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// If viewport is significantly smaller, keyboard is likely open
|
||||
const isKeyboardOpened = windowHeight - viewportHeight > 150;
|
||||
|
||||
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
|
||||
}
|
||||
|
||||
@@ -88,7 +92,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
}
|
||||
|
||||
#setBackdropEffects() {
|
||||
const enabled = options.is("backdropEffectsEnabled");
|
||||
const enabled = options.is("backdropEffectsEnabled") && !isMobile();
|
||||
document.body.classList.toggle("backdrop-effects-disabled", !enabled);
|
||||
}
|
||||
|
||||
@@ -96,7 +100,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
// Supports background effects
|
||||
|
||||
const useBgfx = readCssVar(document.documentElement, "allow-background-effects")
|
||||
.asBoolean(false);
|
||||
.asBoolean(false);
|
||||
|
||||
document.body.classList.toggle("theme-supports-background-effects", useBgfx);
|
||||
}
|
||||
@@ -112,8 +116,23 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
document.body.lang = locale;
|
||||
document.body.dir = correspondingLocale?.rtl ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
#initPWATopbarColor() {
|
||||
if (!utils.isPWA()) return;
|
||||
const tracker = $("#background-color-tracker");
|
||||
|
||||
if (tracker.length) {
|
||||
const applyThemeColor = () => {
|
||||
let meta = $("meta[name='theme-color']");
|
||||
if (!meta.length) {
|
||||
meta = $(`<meta name="theme-color">`).appendTo($("head"));
|
||||
}
|
||||
meta.attr("content", tracker.css("color"));
|
||||
};
|
||||
|
||||
tracker.on("transitionend", applyThemeColor);
|
||||
applyThemeColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getViewportHeight() {
|
||||
return window.visualViewport?.height ?? window.innerHeight;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
.scrolling-container {
|
||||
--content-margin-inline: 24px;
|
||||
|
||||
overflow: auto;
|
||||
scroll-behavior: smooth;
|
||||
position: relative;
|
||||
|
||||
> .inline-title,
|
||||
> .note-detail > .note-detail-editable-text,
|
||||
> .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor,
|
||||
> .note-list-widget:not(.full-height) .note-list-wrapper {
|
||||
padding-inline: 24px;
|
||||
margin-inline: var(--content-margin-inline);
|
||||
}
|
||||
|
||||
> .inline-title {
|
||||
padding-inline: var(--content-margin-inline);
|
||||
}
|
||||
|
||||
> .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor {
|
||||
overflow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.note-split.type-code:not(.mime-text-x-sqlite) {
|
||||
|
||||
@@ -91,8 +91,9 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-editable-text {
|
||||
padding: 0 1em;
|
||||
.modal.popup-editor-dialog .note-detail-editable-text-editor {
|
||||
margin: 0 28px;
|
||||
overflow: visible; /* Allow selection rectangle to go outside of the editor area */
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-file {
|
||||
|
||||
@@ -5,13 +5,15 @@ import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import tree from "../../services/tree";
|
||||
import utils from "../../services/utils";
|
||||
import NoteList from "../collections/NoteList";
|
||||
import FloatingButtons from "../FloatingButtons";
|
||||
import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
|
||||
import { DESKTOP_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
|
||||
import NoteBadges from "../layout/NoteBadges";
|
||||
import NoteIcon from "../note_icon";
|
||||
import NoteTitleWidget from "../note_title";
|
||||
import NoteDetail from "../NoteDetail";
|
||||
@@ -23,8 +25,6 @@ import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar";
|
||||
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
|
||||
import FormattingToolbar from "../ribbon/FormattingToolbar";
|
||||
import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar";
|
||||
import NoteBadges from "../layout/NoteBadges";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function PopupEditor() {
|
||||
const [ noteContext, setNoteContext ] = useState(new NoteContext("_popup-editor"));
|
||||
const isMobile = utils.isMobile();
|
||||
const items = useMemo(() => {
|
||||
const baseItems = isMobile ? MOBILE_FLOATING_BUTTONS : DESKTOP_FLOATING_BUTTONS;
|
||||
const baseItems = isMobile ? [] : DESKTOP_FLOATING_BUTTONS;
|
||||
return baseItems.filter(item => !POPUP_HIDDEN_FLOATING_BUTTONS.includes(item));
|
||||
}, [ isMobile ]);
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { useContext, useMemo } from "preact/hooks";
|
||||
import { LaunchBarContext, LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
||||
import { CSSProperties } from "preact";
|
||||
import type FNote from "../../entities/fnote";
|
||||
import { useChildNotes, useNoteLabelBoolean } from "../react/hooks";
|
||||
import "./BookmarkButtons.css";
|
||||
|
||||
import { CSSProperties } from "preact";
|
||||
import { useContext, useMemo } from "preact/hooks";
|
||||
|
||||
import type FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { FormDropdownSubmenu, FormListItem } from "../react/FormList";
|
||||
import { useChildNotes, useNote, useNoteIcon, useNoteLabelBoolean } from "../react/hooks";
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { CustomNoteLauncher } from "./GenericButtons";
|
||||
import ResponsiveContainer from "../react/ResponsiveContainer";
|
||||
import { CustomNoteLauncher, launchCustomNoteLauncher } from "./GenericButtons";
|
||||
import { LaunchBarContext, LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
||||
|
||||
const PARENT_NOTE_ID = "_lbBookmarks";
|
||||
|
||||
@@ -19,17 +24,64 @@ export default function BookmarkButtons() {
|
||||
const childNotes = useChildNotes(PARENT_NOTE_ID);
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
|
||||
</div>
|
||||
)
|
||||
<ResponsiveContainer
|
||||
desktop={
|
||||
<div style={style}>
|
||||
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
|
||||
</div>
|
||||
}
|
||||
mobile={
|
||||
<LaunchBarDropdownButton
|
||||
icon="bx bx-bookmark"
|
||||
title={t("bookmark_buttons.bookmarks")}
|
||||
>
|
||||
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
|
||||
</LaunchBarDropdownButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SingleBookmark({ note }: { note: FNote }) {
|
||||
const [ bookmarkFolder ] = useNoteLabelBoolean(note, "bookmarkFolder");
|
||||
return bookmarkFolder
|
||||
? <BookmarkFolder note={note} />
|
||||
: <CustomNoteLauncher launcherNote={note} getTargetNoteId={() => note.noteId} />
|
||||
return <ResponsiveContainer
|
||||
desktop={
|
||||
bookmarkFolder
|
||||
? <BookmarkFolder note={note} />
|
||||
: <CustomNoteLauncher launcherNote={note} getTargetNoteId={() => note.noteId} />
|
||||
}
|
||||
mobile={<MobileBookmarkItem noteId={note.noteId} bookmarkFolder={bookmarkFolder} />}
|
||||
/>;
|
||||
}
|
||||
|
||||
function MobileBookmarkItem({ noteId, bookmarkFolder }: { noteId: string, bookmarkFolder: boolean }) {
|
||||
const note = useNote(noteId);
|
||||
const noteIcon = useNoteIcon(note);
|
||||
if (!note) return null;
|
||||
|
||||
return (
|
||||
!bookmarkFolder
|
||||
? <FormListItem icon={noteIcon} onClick={(e) => launchCustomNoteLauncher(e, { launcherNote: note, getTargetNoteId: () => note.noteId })}>{note.title}</FormListItem>
|
||||
: <MobileBookmarkFolder note={note} />
|
||||
);
|
||||
}
|
||||
|
||||
function MobileBookmarkFolder({ note }: { note: FNote }) {
|
||||
const childNotes = useChildNotes(note.noteId);
|
||||
|
||||
return (
|
||||
<FormDropdownSubmenu icon="bx bx-folder" title={note.title}>
|
||||
{childNotes.map(childNote => (
|
||||
<FormListItem
|
||||
key={childNote.noteId}
|
||||
icon={childNote.getIcon()}
|
||||
onClick={(e) => launchCustomNoteLauncher(e, { launcherNote: childNote, getTargetNoteId: () => childNote.noteId })}
|
||||
>
|
||||
{childNote.title}
|
||||
</FormListItem>
|
||||
))}
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
function BookmarkFolder({ note }: { note: FNote }) {
|
||||
@@ -55,5 +107,5 @@ function BookmarkFolder({ note }: { note: FNote }) {
|
||||
</ul>
|
||||
</div>
|
||||
</LaunchBarDropdownButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import clsx from "clsx";
|
||||
import server from "../../services/server";
|
||||
import { TargetedMouseEvent, VNode } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { Dayjs, getWeekInfo, WeekSettings } from "@triliumnext/commons";
|
||||
import { Dayjs } from "@triliumnext/commons";
|
||||
import { t } from "../../services/i18n";
|
||||
|
||||
interface DateNotesForMonth {
|
||||
@@ -22,7 +22,6 @@ const DAYS_OF_WEEK = [
|
||||
|
||||
interface DateRangeInfo {
|
||||
weekNumbers: number[];
|
||||
weekYears: number[];
|
||||
dates: Dayjs[];
|
||||
}
|
||||
|
||||
@@ -37,27 +36,19 @@ export interface CalendarArgs {
|
||||
|
||||
export default function Calendar(args: CalendarArgs) {
|
||||
const [ rawFirstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek");
|
||||
const [ firstWeekOfYear ] = useTriliumOptionInt("firstWeekOfYear");
|
||||
const [ minDaysInFirstWeek ] = useTriliumOptionInt("minDaysInFirstWeek");
|
||||
const firstDayOfWeekISO = (rawFirstDayOfWeek === 0 ? 7 : rawFirstDayOfWeek);
|
||||
|
||||
const weekSettings = {
|
||||
firstDayOfWeek: firstDayOfWeekISO,
|
||||
firstWeekOfYear: firstWeekOfYear ?? 0,
|
||||
minDaysInFirstWeek: minDaysInFirstWeek ?? 4
|
||||
};
|
||||
|
||||
const date = args.date;
|
||||
const firstDay = date.startOf('month');
|
||||
const firstDayISO = firstDay.isoWeekday();
|
||||
const monthInfo = getMonthInformation(date, firstDayISO, weekSettings);
|
||||
const monthInfo = getMonthInformation(date, firstDayISO, firstDayOfWeekISO);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CalendarWeekHeader rawFirstDayOfWeek={rawFirstDayOfWeek} />
|
||||
<div className="calendar-body" data-calendar-area="month">
|
||||
{firstDayISO !== firstDayOfWeekISO && <PreviousMonthDays info={monthInfo.prevMonth} weekSettings={weekSettings} {...args} />}
|
||||
<CurrentMonthDays weekSettings={weekSettings} {...args} />
|
||||
{firstDayISO !== firstDayOfWeekISO && <PreviousMonthDays info={monthInfo.prevMonth} {...args} />}
|
||||
<CurrentMonthDays firstDayOfWeekISO={firstDayOfWeekISO} {...args} />
|
||||
<NextMonthDays dates={monthInfo.nextMonth.dates} {...args} />
|
||||
</div>
|
||||
</>
|
||||
@@ -76,7 +67,7 @@ function CalendarWeekHeader({ rawFirstDayOfWeek }: { rawFirstDayOfWeek: number }
|
||||
)
|
||||
}
|
||||
|
||||
function PreviousMonthDays({ date, info: { dates, weekNumbers, weekYears }, weekSettings, ...args }: { date: Dayjs, info: DateRangeInfo, weekSettings: WeekSettings } & CalendarArgs) {
|
||||
function PreviousMonthDays({ date, info: { dates, weekNumbers }, ...args }: { date: Dayjs, info: DateRangeInfo } & CalendarArgs) {
|
||||
const prevMonth = date.subtract(1, 'month').format('YYYY-MM');
|
||||
const [ dateNotesForPrevMonth, setDateNotesForPrevMonth ] = useState<DateNotesForMonth>();
|
||||
|
||||
@@ -86,28 +77,27 @@ function PreviousMonthDays({ date, info: { dates, weekNumbers, weekYears }, week
|
||||
|
||||
return (
|
||||
<>
|
||||
<CalendarWeek date={date} weekNumber={weekNumbers[0]} weekYear={weekYears[0]} {...args} />
|
||||
<CalendarWeek date={date} weekNumber={weekNumbers[0]} {...args} />
|
||||
{dates.map(date => <CalendarDay key={date.toISOString()} date={date} dateNotesForMonth={dateNotesForPrevMonth} className="calendar-date-prev-month" {...args} />)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function CurrentMonthDays({ date, weekSettings, ...args }: { date: Dayjs, weekSettings: WeekSettings } & CalendarArgs) {
|
||||
function CurrentMonthDays({ date, firstDayOfWeekISO, ...args }: { date: Dayjs, firstDayOfWeekISO: number } & CalendarArgs) {
|
||||
let dateCursor = date;
|
||||
const currentMonth = date.month();
|
||||
const items: VNode[] = [];
|
||||
const curMonthString = date.format('YYYY-MM');
|
||||
const [ dateNotesForCurMonth, setDateNotesForCurMonth ] = useState<DateNotesForMonth>();
|
||||
const { firstDayOfWeek, firstWeekOfYear, minDaysInFirstWeek } = weekSettings;
|
||||
|
||||
useEffect(() => {
|
||||
server.get<DateNotesForMonth>(`special-notes/notes-for-month/${curMonthString}`).then(setDateNotesForCurMonth);
|
||||
}, [ date ]);
|
||||
|
||||
while (dateCursor.month() === currentMonth) {
|
||||
const { weekYear, weekNumber } = getWeekInfo(dateCursor, weekSettings);
|
||||
if (dateCursor.isoWeekday() === firstDayOfWeek) {
|
||||
items.push(<CalendarWeek key={`${weekYear}-W${weekNumber}`} date={dateCursor} weekNumber={weekNumber} weekYear={weekYear} {...args}/>)
|
||||
const weekNumber = getWeekNumber(dateCursor, firstDayOfWeekISO);
|
||||
if (dateCursor.isoWeekday() === firstDayOfWeekISO) {
|
||||
items.push(<CalendarWeek key={`${dateCursor.year()}-W${weekNumber}`} date={dateCursor} weekNumber={weekNumber} {...args}/>)
|
||||
}
|
||||
|
||||
items.push(<CalendarDay key={dateCursor.toISOString()} date={dateCursor} dateNotesForMonth={dateNotesForCurMonth} {...args} />)
|
||||
@@ -151,8 +141,14 @@ function CalendarDay({ date, dateNotesForMonth, className, activeDate, todaysDat
|
||||
);
|
||||
}
|
||||
|
||||
function CalendarWeek({ date, weekNumber, weekYear, weekNotes, onWeekClicked }: { weekNumber: number, weekYear: number, weekNotes: string[] } & Pick<CalendarArgs, "date" | "onWeekClicked">) {
|
||||
const weekString = `${weekYear}-W${String(weekNumber).padStart(2, '0')}`;
|
||||
function CalendarWeek({ date, weekNumber, weekNotes, onWeekClicked }: { weekNumber: number, weekNotes: string[] } & Pick<CalendarArgs, "date" | "onWeekClicked">) {
|
||||
const localDate = date.local();
|
||||
|
||||
// Handle case where week is in between years.
|
||||
let year = localDate.year();
|
||||
if (localDate.month() === 11 && weekNumber === 1) year++;
|
||||
|
||||
const weekString = `${year}-W${String(weekNumber).padStart(2, '0')}`;
|
||||
|
||||
if (onWeekClicked) {
|
||||
return (
|
||||
@@ -173,33 +169,33 @@ function CalendarWeek({ date, weekNumber, weekYear, weekNotes, onWeekClicked }:
|
||||
>{weekNumber}</span>);
|
||||
}
|
||||
|
||||
export function getMonthInformation(date: Dayjs, firstDayISO: number, weekSettings: WeekSettings) {
|
||||
export function getMonthInformation(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number) {
|
||||
return {
|
||||
prevMonth: getPrevMonthDays(date, firstDayISO, weekSettings),
|
||||
nextMonth: getNextMonthDays(date, weekSettings.firstDayOfWeek)
|
||||
prevMonth: getPrevMonthDays(date, firstDayISO, firstDayOfWeekISO),
|
||||
nextMonth: getNextMonthDays(date, firstDayOfWeekISO)
|
||||
}
|
||||
}
|
||||
|
||||
function getPrevMonthDays(date: Dayjs, firstDayISO: number, weekSettings: WeekSettings): DateRangeInfo {
|
||||
function getPrevMonthDays(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number): DateRangeInfo {
|
||||
const prevMonthLastDay = date.subtract(1, 'month').endOf('month');
|
||||
const daysToAdd = (firstDayISO - weekSettings.firstDayOfWeek + 7) % 7;
|
||||
const daysToAdd = (firstDayISO - firstDayOfWeekISO + 7) % 7;
|
||||
const dates: Dayjs[] = [];
|
||||
|
||||
const firstDay = date.startOf('month');
|
||||
const { weekYear, weekNumber } = getWeekInfo(firstDay, weekSettings);
|
||||
const weekNumber = getWeekNumber(firstDay, firstDayOfWeekISO);
|
||||
|
||||
// Get dates from previous month
|
||||
for (let i = daysToAdd - 1; i >= 0; i--) {
|
||||
dates.push(prevMonthLastDay.subtract(i, 'day'));
|
||||
}
|
||||
|
||||
return { weekNumbers: [ weekNumber ], weekYears: [ weekYear ], dates };
|
||||
return { weekNumbers: [ weekNumber ], dates };
|
||||
}
|
||||
|
||||
function getNextMonthDays(date: Dayjs, firstDayOfWeek: number): DateRangeInfo {
|
||||
function getNextMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo {
|
||||
const lastDayOfMonth = date.endOf('month');
|
||||
const lastDayISO = lastDayOfMonth.isoWeekday();
|
||||
const lastDayOfUserWeek = ((firstDayOfWeek + 6 - 1) % 7) + 1;
|
||||
const lastDayOfUserWeek = ((firstDayOfWeekISO + 6 - 1) % 7) + 1;
|
||||
const nextMonthFirstDay = date.add(1, 'month').startOf('month');
|
||||
const dates: Dayjs[] = [];
|
||||
|
||||
@@ -210,5 +206,16 @@ function getNextMonthDays(date: Dayjs, firstDayOfWeek: number): DateRangeInfo {
|
||||
dates.push(nextMonthFirstDay.add(i, 'day'));
|
||||
}
|
||||
}
|
||||
return { weekNumbers: [], weekYears: [], dates };
|
||||
return { weekNumbers: [], dates };
|
||||
}
|
||||
|
||||
export function getWeekNumber(date: Dayjs, firstDayOfWeekISO: number): number {
|
||||
const weekStart = getWeekStartDate(date, firstDayOfWeekISO);
|
||||
return weekStart.isoWeek();
|
||||
}
|
||||
|
||||
function getWeekStartDate(date: Dayjs, firstDayOfWeekISO: number): Dayjs {
|
||||
const currentISO = date.isoWeekday();
|
||||
const diff = (currentISO - firstDayOfWeekISO + 7) % 7;
|
||||
return date.clone().subtract(diff, "day").startOf("day");
|
||||
}
|
||||
|
||||
@@ -7,32 +7,18 @@ import { isCtrlKey } from "../../services/utils";
|
||||
import { useGlobalShortcut, useNoteLabel } from "../react/hooks";
|
||||
import { LaunchBarActionButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
||||
|
||||
export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNoteId }: {
|
||||
export function CustomNoteLauncher(props: {
|
||||
launcherNote: FNote;
|
||||
getTargetNoteId: (launcherNote: FNote) => string | null | Promise<string | null>;
|
||||
getHoistedNoteId?: (launcherNote: FNote) => string | null;
|
||||
keyboardShortcut?: string;
|
||||
}) {
|
||||
const { launcherNote, getTargetNoteId } = props;
|
||||
const { icon, title } = useLauncherIconAndTitle(launcherNote);
|
||||
|
||||
const launch = useCallback(async (evt: MouseEvent | KeyboardEvent) => {
|
||||
if (evt.which === 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetNoteId = await getTargetNoteId(launcherNote);
|
||||
if (!targetNoteId) return;
|
||||
|
||||
const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
const ctrlKey = isCtrlKey(evt);
|
||||
|
||||
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
|
||||
const activate = !!evt.shiftKey;
|
||||
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate);
|
||||
} else {
|
||||
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault);
|
||||
}
|
||||
}, [ launcherNote, getTargetNoteId, getHoistedNoteId ]);
|
||||
await launchCustomNoteLauncher(evt, props);
|
||||
}, [ props ]);
|
||||
|
||||
// Keyboard shortcut.
|
||||
const [ shortcut ] = useNoteLabel(launcherNote, "keyboardShortcut");
|
||||
@@ -54,3 +40,24 @@ export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNo
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function launchCustomNoteLauncher(evt: MouseEvent | KeyboardEvent, { launcherNote, getTargetNoteId, getHoistedNoteId }: {
|
||||
launcherNote: FNote;
|
||||
getTargetNoteId: (launcherNote: FNote) => string | null | Promise<string | null>;
|
||||
getHoistedNoteId?: (launcherNote: FNote) => string | null;
|
||||
}) {
|
||||
if (evt.which === 3) return;
|
||||
|
||||
const targetNoteId = await getTargetNoteId(launcherNote);
|
||||
if (!targetNoteId) return;
|
||||
|
||||
const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
const ctrlKey = isCtrlKey(evt);
|
||||
|
||||
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
|
||||
const activate = !!evt.shiftKey;
|
||||
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate);
|
||||
} else {
|
||||
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export function LaunchBarDropdownButton({ children, icon, dropdownOptions, ...pr
|
||||
placement: isHorizontalLayout ? "bottom" : "right"
|
||||
}
|
||||
}}
|
||||
mobileBackdrop
|
||||
{...props}
|
||||
>{children}</Dropdown>
|
||||
);
|
||||
|
||||
@@ -50,6 +50,9 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body.desktop .title-actions {
|
||||
> .collapsible,
|
||||
> .note-type-switcher {
|
||||
padding-inline-start: calc(24px - var(--title-actions-padding-start));
|
||||
@@ -57,3 +60,4 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
import "./NoteTitleActions.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import CollectionProperties from "../note_bars/CollectionProperties";
|
||||
import { checkFullHeight, getExtendedWidgetType } from "../NoteDetail";
|
||||
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
|
||||
import SimpleBadge from "../react/Badge";
|
||||
import Collapsible, { ExternallyControlledCollapsible } from "../react/Collapsible";
|
||||
import { useNoteContext, useNoteLabel, useNoteProperty, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
|
||||
import NoteLink, { NewNoteLink } from "../react/NoteLink";
|
||||
import { NewNoteLink } from "../react/NoteLink";
|
||||
import { useEditedNotes } from "../ribbon/EditedNotesTab";
|
||||
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
|
||||
import NoteTypeSwitcher from "./NoteTypeSwitcher";
|
||||
|
||||
export default function NoteTitleActions() {
|
||||
const { note, ntxId, componentId, noteContext } = useNoteContext();
|
||||
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
|
||||
const { note, ntxId, componentId, noteContext, viewScope } = useNoteContext();
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
|
||||
return (
|
||||
@@ -27,7 +23,7 @@ export default function NoteTitleActions() {
|
||||
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
|
||||
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
|
||||
<EditedNotes />
|
||||
<NoteTypeSwitcher />
|
||||
{(!viewScope?.viewMode || viewScope.viewMode === "default") && <NoteTypeSwitcher />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -48,7 +44,7 @@ function PromotedAttributes({ note, componentId, noteContext }: {
|
||||
componentId: string,
|
||||
noteContext: NoteContext | undefined
|
||||
}) {
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext);
|
||||
const [ expanded, setExpanded ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -58,101 +58,12 @@
|
||||
|
||||
.dropdown-note-info {
|
||||
padding: 1em !important;
|
||||
|
||||
ul {
|
||||
--row-block-margin: .2em;
|
||||
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: calc(0px - var(--row-block-margin));
|
||||
margin-bottom: 12px;
|
||||
display: table;
|
||||
|
||||
li {
|
||||
display: table-row;
|
||||
|
||||
> strong {
|
||||
display: table-cell;
|
||||
padding: var(--row-block-margin) 0;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: table-cell;
|
||||
user-select: text;
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-note-paths {
|
||||
.note-paths-widget {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.note-path-intro {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.note-path-list {
|
||||
margin: 12px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
/* Note path card */
|
||||
li {
|
||||
--border-radius: 6px;
|
||||
|
||||
position: relative;
|
||||
background: var(--card-background-color);
|
||||
padding: 8px 20px 8px 25px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Current path arrow */
|
||||
&.path-current::before {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
content: "\ee8f";
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
bottom: 0;
|
||||
font-family: "boxicons";
|
||||
font-size: .75em;
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Note path segment */
|
||||
a {
|
||||
margin-inline: 2px;
|
||||
padding-inline: 2px;
|
||||
color: currentColor;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
|
||||
/* The last segment of the note path */
|
||||
&.basename {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.backlinks-widget > .dropdown-menu {
|
||||
@@ -160,84 +71,6 @@
|
||||
|
||||
max-height: 60vh;
|
||||
overflow-y: scroll;
|
||||
|
||||
/* Backlink card */
|
||||
li {
|
||||
--border-radius: 8px;
|
||||
|
||||
max-width: 600px;
|
||||
padding: 10px 20px;
|
||||
background: var(--card-background-color);
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
/* Card header */
|
||||
& > span:first-child {
|
||||
display: block;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
/* Note path */
|
||||
> small {
|
||||
flex: 100%;
|
||||
order: -1;
|
||||
font-size: .65rem;
|
||||
|
||||
.note-path {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note icon */
|
||||
> .tn-icon {
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
|
||||
/* Note title */
|
||||
> a {
|
||||
margin-inline-start: 4px;
|
||||
color: currentColor;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Card content - excerpt */
|
||||
& > span:nth-child(2) > div {
|
||||
all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */
|
||||
display: block;
|
||||
|
||||
margin: 8px 0;
|
||||
border-radius: 4px;
|
||||
background: var(--quick-search-result-content-background);
|
||||
padding: 8px;
|
||||
font-size: .75rem;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
color: var(--quick-search-result-highlight-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,16 +117,50 @@
|
||||
|
||||
}
|
||||
|
||||
div.similar-notes-widget div.similar-notes-wrapper {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
button.select-button:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout .note-info-content {
|
||||
ul {
|
||||
--row-block-margin: .2em;
|
||||
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: calc(0px - var(--row-block-margin));
|
||||
margin-bottom: 12px;
|
||||
display: table;
|
||||
|
||||
li {
|
||||
display: table-row;
|
||||
|
||||
> strong {
|
||||
display: table-cell;
|
||||
padding: var(--row-block-margin) 0;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: table-cell;
|
||||
user-select: text;
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout div.similar-notes-widget div.similar-notes-wrapper {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout.mobile div.similar-notes-widget div.similar-notes-wrapper {
|
||||
max-height: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
margin: 0 !important;
|
||||
padding: 0;
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function StatusBar() {
|
||||
similarNotesShown: activePane === "similar-notes",
|
||||
setSimilarNotesShown: (shown) => setActivePane(shown && "similar-notes")
|
||||
};
|
||||
const isHiddenNote = note?.isInHiddenSubtree();
|
||||
const isHiddenNote = note?.isHiddenCompletely();
|
||||
|
||||
return (
|
||||
<div className="status-bar">
|
||||
@@ -212,8 +212,8 @@ export function getLocaleName(locale: Locale | null | undefined) {
|
||||
|
||||
//#region Note info & Similar
|
||||
interface NoteInfoContext extends StatusBarContext {
|
||||
similarNotesShown: boolean;
|
||||
setSimilarNotesShown: (value: boolean) => void;
|
||||
similarNotesShown?: boolean;
|
||||
setSimilarNotesShown?: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function NoteInfoBadge(context: NoteInfoContext) {
|
||||
@@ -225,7 +225,7 @@ export function NoteInfoBadge(context: NoteInfoContext) {
|
||||
|
||||
// Keyboard shortcut.
|
||||
useTriliumEvent("toggleRibbonTabNoteInfo", () => enabled && dropdownRef.current?.show());
|
||||
useTriliumEvent("toggleRibbonTabSimilarNotes", () => setSimilarNotesShown(!similarNotesShown));
|
||||
useTriliumEvent("toggleRibbonTabSimilarNotes", () => setSimilarNotesShown && setSimilarNotesShown(!similarNotesShown));
|
||||
|
||||
return (enabled &&
|
||||
<StatusBarDropdown
|
||||
@@ -242,8 +242,8 @@ export function NoteInfoBadge(context: NoteInfoContext) {
|
||||
);
|
||||
}
|
||||
|
||||
function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }: NoteInfoContext & {
|
||||
dropdownRef: RefObject<BootstrapDropdown>;
|
||||
export function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }: Pick<NoteInfoContext, "note" | "setSimilarNotesShown"> & {
|
||||
dropdownRef?: RefObject<BootstrapDropdown>;
|
||||
noteType: NoteType;
|
||||
}) {
|
||||
const { metadata, ...sizeProps } = useNoteMetadata(note);
|
||||
@@ -251,7 +251,7 @@ function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }:
|
||||
const noteTypeMapping = useMemo(() => NOTE_TYPES.find(t => t.type === noteType), [ noteType ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="note-info-content">
|
||||
<ul>
|
||||
{originalFileName && <NoteInfoValue text={t("file_properties.original_file_name")} value={originalFileName} />}
|
||||
<NoteInfoValue text={t("note_info_widget.created")} value={formatDateTime(metadata?.dateCreated)} />
|
||||
@@ -262,14 +262,14 @@ function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }:
|
||||
<NoteInfoValue text={t("note_info_widget.note_size")} title={t("note_info_widget.note_size_info")} value={<NoteSizeWidget {...sizeProps} />} />
|
||||
</ul>
|
||||
|
||||
<LinkButton
|
||||
{setSimilarNotesShown && <LinkButton
|
||||
text={t("note_info_widget.show_similar_notes")}
|
||||
onClick={() => {
|
||||
dropdownRef.current?.hide();
|
||||
dropdownRef?.current?.hide();
|
||||
setSimilarNotesShown(true);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ function BacklinksBadge({ note, viewScope }: StatusBarContext) {
|
||||
const count = useBacklinkCount(note, viewScope?.viewMode === "default");
|
||||
return (note && count > 0 &&
|
||||
<StatusBarDropdown
|
||||
className="backlinks-badge backlinks-widget"
|
||||
className="backlinks-badge backlinks-widget tn-backlinks-widget"
|
||||
icon="bx bx-link"
|
||||
text={t("status_bar.backlinks", { count })}
|
||||
title={t("status_bar.backlinks_title", { count })}
|
||||
|
||||
@@ -89,12 +89,25 @@
|
||||
|
||||
&.type-text {
|
||||
padding: 10px;
|
||||
--ck-content-todo-list-checkmark-size: 8px;
|
||||
|
||||
p { margin-bottom: 0.2em;}
|
||||
hr { margin-block: 0.1em; height: 1px; }
|
||||
h2 { font-size: 1.20em; }
|
||||
h3 { font-size: 1.15em; }
|
||||
h4 { font-size: 1.10em; }
|
||||
h5 { font-size: 1.05em}
|
||||
h6 { font-size: 1em; }
|
||||
ul, ol { margin: 0 }
|
||||
}
|
||||
|
||||
&.type-book,
|
||||
&.type-contentWidget,
|
||||
&.type-search,
|
||||
&.type-empty {
|
||||
&.type-empty,
|
||||
&.type-relationMap,
|
||||
&.type-launcher,
|
||||
&.tab-preview-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -105,13 +118,6 @@
|
||||
.preview-placeholder {
|
||||
font-size: 500%;
|
||||
}
|
||||
|
||||
p { margin-bottom: 0.2em;}
|
||||
h2 { font-size: 1.20em; }
|
||||
h3 { font-size: 1.15em; }
|
||||
h4 { font-size: 1.10em; }
|
||||
h5 { font-size: 1.05em}
|
||||
h6 { font-size: 1em; }
|
||||
}
|
||||
|
||||
&.with-split {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "./TabSwitcher.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { ComponentChild } from "preact";
|
||||
import { createPortal, Fragment } from "preact/compat";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -11,6 +12,7 @@ import contextMenu from "../../menus/context_menu";
|
||||
import { getHue, parseColor } from "../../services/css_class_manager";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import type { ViewMode, ViewScope } from "../../services/link";
|
||||
import { NoteContent } from "../collections/legacy/ListOrGridView";
|
||||
import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets";
|
||||
import { ICON_MAPPINGS } from "../note_bars/CollectionProperties";
|
||||
@@ -20,6 +22,13 @@ import Icon from "../react/Icon";
|
||||
import LinkButton from "../react/LinkButton";
|
||||
import Modal from "../react/Modal";
|
||||
|
||||
const VIEW_MODE_ICON_MAPPINGS: Record<Exclude<ViewMode, "default">, string> = {
|
||||
source: "bx bx-code",
|
||||
"contextual-help": "bx bx-help-circle",
|
||||
"note-map": "bx bxs-network-chart",
|
||||
attachments: "bx bx-paperclip",
|
||||
};
|
||||
|
||||
export default function TabSwitcher() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const mainNoteContexts = useMainNoteContexts();
|
||||
@@ -138,7 +147,6 @@ function Tab({ noteContext, containerRef, selectTab, activeNtxId }: {
|
||||
activeNtxId: string | null | undefined;
|
||||
}) {
|
||||
const { note } = noteContext;
|
||||
const iconClass = useNoteIcon(note);
|
||||
const colorClass = note?.getColorClass() || '';
|
||||
const workspaceTabBackgroundColorHue = getWorkspaceTabBackgroundColorHue(noteContext);
|
||||
const subContexts = noteContext.getSubContexts();
|
||||
@@ -158,46 +166,65 @@ function Tab({ noteContext, containerRef, selectTab, activeNtxId }: {
|
||||
>
|
||||
{subContexts.map(subContext => (
|
||||
<Fragment key={subContext.ntxId}>
|
||||
<header className={colorClass}>
|
||||
{subContext.note && <Icon icon={iconClass} />}
|
||||
<span className="title">{subContext.note?.title ?? t("tab_row.new_tab")}</span>
|
||||
{subContext.isMainContext() && <ActionButton
|
||||
icon="bx bx-x"
|
||||
text={t("tab_row.close_tab")}
|
||||
onClick={(e) => {
|
||||
// We are closing a tab, so we need to prevent propagation for click (activate tab).
|
||||
e.stopPropagation();
|
||||
appContext.tabManager.removeNoteContext(subContext.ntxId);
|
||||
}}
|
||||
/>}
|
||||
</header>
|
||||
<div className={clsx("tab-preview", `type-${subContext.note?.type ?? "empty"}`)}>
|
||||
<TabPreviewContent note={subContext.note} />
|
||||
</div>
|
||||
<TabHeader noteContext={subContext} colorClass={colorClass} />
|
||||
<TabPreviewContent note={subContext.note} viewScope={subContext.viewScope} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TabPreviewContent({ note }: {
|
||||
note: FNote | null
|
||||
}) {
|
||||
if (!note) {
|
||||
return <PreviewPlaceholder icon="bx bx-plus" />;
|
||||
}
|
||||
function TabHeader({ noteContext, colorClass }: { noteContext: NoteContext, colorClass: string }) {
|
||||
const iconClass = useNoteIcon(noteContext.note);
|
||||
const [ navigationTitle, setNavigationTitle ] = useState<string | null>(null);
|
||||
|
||||
if (note.type === "book") {
|
||||
return <PreviewPlaceholder icon={ICON_MAPPINGS[note.getLabelValue("viewType") ?? ""] ?? "bx bx-book"} />;
|
||||
}
|
||||
// Manage the title for read-only notes
|
||||
useEffect(() => {
|
||||
noteContext?.getNavigationTitle().then(setNavigationTitle);
|
||||
}, [noteContext]);
|
||||
|
||||
return (
|
||||
<NoteContent
|
||||
<header className={colorClass}>
|
||||
{noteContext.note && <Icon icon={iconClass} />}
|
||||
<span className="title">{navigationTitle ?? noteContext.note?.title ?? t("tab_row.new_tab")}</span>
|
||||
{noteContext.isMainContext() && <ActionButton
|
||||
icon="bx bx-x"
|
||||
text={t("tab_row.close_tab")}
|
||||
onClick={(e) => {
|
||||
// We are closing a tab, so we need to prevent propagation for click (activate tab).
|
||||
e.stopPropagation();
|
||||
appContext.tabManager.removeNoteContext(noteContext.ntxId);
|
||||
}}
|
||||
/>}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function TabPreviewContent({ note, viewScope }: {
|
||||
note: FNote | null,
|
||||
viewScope: ViewScope | undefined
|
||||
}) {
|
||||
let el: ComponentChild;
|
||||
let isPlaceholder = true;
|
||||
|
||||
if (!note) {
|
||||
el = <PreviewPlaceholder icon="bx bx-plus" />;
|
||||
} else if (note.type === "book") {
|
||||
el = <PreviewPlaceholder icon={ICON_MAPPINGS[note.getLabelValue("viewType") ?? ""] ?? "bx bx-book"} />;
|
||||
} else if (viewScope?.viewMode && viewScope.viewMode !== "default") {
|
||||
el = <PreviewPlaceholder icon={VIEW_MODE_ICON_MAPPINGS[viewScope?.viewMode ?? ""] ?? "bx bx-empty"} />;
|
||||
} else {
|
||||
el = <NoteContent
|
||||
note={note}
|
||||
highlightedTokens={undefined}
|
||||
trim
|
||||
includeArchivedNotes={false}
|
||||
/>
|
||||
/>;
|
||||
isPlaceholder = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("tab-preview", `type-${note?.type ?? "empty"}`, { "tab-preview-placeholder": isPlaceholder })}>{el}</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.code-note-switcher-modal .dropdown-menu {
|
||||
background: none !important;
|
||||
}
|
||||
@@ -1,84 +1,240 @@
|
||||
import { useContext } from "preact/hooks";
|
||||
import "./mobile_detail_menu.css";
|
||||
|
||||
import appContext, { CommandMappings } from "../../components/app_context";
|
||||
import contextMenu, { MenuItem } from "../../menus/context_menu";
|
||||
import branches from "../../services/branches";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import { createPortal, useRef, useState } from "preact/compat";
|
||||
|
||||
import FNote, { NotePathRecord } from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { getHelpUrlForNote } from "../../services/in_app_help";
|
||||
import note_create from "../../services/note_create";
|
||||
import tree from "../../services/tree";
|
||||
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||
import BasicWidget from "../basic_widget";
|
||||
import server from "../../services/server";
|
||||
import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions";
|
||||
import { getLocaleName, NoteInfoContent } from "../layout/StatusBar";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList";
|
||||
import { useNoteContext, useNoteProperty } from "../react/hooks";
|
||||
import Modal from "../react/Modal";
|
||||
import { NoteTypeCodeNoteList, useLanguageSwitcher, useMimeTypes } from "../ribbon/BasicPropertiesTab";
|
||||
import { NoteContextMenu } from "../ribbon/NoteActions";
|
||||
import NoteActionsCustom from "../ribbon/NoteActionsCustom";
|
||||
import { NotePathsWidget, useSortedNotePaths } from "../ribbon/NotePathsTab";
|
||||
import SimilarNotesTab from "../ribbon/SimilarNotesTab";
|
||||
import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector";
|
||||
|
||||
export default function MobileDetailMenu() {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const dropdownRef = useRef<BootstrapDropdown | null>(null);
|
||||
const { note, noteContext, parentComponent, ntxId, viewScope, hoistedNoteId } = useNoteContext();
|
||||
const subContexts = noteContext?.getMainContext().getSubContexts() ?? [];
|
||||
const isMainContext = noteContext?.isMainContext();
|
||||
const [ backlinksModalShown, setBacklinksModalShown ] = useState(false);
|
||||
const [ notePathsModalShown, setNotePathsModalShown ] = useState(false);
|
||||
const [ noteInfoModalShown, setNoteInfoModalShown ] = useState(false);
|
||||
const [ similarNotesModalShown, setSimilarNotesModalShown ] = useState(false);
|
||||
const [ codeNoteSwitcherModalShown, setCodeNoteSwitcherModalShown ] = useState(false);
|
||||
const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId);
|
||||
const backlinksCount = useBacklinkCount(note, viewScope?.viewMode === "default");
|
||||
|
||||
function closePane() {
|
||||
// Wait first for the context menu to be dismissed, otherwise the backdrop stays on.
|
||||
requestAnimationFrame(() => {
|
||||
parentComponent.triggerCommand("closeThisNoteSplit", { ntxId });
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
icon="bx bx-dots-vertical-rounded"
|
||||
text=""
|
||||
onClick={(e) => {
|
||||
const ntxId = (parentComponent as BasicWidget | null)?.getClosestNtxId();
|
||||
if (!ntxId) return;
|
||||
<div style={{ contain: "none" }}>
|
||||
{note ? (
|
||||
<NoteContextMenu
|
||||
dropdownRef={dropdownRef}
|
||||
note={note} noteContext={noteContext}
|
||||
itemsAtStart={<>
|
||||
<div className="form-list-row">
|
||||
<div className="form-list-col">
|
||||
<FormListItem
|
||||
icon="bx bx-link"
|
||||
onClick={() => setBacklinksModalShown(true)}
|
||||
disabled={backlinksCount === 0}
|
||||
>{t("status_bar.backlinks", { count: backlinksCount })}</FormListItem>
|
||||
</div>
|
||||
<div className="form-list-col">
|
||||
<FormListItem
|
||||
icon="bx bx-directions"
|
||||
onClick={() => setNotePathsModalShown(true)}
|
||||
disabled={(sortedNotePaths?.length ?? 0) <= 1}
|
||||
>{t("status_bar.note_paths", { count: sortedNotePaths?.length })}</FormListItem>
|
||||
</div>
|
||||
</div>
|
||||
<FormDropdownDivider />
|
||||
|
||||
const noteContext = appContext.tabManager.getNoteContextById(ntxId);
|
||||
const subContexts = noteContext.getMainContext().getSubContexts();
|
||||
const isMainContext = noteContext?.isMainContext();
|
||||
const note = noteContext.note;
|
||||
const helpUrl = getHelpUrlForNote(note);
|
||||
{noteContext && ntxId && <NoteActionsCustom note={note} noteContext={noteContext} ntxId={ntxId} />}
|
||||
<FormListItem
|
||||
onClick={() => noteContext?.notePath && note_create.createNote(noteContext.notePath)}
|
||||
icon="bx bx-plus"
|
||||
>{t("mobile_detail_menu.insert_child_note")}</FormListItem>
|
||||
{subContexts.length < 2 && <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
onClick={(e) => {
|
||||
// We have to manually manage the hide because otherwise the old note context gets activated.
|
||||
e.stopPropagation();
|
||||
dropdownRef.current?.hide();
|
||||
parentComponent.triggerCommand("openNewNoteSplit", { ntxId });
|
||||
}}
|
||||
icon="bx bx-dock-right"
|
||||
>{t("create_pane_button.create_new_split")}</FormListItem>
|
||||
</>}
|
||||
{!isMainContext && <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
icon="bx bx-x"
|
||||
onClick={closePane}
|
||||
>{t("close_pane_button.close_this_pane")}</FormListItem>
|
||||
</>}
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
itemsNearNoteSettings={<>
|
||||
{note.type === "text" && <ContentLanguageSelector note={note} />}
|
||||
{note.type === "code" && <FormListItem icon={"bx bx-code"} onClick={() => setCodeNoteSwitcherModalShown(true)}>{t("status_bar.code_note_switcher")}</FormListItem>}
|
||||
<FormListItem icon="bx bx-info-circle" onClick={() => setNoteInfoModalShown(true)}>{t("note_info_widget.title")}</FormListItem>
|
||||
<FormListItem icon="bx bx-bar-chart" onClick={() => setSimilarNotesModalShown(true)}>{t("similar_notes.title")}</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
icon="bx bx-x"
|
||||
onClick={closePane}
|
||||
text={t("close_pane_button.close_this_pane")}
|
||||
/>
|
||||
)}
|
||||
|
||||
const items: (MenuItem<keyof CommandMappings>)[] = [
|
||||
{ title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" },
|
||||
{ title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note?.noteId !== "root" },
|
||||
{ kind: "separator" },
|
||||
{ title: t("mobile_detail_menu.note_revisions"), command: "showRevisions", uiIcon: "bx bx-history" },
|
||||
{ kind: "separator" },
|
||||
helpUrl && {
|
||||
title: t("help-button.title"),
|
||||
uiIcon: "bx bx-help-circle",
|
||||
handler: () => openInAppHelpFromUrl(helpUrl)
|
||||
},
|
||||
{ kind: "separator" },
|
||||
subContexts.length < 2 && { title: t("create_pane_button.create_new_split"), command: "openNewNoteSplit", uiIcon: "bx bx-dock-right" },
|
||||
!isMainContext && { title: t("close_pane_button.close_this_pane"), command: "closeThisNoteSplit", uiIcon: "bx bx-x" }
|
||||
].filter(i => !!i) as MenuItem<keyof CommandMappings>[];
|
||||
|
||||
const lastItem = items.at(-1);
|
||||
if (lastItem && "kind" in lastItem && lastItem.kind === "separator") {
|
||||
items.pop();
|
||||
}
|
||||
|
||||
contextMenu.show<keyof CommandMappings>({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items,
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === "insertChildNote") {
|
||||
note_create.createNote(appContext.tabManager.getActiveContextNotePath() ?? undefined);
|
||||
} else if (command === "delete") {
|
||||
const notePath = appContext.tabManager.getActiveContextNotePath();
|
||||
if (!notePath) {
|
||||
throw new Error("Cannot get note path to delete.");
|
||||
}
|
||||
|
||||
const branchId = await tree.getBranchIdFromUrl(notePath);
|
||||
|
||||
if (!branchId) {
|
||||
throw new Error(t("mobile_detail_menu.error_cannot_get_branch_id", { notePath }));
|
||||
}
|
||||
|
||||
if (await branches.deleteNotes([branchId]) && parentComponent) {
|
||||
parentComponent.triggerCommand("setActiveScreen", { screen: "tree" });
|
||||
}
|
||||
} else if (command && parentComponent) {
|
||||
parentComponent.triggerCommand(command, { ntxId });
|
||||
}
|
||||
},
|
||||
forcePositionOnMobile: true
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{createPortal((
|
||||
<>
|
||||
<BacklinksModal note={note} modalShown={backlinksModalShown} setModalShown={setBacklinksModalShown} />
|
||||
<NotePathsModal note={note} modalShown={notePathsModalShown} notePath={noteContext?.notePath} sortedNotePaths={sortedNotePaths} setModalShown={setNotePathsModalShown} />
|
||||
<NoteInfoModal note={note} modalShown={noteInfoModalShown} setModalShown={setNoteInfoModalShown} />
|
||||
<SimilarNotesModal note={note} modalShown={similarNotesModalShown} setModalShown={setSimilarNotesModalShown} />
|
||||
<CodeNoteSwitcherModal note={note} modalShown={codeNoteSwitcherModalShown} setModalShown={setCodeNoteSwitcherModalShown} />
|
||||
</>
|
||||
), document.body)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ContentLanguageSelector({ note }: { note: FNote | null | undefined }) {
|
||||
const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note);
|
||||
const { activeLocale, processedLocales } = useProcessedLocales(locales, DEFAULT_LOCALE, currentNoteLanguage ?? DEFAULT_LOCALE.id);
|
||||
|
||||
return (
|
||||
<FormDropdownSubmenu
|
||||
icon="bx bx-globe"
|
||||
title={t("mobile_detail_menu.content_language_switcher", { language: getLocaleName(activeLocale ?? DEFAULT_LOCALE) })}
|
||||
>
|
||||
{processedLocales.map((locale, index) =>
|
||||
(typeof locale === "object") ? (
|
||||
<FormListItem
|
||||
key={locale.id}
|
||||
rtl={locale.rtl}
|
||||
checked={locale.id === currentNoteLanguage}
|
||||
onClick={() => setCurrentNoteLanguage(locale.id)}
|
||||
>{locale.name}</FormListItem>
|
||||
) : (
|
||||
<FormDropdownDivider key={`divider-${index}`} />
|
||||
)
|
||||
)}
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
interface WithModal {
|
||||
modalShown: boolean;
|
||||
setModalShown: (shown: boolean) => void;
|
||||
}
|
||||
|
||||
function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="backlinks-modal tn-backlinks-widget"
|
||||
size="md"
|
||||
title={t("mobile_detail_menu.backlinks")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
<ul className="backlinks-items">
|
||||
{note && <BacklinksList note={note} />}
|
||||
</ul>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function NotePathsModal({ note, modalShown, notePath, sortedNotePaths, setModalShown }: { note: FNote | null | undefined, sortedNotePaths: NotePathRecord[] | undefined, notePath: string | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="note-paths-modal"
|
||||
size="md"
|
||||
title={t("note_paths.title")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
{note && (
|
||||
<NotePathsWidget
|
||||
sortedNotePaths={sortedNotePaths}
|
||||
currentNotePath={notePath}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteInfoModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="note-info-modal"
|
||||
size="md"
|
||||
title={t("note_info_widget.title")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
{note && <NoteInfoContent note={note} noteType={note.type} />}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function SimilarNotesModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="similar-notes-modal"
|
||||
size="md"
|
||||
title={t("similar_notes.title")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
<SimilarNotesTab note={note} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeNoteSwitcherModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
const currentNoteMime = useNoteProperty(note, "mime");
|
||||
const mimeTypes = useMimeTypes();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="code-note-switcher-modal"
|
||||
size="md"
|
||||
title={t("status_bar.code_note_switcher")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
<div className="dropdown-menu static show">
|
||||
{note && <NoteTypeCodeNoteList
|
||||
currentMimeType={currentNoteMime}
|
||||
mimeTypes={mimeTypes}
|
||||
changeNoteType={(type, mime) => {
|
||||
server.put(`notes/${note.noteId}/type`, { type, mime });
|
||||
setModalShown(false);
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,3 +117,35 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile .modal.icon-switcher {
|
||||
.modal-dialog {
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: unset;
|
||||
transform: unset;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .filter-row {
|
||||
padding: 0.25em var(--bs-modal-padding) 0.5em var(--bs-modal-padding);
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-list {
|
||||
margin: auto;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
|
||||
span {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import { IconRegistry } from "@triliumnext/commons";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { t } from "i18next";
|
||||
import { CSSProperties, RefObject } from "preact";
|
||||
import { CSSProperties } from "preact";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import type React from "react";
|
||||
import { CellComponentProps, Grid } from "react-window";
|
||||
@@ -12,11 +13,15 @@ import { CellComponentProps, Grid } from "react-window";
|
||||
import FNote from "../entities/fnote";
|
||||
import attributes from "../services/attributes";
|
||||
import server from "../services/server";
|
||||
import { isDesktop, isMobile } from "../services/utils";
|
||||
import ActionButton from "./react/ActionButton";
|
||||
import Dropdown from "./react/Dropdown";
|
||||
import { FormDropdownDivider, FormListItem } from "./react/FormList";
|
||||
import FormTextBox from "./react/FormTextBox";
|
||||
import { useNoteContext, useNoteLabel, useStaticTooltip } from "./react/hooks";
|
||||
import { useNoteContext, useNoteLabel, useStaticTooltip, useWindowSize } from "./react/hooks";
|
||||
import Modal from "./react/Modal";
|
||||
|
||||
const ICON_SIZE = isMobile() ? 56 : 48;
|
||||
|
||||
interface IconToCountCache {
|
||||
iconClassToCountMap: Record<string, number>;
|
||||
@@ -37,6 +42,10 @@ export default function NoteIcon() {
|
||||
setIcon(note?.getIcon());
|
||||
}, [ note, iconClass, workspaceIconClass ]);
|
||||
|
||||
if (isMobile()) {
|
||||
return <MobileNoteIconSwitcher note={note} icon={icon} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="note-icon-widget"
|
||||
@@ -48,16 +57,47 @@ export default function NoteIcon() {
|
||||
hideToggleArrow
|
||||
disabled={viewScope?.viewMode !== "default"}
|
||||
>
|
||||
{ note && <NoteIconList note={note} dropdownRef={dropdownRef} /> }
|
||||
{ note && <NoteIconList note={note} onHide={() => dropdownRef?.current?.hide()} columnCount={12} /> }
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteIconList({ note, dropdownRef }: {
|
||||
note: FNote,
|
||||
dropdownRef: RefObject<BootstrapDropdown>;
|
||||
function MobileNoteIconSwitcher({ note, icon }: {
|
||||
note: FNote | null | undefined;
|
||||
icon: string | null | undefined;
|
||||
}) {
|
||||
const [ modalShown, setModalShown ] = useState(false);
|
||||
const { windowWidth } = useWindowSize();
|
||||
|
||||
return (note &&
|
||||
<div className="note-icon-widget">
|
||||
<ActionButton
|
||||
className="note-icon"
|
||||
icon={icon ?? "bx bx-empty"}
|
||||
text={t("note_icon.change_note_icon")}
|
||||
onClick={() => setModalShown(true)}
|
||||
/>
|
||||
|
||||
{createPortal((
|
||||
<Modal
|
||||
title={t("note_icon.change_note_icon")}
|
||||
size="xl"
|
||||
show={modalShown} onHidden={() => setModalShown(false)}
|
||||
className="icon-switcher note-icon-widget"
|
||||
scrollable
|
||||
>
|
||||
<NoteIconList note={note} onHide={() => setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} />
|
||||
</Modal>
|
||||
), document.body)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteIconList({ note, onHide, columnCount }: {
|
||||
note: FNote;
|
||||
onHide: () => void;
|
||||
columnCount: number;
|
||||
}) {
|
||||
const searchBoxRef = useRef<HTMLInputElement>(null);
|
||||
const iconListRef = useRef<HTMLDivElement>(null);
|
||||
const [ search, setSearch ] = useState<string>();
|
||||
const [ filterByPrefix, setFilterByPrefix ] = useState<string | null>(null);
|
||||
@@ -73,53 +113,22 @@ function NoteIconList({ note, dropdownRef }: {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="filter-row">
|
||||
<span>{t("note_icon.search")}</span>
|
||||
<FormTextBox
|
||||
inputRef={searchBoxRef}
|
||||
type="text"
|
||||
name="icon-search"
|
||||
placeholder={ filterByPrefix
|
||||
? t("note_icon.search_placeholder_filtered", {
|
||||
number: filteredIcons.length ?? 0,
|
||||
name: glob.iconRegistry.sources.find(s => s.prefix === filterByPrefix)?.name ?? ""
|
||||
})
|
||||
: t("note_icon.search_placeholder", { number: filteredIcons.length ?? 0, count: glob.iconRegistry.sources.length })}
|
||||
currentValue={search} onChange={setSearch}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{getIconLabels(note).length > 0 && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ActionButton
|
||||
icon="bx bx-reset"
|
||||
text={t("note_icon.reset-default")}
|
||||
onClick={() => {
|
||||
if (!note) return;
|
||||
for (const label of getIconLabels(note)) {
|
||||
attributes.removeAttributeById(note.noteId, label.attributeId);
|
||||
}
|
||||
dropdownRef?.current?.hide();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{glob.iconRegistry.sources.length > 0 && <Dropdown
|
||||
buttonClassName="bx bx-filter-alt"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
title={t("note_icon.filter")}
|
||||
>
|
||||
<IconFilterContent filterByPrefix={filterByPrefix} setFilterByPrefix={setFilterByPrefix} />
|
||||
</Dropdown>}
|
||||
</div>
|
||||
<FilterRow
|
||||
note={note}
|
||||
filterByPrefix={filterByPrefix}
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
setFilterByPrefix={setFilterByPrefix}
|
||||
filteredIcons={filteredIcons}
|
||||
onHide={onHide}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="icon-list"
|
||||
ref={iconListRef}
|
||||
style={{
|
||||
width: (columnCount * ICON_SIZE + 10),
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// Make sure we are not clicking on something else than a button.
|
||||
const clickedTarget = e.target as HTMLElement;
|
||||
@@ -130,18 +139,19 @@ function NoteIconList({ note, dropdownRef }: {
|
||||
const attributeToSet = note.hasOwnedLabel("workspace") ? "workspaceIconClass" : "iconClass";
|
||||
attributes.setLabel(note.noteId, attributeToSet, iconClass);
|
||||
}
|
||||
dropdownRef?.current?.hide();
|
||||
onHide();
|
||||
}}
|
||||
>
|
||||
{filteredIcons.length ? (
|
||||
<Grid
|
||||
columnCount={12}
|
||||
columnWidth={48}
|
||||
rowCount={Math.ceil(filteredIcons.length / 12)}
|
||||
rowHeight={48}
|
||||
columnCount={columnCount}
|
||||
columnWidth={ICON_SIZE}
|
||||
rowCount={Math.ceil(filteredIcons.length / columnCount)}
|
||||
rowHeight={ICON_SIZE}
|
||||
cellComponent={IconItemCell}
|
||||
cellProps={{
|
||||
filteredIcons
|
||||
filteredIcons,
|
||||
columnCount
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@@ -152,10 +162,95 @@ function NoteIconList({ note, dropdownRef }: {
|
||||
);
|
||||
}
|
||||
|
||||
function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellComponentProps<{
|
||||
function FilterRow({ note, filterByPrefix, search, setSearch, setFilterByPrefix, filteredIcons, onHide }: {
|
||||
note: FNote;
|
||||
filterByPrefix: string | null;
|
||||
search: string | undefined;
|
||||
setSearch: (value: string | undefined) => void;
|
||||
setFilterByPrefix: (value: string | null) => void;
|
||||
filteredIcons: IconWithName[];
|
||||
onHide: () => void;
|
||||
}) {
|
||||
const searchBoxRef = useRef<HTMLInputElement>(null);
|
||||
const hasCustomIcon = getIconLabels(note).length > 0;
|
||||
|
||||
function resetToDefaultIcon() {
|
||||
if (!note) return;
|
||||
for (const label of getIconLabels(note)) {
|
||||
attributes.removeAttributeById(note.noteId, label.attributeId);
|
||||
}
|
||||
onHide();
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="filter-row">
|
||||
<span>{t("note_icon.search")}</span>
|
||||
<FormTextBox
|
||||
inputRef={searchBoxRef}
|
||||
type="text"
|
||||
name="icon-search"
|
||||
placeholder={ filterByPrefix
|
||||
? t("note_icon.search_placeholder_filtered", {
|
||||
number: filteredIcons.length ?? 0,
|
||||
name: glob.iconRegistry.sources.find(s => s.prefix === filterByPrefix)?.name ?? ""
|
||||
})
|
||||
: t("note_icon.search_placeholder", { number: filteredIcons.length ?? 0, count: glob.iconRegistry.sources.length })}
|
||||
currentValue={search} onChange={setSearch}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{isDesktop()
|
||||
? <>
|
||||
{hasCustomIcon && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ActionButton
|
||||
icon="bx bx-reset"
|
||||
text={t("note_icon.reset-default")}
|
||||
onClick={resetToDefaultIcon}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{<Dropdown
|
||||
buttonClassName="bx bx-filter-alt"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
title={t("note_icon.filter")}
|
||||
>
|
||||
<IconFilterContent filterByPrefix={filterByPrefix} setFilterByPrefix={setFilterByPrefix} />
|
||||
</Dropdown>}
|
||||
</> : (
|
||||
<Dropdown
|
||||
buttonClassName="bx bx-dots-vertical-rounded"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
dropdownContainerClassName="mobile-bottom-menu"
|
||||
>
|
||||
{hasCustomIcon && <>
|
||||
<FormListItem
|
||||
icon="bx bx-reset"
|
||||
onClick={resetToDefaultIcon}
|
||||
disabled={!hasCustomIcon}
|
||||
>{t("note_icon.reset-default")}</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
|
||||
<IconFilterContent filterByPrefix={filterByPrefix} setFilterByPrefix={setFilterByPrefix} />
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function IconItemCell({ rowIndex, columnIndex, style, filteredIcons, columnCount }: CellComponentProps<{
|
||||
filteredIcons: IconWithName[];
|
||||
columnCount: number;
|
||||
}>) {
|
||||
const iconIndex = rowIndex * 12 + columnIndex;
|
||||
const iconIndex = rowIndex * columnCount + columnIndex;
|
||||
const iconData = filteredIcons[iconIndex] as IconWithName | undefined;
|
||||
if (!iconData) return <></> as React.ReactElement;
|
||||
|
||||
@@ -184,7 +279,7 @@ function IconFilterContent({ filterByPrefix, setFilterByPrefix }: {
|
||||
checked={filterByPrefix === "bx"}
|
||||
onClick={() => setFilterByPrefix("bx")}
|
||||
>{t("note_icon.filter-default")}</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
{glob.iconRegistry.sources.length > 1 && <FormDropdownDivider />}
|
||||
|
||||
{glob.iconRegistry.sources.map(({ prefix, name, icon }) => (
|
||||
prefix !== "bx" && <FormListItem
|
||||
|
||||
@@ -109,4 +109,29 @@ body.experimental-feature-new-layout {
|
||||
--input-focus-color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile .title-row {
|
||||
.icon-action:not(.note-icon) {
|
||||
--icon-button-size: 45px;
|
||||
--icon-button-icon-ratio: 0.5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-actions {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.note-badges {
|
||||
margin-inline: 0.5em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-icon-widget {
|
||||
margin-inline: 0.5em;
|
||||
|
||||
.note-icon {
|
||||
--icon-button-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#left-pane .tree-wrapper {
|
||||
.tree-wrapper {
|
||||
.note-indicator-icon.subtree-hidden-badge {
|
||||
font-family: inherit !important;
|
||||
margin-inline: 0.5em;
|
||||
|
||||
@@ -17,21 +17,34 @@
|
||||
.collapsible-body {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&.fully-expanded {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsible-inner-body {
|
||||
padding-top: 0.5em;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.collapsible-title .arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.collapsible-inner-body {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-transition {
|
||||
.collapsible-body {
|
||||
transition: height 250ms ease-in;
|
||||
}
|
||||
|
||||
.collapsible-inner-body {
|
||||
transition: opacity 250ms ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
|
||||
const { height } = useElementSize(innerRef) ?? {};
|
||||
const contentId = useUniqueName();
|
||||
const [ transitionEnabled, setTransitionEnabled ] = useState(false);
|
||||
const [ fullyExpanded, setFullyExpanded ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -35,6 +36,21 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded) {
|
||||
if (transitionEnabled) {
|
||||
const timeout = setTimeout(() => {
|
||||
setFullyExpanded(true);
|
||||
}, 250);
|
||||
return () => clearTimeout(timeout);
|
||||
} else {
|
||||
setFullyExpanded(true);
|
||||
}
|
||||
} else {
|
||||
setFullyExpanded(false);
|
||||
}
|
||||
}, [expanded, transitionEnabled])
|
||||
|
||||
return (
|
||||
<div className={clsx("collapsible", className, {
|
||||
expanded,
|
||||
@@ -53,7 +69,7 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
|
||||
<div
|
||||
id={contentId}
|
||||
ref={bodyRef}
|
||||
className="collapsible-body"
|
||||
className={clsx("collapsible-body", {"fully-expanded": fullyExpanded})}
|
||||
style={{ height: expanded ? height : "0" }}
|
||||
aria-hidden={!expanded}
|
||||
>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ComponentChildren, HTMLAttributes } from "preact";
|
||||
import { CSSProperties, HTMLProps } from "preact/compat";
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { isMobile } from "../../services/utils";
|
||||
import { useTooltip, useUniqueName } from "./hooks";
|
||||
|
||||
type DataAttributes = {
|
||||
@@ -32,9 +33,10 @@ export interface DropdownProps extends Pick<HTMLProps<HTMLDivElement>, "id" | "c
|
||||
dropdownRef?: MutableRef<BootstrapDropdown | null>;
|
||||
titlePosition?: "top" | "right" | "bottom" | "left";
|
||||
titleOptions?: Partial<Tooltip.Options>;
|
||||
mobileBackdrop?: boolean;
|
||||
}
|
||||
|
||||
export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions }: DropdownProps) {
|
||||
export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions, mobileBackdrop }: DropdownProps) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
const dropdownContainerRef = useRef<HTMLUListElement | null>(null);
|
||||
@@ -74,12 +76,18 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
|
||||
setShown(true);
|
||||
externalOnShown?.();
|
||||
hideTooltip();
|
||||
}, [ hideTooltip ]);
|
||||
if (mobileBackdrop && isMobile()) {
|
||||
document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover");
|
||||
}
|
||||
}, [ hideTooltip, mobileBackdrop ]);
|
||||
|
||||
const onHidden = useCallback(() => {
|
||||
setShown(false);
|
||||
externalOnHidden?.();
|
||||
}, []);
|
||||
if (mobileBackdrop && isMobile()) {
|
||||
document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover");
|
||||
}
|
||||
}, [ mobileBackdrop ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import ActionButton, { ActionButtonProps } from "./ActionButton";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
import { FormListItem, FormListItemOpts } from "./FormList";
|
||||
|
||||
interface FormFileUploadProps {
|
||||
export interface FormFileUploadProps {
|
||||
name?: string;
|
||||
onChange: (files: FileList | null) => void;
|
||||
multiple?: boolean;
|
||||
@@ -75,3 +76,25 @@ export function FormFileUploadActionButton({ onChange, ...buttonProps }: Omit<Ac
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link FormFileUploadButton}, but uses an {@link FormListItem} instead of a normal {@link Button}.
|
||||
* @param param the change listener for the file upload and the properties for the button.
|
||||
*/
|
||||
export function FormFileUploadFormListItem({ onChange, children, ...buttonProps }: Omit<FormListItemOpts, "onClick"> & Pick<FormFileUploadProps, "onChange">) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormListItem
|
||||
{...buttonProps}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
>{children}</FormListItem>
|
||||
<FormFileUpload
|
||||
inputRef={inputRef}
|
||||
hidden
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,3 +27,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-list-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1em;
|
||||
|
||||
.form-list-col {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export interface FormListBadge {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface FormListItemOpts {
|
||||
export interface FormListItemOpts {
|
||||
children: ComponentChildren;
|
||||
icon?: string;
|
||||
value?: string;
|
||||
|
||||
12
apps/client/src/widgets/react/ResponsiveContainer.tsx
Normal file
12
apps/client/src/widgets/react/ResponsiveContainer.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ComponentChildren } from "preact";
|
||||
|
||||
import { isMobile } from "../../services/utils";
|
||||
|
||||
interface ResponsiveContainerProps {
|
||||
mobile?: ComponentChildren;
|
||||
desktop?: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function ResponsiveContainer({ mobile, desktop }: ResponsiveContainerProps) {
|
||||
return (isMobile() ? mobile : desktop);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MimeType, NoteType, ToggleInParentResponse } from "@triliumnext/commons";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
||||
|
||||
@@ -117,19 +116,18 @@ export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note
|
||||
onClick={() => changeNoteType(type, mime)}
|
||||
>{title}</FormListItem>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
checked={checked}
|
||||
disabled
|
||||
>
|
||||
<strong>{title}</strong>
|
||||
</FormListItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
checked={checked}
|
||||
disabled
|
||||
>
|
||||
<strong>{title}</strong>
|
||||
</FormListItem>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
|
||||
{!noCodeNotes && <NoteTypeCodeNoteList mimeTypes={mimeTypes} changeNoteType={changeNoteType} setModalShown={setModalShown} />}
|
||||
@@ -141,7 +139,7 @@ export function NoteTypeCodeNoteList({ currentMimeType, mimeTypes, changeNoteTyp
|
||||
currentMimeType?: string;
|
||||
mimeTypes: MimeType[];
|
||||
changeNoteType(type: NoteType, mime: string): void;
|
||||
setModalShown(shown: boolean): void;
|
||||
setModalShown?(shown: boolean): void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -155,8 +153,10 @@ export function NoteTypeCodeNoteList({ currentMimeType, mimeTypes, changeNoteTyp
|
||||
</FormListItem>
|
||||
))}
|
||||
|
||||
<FormDropdownDivider />
|
||||
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
|
||||
{setModalShown && <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
|
||||
</>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ function ProtectedNoteSwitch({ note }: { note?: FNote | null }) {
|
||||
onChange={(shouldProtect) => note && protected_session.protectNote(note.noteId, shouldProtect, false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function EditabilitySelect({ note }: { note?: FNote | null }) {
|
||||
@@ -417,9 +417,9 @@ function findTypeTitle(type?: NoteType, mime?: string | null) {
|
||||
const found = mimeTypes.find((mt) => mt.mime === mime);
|
||||
|
||||
return found ? found.title : mime;
|
||||
} else {
|
||||
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
|
||||
|
||||
return noteType ? noteType.title : type;
|
||||
}
|
||||
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
|
||||
|
||||
return noteType ? noteType.title : type;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ConvertToAttachmentResponse } from "@triliumnext/commons";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import { RefObject } from "preact";
|
||||
import { ComponentChildren, RefObject } from "preact";
|
||||
import { useContext, useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import appContext, { CommandNames } from "../../components/app_context";
|
||||
@@ -22,7 +22,7 @@ import MovePaneButton from "../buttons/move_pane_button";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListHeader, FormListItem, FormListToggleableItem } from "../react/FormList";
|
||||
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useSyncedRef, useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { NoteTypeDropdownContent, useNoteBookmarkState, useShareState } from "./BasicPropertiesTab";
|
||||
import NoteActionsCustom from "./NoteActionsCustom";
|
||||
@@ -63,8 +63,14 @@ function RevisionsButton({ note }: { note: FNote }) {
|
||||
|
||||
type ItemToFocus = "basic-properties";
|
||||
|
||||
function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) {
|
||||
const dropdownRef = useRef<BootstrapDropdown>(null);
|
||||
export function NoteContextMenu({ note, noteContext, itemsAtStart, itemsNearNoteSettings, dropdownRef: externalDropdownRef }: {
|
||||
note: FNote,
|
||||
noteContext?: NoteContext,
|
||||
itemsAtStart?: ComponentChildren;
|
||||
itemsNearNoteSettings?: ComponentChildren;
|
||||
dropdownRef?: RefObject<BootstrapDropdown>;
|
||||
}) {
|
||||
const dropdownRef = useSyncedRef<BootstrapDropdown>(externalDropdownRef, null);
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const noteType = useNoteProperty(note, "type") ?? "";
|
||||
const [viewType] = useNoteLabel(note, "viewType");
|
||||
@@ -99,12 +105,15 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
||||
dropdownRef={dropdownRef}
|
||||
buttonClassName={ isNewLayout ? "bx bx-dots-horizontal-rounded" : "bx bx-dots-vertical-rounded" }
|
||||
className="note-actions"
|
||||
dropdownContainerClassName="mobile-bottom-menu"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
onHidden={() => itemToFocusRef.current = null }
|
||||
mobileBackdrop
|
||||
>
|
||||
{itemsAtStart}
|
||||
|
||||
{isReadOnly && <>
|
||||
<CommandItem icon="bx bx-pencil" text={t("read-only-info.edit-note")}
|
||||
@@ -123,6 +132,8 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
|
||||
{itemsNearNoteSettings}
|
||||
|
||||
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
|
||||
disabled={isInOptionsOrHelp || note.type === "search"}
|
||||
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />
|
||||
@@ -277,7 +288,7 @@ function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?:
|
||||
);
|
||||
}
|
||||
|
||||
function CommandItem({ icon, text, title, command, disabled }: { icon: string, text: string, title?: string, command: CommandNames | (() => void), disabled?: boolean, destructive?: boolean }) {
|
||||
export function CommandItem({ icon, text, title, command, disabled }: { icon: string, text: string, title?: string, command: CommandNames | (() => void), disabled?: boolean, destructive?: boolean }) {
|
||||
return <FormListItem
|
||||
icon={icon}
|
||||
title={title}
|
||||
|
||||
3
apps/client/src/widgets/ribbon/NoteActionsCustom.css
Normal file
3
apps/client/src/widgets/ribbon/NoteActionsCustom.css
Normal file
@@ -0,0 +1,3 @@
|
||||
body.mobile .note-actions-custom:not(:empty) {
|
||||
margin-bottom: calc(var(--bs-dropdown-divider-margin-y) * 2);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./NoteActionsCustom.css";
|
||||
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -7,11 +9,12 @@ import FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { getHelpUrlForNote } from "../../services/in_app_help";
|
||||
import { downloadFileNote, openNoteExternally } from "../../services/open";
|
||||
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||
import { isMobile, openInAppHelpFromUrl } from "../../services/utils";
|
||||
import { ViewTypeOptions } from "../collections/interface";
|
||||
import { buildSaveSqlToNoteHandler } from "../FloatingButtonsDefinitions";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import { FormFileUploadActionButton } from "../react/FormFileUpload";
|
||||
import ActionButton, { ActionButtonProps } from "../react/ActionButton";
|
||||
import { FormFileUploadActionButton, FormFileUploadFormListItem, FormFileUploadProps } from "../react/FormFileUpload";
|
||||
import { FormListItem } from "../react/FormList";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab";
|
||||
@@ -32,6 +35,8 @@ interface NoteActionsCustomInnerProps extends NoteActionsCustomProps {
|
||||
viewType: ViewTypeOptions | null | undefined;
|
||||
}
|
||||
|
||||
const cachedIsMobile = isMobile();
|
||||
|
||||
/**
|
||||
* Part of {@link NoteActions} on the new layout, but are rendered with a slight spacing
|
||||
* from the rest of the note items and the buttons differ based on the note type.
|
||||
@@ -115,7 +120,7 @@ function UploadNewRevisionButton({ note, onChange }: NoteActionsCustomInnerProps
|
||||
onChange: (files: FileList | null) => void;
|
||||
}) {
|
||||
return (
|
||||
<FormFileUploadActionButton
|
||||
<NoteActionWithFileUpload
|
||||
icon="bx bx-folder-open"
|
||||
text={t("image_properties.upload_new_revision")}
|
||||
disabled={!note.isContentAvailable()}
|
||||
@@ -125,8 +130,8 @@ function UploadNewRevisionButton({ note, onChange }: NoteActionsCustomInnerProps
|
||||
}
|
||||
|
||||
function OpenExternallyButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
return (
|
||||
<ActionButton
|
||||
return (!cachedIsMobile &&
|
||||
<NoteAction
|
||||
icon="bx bx-link-external"
|
||||
text={t("file_properties.open")}
|
||||
disabled={note.isProtected}
|
||||
@@ -137,7 +142,7 @@ function OpenExternallyButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
|
||||
function DownloadFileButton({ note, parentComponent, ntxId }: NoteActionsCustomInnerProps) {
|
||||
return (
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
icon="bx bx-download"
|
||||
text={t("file_properties.download")}
|
||||
disabled={!note.isContentAvailable()}
|
||||
@@ -149,7 +154,7 @@ function DownloadFileButton({ note, parentComponent, ntxId }: NoteActionsCustomI
|
||||
//#region Floating buttons
|
||||
function CopyReferenceToClipboardButton({ ntxId, noteType, parentComponent }: NoteActionsCustomInnerProps) {
|
||||
return (["mermaid", "canvas", "mindMap", "image"].includes(noteType) &&
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
text={t("image_properties.copy_reference_to_clipboard")}
|
||||
icon="bx bx-copy"
|
||||
onClick={() => parentComponent?.triggerEvent("copyImageReferenceToClipboard", { ntxId })}
|
||||
@@ -161,7 +166,7 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not
|
||||
const isEnabled = (note.noteId === "_backendLog" || noteType === "render") && isDefaultViewMode;
|
||||
|
||||
return (isEnabled &&
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
text={t("backend_log.refresh")}
|
||||
icon="bx bx-refresh"
|
||||
onClick={() => parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })}
|
||||
@@ -170,11 +175,11 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not
|
||||
}
|
||||
|
||||
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: NoteActionsCustomInnerProps) {
|
||||
const isShown = note.type === "mermaid" && note.isContentAvailable() && isDefaultViewMode;
|
||||
const isShown = note.type === "mermaid" && !cachedIsMobile && note.isContentAvailable() && isDefaultViewMode;
|
||||
const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
|
||||
const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal";
|
||||
|
||||
return isShown && <ActionButton
|
||||
return isShown && <NoteAction
|
||||
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)}
|
||||
@@ -188,7 +193,7 @@ export function ToggleReadOnlyButton({ note, isDefaultViewMode }: NoteActionsCus
|
||||
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || isSavedSqlite)
|
||||
&& note.isContentAvailable() && isDefaultViewMode;
|
||||
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
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)}
|
||||
@@ -197,7 +202,7 @@ export function ToggleReadOnlyButton({ note, isDefaultViewMode }: NoteActionsCus
|
||||
|
||||
function RunActiveNoteButton({ noteMime }: NoteActionsCustomInnerProps) {
|
||||
const isEnabled = noteMime.startsWith("application/javascript") || noteMime === "text/x-sqlite;schema=trilium";
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
icon="bx bx-play"
|
||||
text={t("code_buttons.execute_button_title")}
|
||||
triggerCommand="runActiveNote"
|
||||
@@ -218,7 +223,7 @@ function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
}
|
||||
});
|
||||
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
icon="bx bx-save"
|
||||
text={t("code_buttons.save_to_note_button_title")}
|
||||
onClick={buildSaveSqlToNoteHandler(note)}
|
||||
@@ -227,7 +232,7 @@ function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
|
||||
function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) {
|
||||
const isEnabled = noteMime.startsWith("application/javascript;env=");
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
icon="bx bx-help-circle"
|
||||
text={t("code_buttons.trilium_api_docs_button_title")}
|
||||
onClick={() => openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")}
|
||||
@@ -239,7 +244,7 @@ function InAppHelpButton({ note }: NoteActionsCustomInnerProps) {
|
||||
const isEnabled = !!helpUrl;
|
||||
|
||||
return isEnabled && (
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
icon="bx bx-help-circle"
|
||||
text={t("help-button.title")}
|
||||
onClick={() => helpUrl && openInAppHelpFromUrl(helpUrl)}
|
||||
@@ -249,7 +254,7 @@ function InAppHelpButton({ note }: NoteActionsCustomInnerProps) {
|
||||
|
||||
function AddChildButton({ parentComponent, noteType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) {
|
||||
if (noteType === "relationMap") {
|
||||
return <ActionButton
|
||||
return <NoteAction
|
||||
icon="bx bx-folder-plus"
|
||||
text={t("relation_map_buttons.create_child_note_title")}
|
||||
onClick={() => parentComponent.triggerEvent("relationMapCreateChildNote", { ntxId })}
|
||||
@@ -258,3 +263,19 @@ function AddChildButton({ parentComponent, noteType, ntxId, isReadOnly }: NoteAc
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
function NoteAction({ text, ...props }: Pick<ActionButtonProps, "text" | "icon" | "disabled" | "triggerCommand"> & {
|
||||
onClick?: ((e: MouseEvent) => void) | undefined;
|
||||
}) {
|
||||
return (cachedIsMobile
|
||||
? <FormListItem {...props}>{text}</FormListItem>
|
||||
: <ActionButton text={text} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function NoteActionWithFileUpload({ text, ...props }: Pick<ActionButtonProps, "text" | "icon" | "disabled" | "triggerCommand"> & Pick<FormFileUploadProps, "onChange">) {
|
||||
return (cachedIsMobile
|
||||
? <FormFileUploadFormListItem {...props}>{text}</FormFileUploadFormListItem>
|
||||
: <FormFileUploadActionButton text={text} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
63
apps/client/src/widgets/ribbon/NotePathsTab.css
Normal file
63
apps/client/src/widgets/ribbon/NotePathsTab.css
Normal file
@@ -0,0 +1,63 @@
|
||||
body.experimental-feature-new-layout .note-paths-widget {
|
||||
.note-path-intro {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.note-path-list {
|
||||
margin: 12px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
/* Note path card */
|
||||
li {
|
||||
--border-radius: 6px;
|
||||
|
||||
position: relative;
|
||||
background: var(--card-background-color);
|
||||
padding: 8px 20px 8px 25px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Current path arrow */
|
||||
&.path-current::before {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
content: "\ee8f";
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
bottom: 0;
|
||||
font-family: "boxicons";
|
||||
font-size: .75em;
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Note path segment */
|
||||
a {
|
||||
margin-inline: 2px;
|
||||
padding-inline: 2px;
|
||||
color: currentColor;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
|
||||
/* The last segment of the note path */
|
||||
&.basename {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
import "./NotePathsTab.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||
|
||||
import FNote, { NotePathRecord } from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { NOTE_PATH_TITLE_SEPARATOR } from "../../services/tree";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { joinElements } from "../react/react_utils";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
import LinkButton from "../react/LinkButton";
|
||||
import clsx from "clsx";
|
||||
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { joinElements, ParentComponent } from "../react/react_utils";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
|
||||
export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabContext) {
|
||||
const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId);
|
||||
@@ -20,6 +21,7 @@ export function NotePathsWidget({ sortedNotePaths, currentNotePath }: {
|
||||
sortedNotePaths: NotePathRecord[] | undefined;
|
||||
currentNotePath?: string | null | undefined;
|
||||
}) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
return (
|
||||
<div class="note-paths-widget">
|
||||
<>
|
||||
@@ -39,7 +41,7 @@ export function NotePathsWidget({ sortedNotePaths, currentNotePath }: {
|
||||
|
||||
<LinkButton
|
||||
text={t("note_paths.clone_button")}
|
||||
triggerCommand="cloneNoteIdsTo"
|
||||
onClick={() => parentComponent?.triggerCommand("cloneNoteIdsTo")}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
@@ -112,9 +114,9 @@ function NotePath({ currentNotePath, notePathRecord }: { currentNotePath?: strin
|
||||
<li class={classes}>
|
||||
{joinElements(fullNotePaths.map((notePath, index, arr) => (
|
||||
<NoteLink key={notePath}
|
||||
className={clsx({"basename": (index === arr.length - 1)})}
|
||||
notePath={notePath}
|
||||
noPreview />
|
||||
className={clsx({"basename": (index === arr.length - 1)})}
|
||||
notePath={notePath}
|
||||
noPreview />
|
||||
)), NOTE_PATH_TITLE_SEPARATOR)}
|
||||
|
||||
{icons.map(({ icon, title }) => (
|
||||
|
||||
@@ -2,6 +2,7 @@ import "./SearchDefinitionTab.css";
|
||||
|
||||
import { SaveSearchNoteResponse } from "@triliumnext/commons";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
@@ -15,12 +16,13 @@ import tree from "../../services/tree";
|
||||
import { getErrorMessage } from "../../services/utils";
|
||||
import ws from "../../services/ws";
|
||||
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
|
||||
import Button from "../react/Button";
|
||||
import Button, { SplitButton } from "../react/Button";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormListHeader, FormListItem } from "../react/FormList";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import ResponsiveContainer from "../react/ResponsiveContainer";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions";
|
||||
|
||||
@@ -84,15 +86,32 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
|
||||
<tr>
|
||||
<td className="title-column">{t("search_definition.add_search_option")}</td>
|
||||
<td colSpan={2} className="add-search-option">
|
||||
{searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
|
||||
<Button
|
||||
size="small"
|
||||
icon={icon}
|
||||
text={label}
|
||||
title={tooltip}
|
||||
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
|
||||
/>
|
||||
))}
|
||||
<ResponsiveContainer
|
||||
desktop={searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
|
||||
<Button
|
||||
key={`${attributeType}-${attributeName}`}
|
||||
size="small" icon={icon} text={label} title={tooltip}
|
||||
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
|
||||
/>
|
||||
))}
|
||||
mobile={
|
||||
<Dropdown
|
||||
buttonClassName="action-add-toggle btn btn-sm"
|
||||
text={<><Icon icon="bx bx-plus" />{" "}{t("search_definition.option")}</>}
|
||||
dropdownContainerClassName="mobile-bottom-menu" mobileBackdrop
|
||||
noSelectButtonStyle
|
||||
>
|
||||
{searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
|
||||
<FormListItem
|
||||
key={`${attributeType}-${attributeName}`}
|
||||
icon={icon}
|
||||
description={tooltip}
|
||||
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
|
||||
>{label}</FormListItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
|
||||
<AddBulkActionButton note={note} />
|
||||
</td>
|
||||
@@ -113,48 +132,7 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
|
||||
})}
|
||||
</tbody>
|
||||
<BulkActionsList note={note} />
|
||||
<tbody className="search-actions">
|
||||
<tr>
|
||||
<td colSpan={3}>
|
||||
<div className="search-actions-container">
|
||||
<Button
|
||||
icon="bx bx-search"
|
||||
text={t("search_definition.search_button")}
|
||||
keyboardShortcut="Enter"
|
||||
onClick={refreshResults}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="bx bxs-zap"
|
||||
text={t("search_definition.search_execute")}
|
||||
onClick={async () => {
|
||||
await server.post(`search-and-execute-note/${note.noteId}`);
|
||||
refreshResults();
|
||||
toast.showMessage(t("search_definition.actions_executed"), 3000);
|
||||
}}
|
||||
/>
|
||||
|
||||
{note.isHiddenCompletely() && <Button
|
||||
icon="bx bx-save"
|
||||
text={t("search_definition.save_to_note")}
|
||||
onClick={async () => {
|
||||
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: note.noteId });
|
||||
if (!notePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
|
||||
// Note the {{- notePathTitle}} in json file is not typo, it's unescaping
|
||||
// See https://www.i18next.com/translation-function/interpolation#unescape
|
||||
toast.showMessage(t("search_definition.search_note_saved", { notePathTitle: await tree.getNotePathTitle(notePath) }));
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<SearchButtonBar note={note} refreshResults={refreshResults} />
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
@@ -162,6 +140,56 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
|
||||
);
|
||||
}
|
||||
|
||||
function SearchButtonBar({ note, refreshResults }: {
|
||||
note: FNote;
|
||||
refreshResults(): void;
|
||||
}) {
|
||||
async function searchAndExecuteActions() {
|
||||
await server.post(`search-and-execute-note/${note.noteId}`);
|
||||
refreshResults();
|
||||
toast.showMessage(t("search_definition.actions_executed"), 3000);
|
||||
}
|
||||
|
||||
async function saveSearchNote() {
|
||||
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: note.noteId });
|
||||
if (!notePath) return;
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
|
||||
// Note the {{- notePathTitle}} in json file is not typo, it's unescaping
|
||||
// See https://www.i18next.com/translation-function/interpolation#unescape
|
||||
toast.showMessage(t("search_definition.search_note_saved", { notePathTitle: await tree.getNotePathTitle(notePath) }));
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody className="search-actions">
|
||||
<tr>
|
||||
<td colSpan={3}>
|
||||
<ResponsiveContainer
|
||||
desktop={
|
||||
<div className="search-actions-container">
|
||||
<Button icon="bx bx-search" text={t("search_definition.search_button")} keyboardShortcut="Enter" onClick={refreshResults} />
|
||||
<Button icon="bx bxs-zap" text={t("search_definition.search_execute")} onClick={searchAndExecuteActions} />
|
||||
{note.isHiddenCompletely() && <Button icon="bx bx-save" text={t("search_definition.save_to_note")} onClick={saveSearchNote} />}
|
||||
</div>
|
||||
}
|
||||
mobile={
|
||||
<SplitButton
|
||||
text={t("search_definition.search_button")} icon="bx bx-search"
|
||||
onClick={refreshResults}
|
||||
>
|
||||
<FormListItem icon="bx bxs-zap" onClick={searchAndExecuteActions}>{t("search_definition.search_execute")}</FormListItem>
|
||||
{note.isHiddenCompletely() && <FormListItem icon="bx bx-save" onClick={saveSearchNote}>{t("search_definition.save_to_note")}</FormListItem>}
|
||||
</SplitButton>
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
||||
function BulkActionsList({ note }: { note: FNote }) {
|
||||
const [ bulkActions, setBulkActions ] = useState<RenameNoteBulkAction[]>();
|
||||
|
||||
@@ -194,15 +222,18 @@ function AddBulkActionButton({ note }: { note: FNote }) {
|
||||
buttonClassName="action-add-toggle btn btn-sm"
|
||||
text={<><Icon icon="bx bxs-zap" />{" "}{t("search_definition.action")}</>}
|
||||
noSelectButtonStyle
|
||||
dropdownContainerClassName="mobile-bottom-menu" mobileBackdrop
|
||||
>
|
||||
{ACTION_GROUPS.map(({ actions, title }) => (
|
||||
<>
|
||||
{ACTION_GROUPS.map(({ actions, title }, index) => (
|
||||
<Fragment key={index}>
|
||||
<FormListHeader text={title} />
|
||||
|
||||
{actions.map(({ actionName, actionTitle }) => (
|
||||
<FormListItem onClick={() => bulk_action.addAction(note.noteId, actionName)}>{actionTitle}</FormListItem>
|
||||
))}
|
||||
</>
|
||||
<div>
|
||||
{actions.map(({ actionName, actionTitle }) => (
|
||||
<FormListItem key={actionName} onClick={() => bulk_action.addAction(note.noteId, actionName)}>{actionTitle}</FormListItem>
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
|
||||
@media (max-width: 991px) {
|
||||
margin-block: 1em;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
@@ -42,6 +48,12 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
@media (max-width: 991px) {
|
||||
gap: 0.5em;
|
||||
flex-wrap: wrap;
|
||||
.attachment-title { flex-grow: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-details {
|
||||
@@ -127,10 +139,6 @@
|
||||
|
||||
/* #region Attachment actions */
|
||||
|
||||
.attachment-actions .dropdown-menu {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
.attachment-actions .dropdown-item .tn-icon {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
|
||||
@@ -239,6 +239,8 @@ function AttachmentActions({ attachment, copyAttachmentLinkToClipboard }: { atta
|
||||
text={<Icon icon="bx bx-dots-vertical-rounded" />}
|
||||
buttonClassName="icon-action-always-border"
|
||||
iconAction
|
||||
dropdownContainerClassName="mobile-bottom-menu"
|
||||
mobileBackdrop
|
||||
>
|
||||
<FormListItem
|
||||
icon="bx bx-file-find"
|
||||
|
||||
@@ -388,10 +388,10 @@ function Performance() {
|
||||
currentValue={shadowsEnabled} onChange={setShadowsEnabled}
|
||||
/>
|
||||
|
||||
<FormCheckbox
|
||||
{!isMobile() && <FormCheckbox
|
||||
label={t("ui-performance.enable-backdrop-effects")}
|
||||
currentValue={backdropEffectsEnabled} onChange={setBackdropEffectsEnabled}
|
||||
/>
|
||||
/>}
|
||||
|
||||
{isElectron() && <SmoothScrollEnabledOption />}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useNoteContext, useTriliumEvent } from "../../react/hooks";
|
||||
import "./mobile_editor_toolbar.css";
|
||||
import { isIOS } from "../../../services/utils";
|
||||
|
||||
import { CKTextEditor, ClassicEditor } from "@triliumnext/ckeditor5";
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { isIOS } from "../../../services/utils";
|
||||
import { useIsNoteReadOnly, useNoteContext, useNoteProperty, useTriliumEvent } from "../../react/hooks";
|
||||
|
||||
interface MobileEditorToolbarProps {
|
||||
inPopupEditor?: boolean;
|
||||
@@ -17,17 +19,13 @@ interface MobileEditorToolbarProps {
|
||||
export default function MobileEditorToolbar({ inPopupEditor }: MobileEditorToolbarProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { note, noteContext, ntxId } = useNoteContext();
|
||||
const [ shouldDisplay, setShouldDisplay ] = useState(false);
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const { isReadOnly } = useIsNoteReadOnly(note, noteContext);
|
||||
const shouldDisplay = noteType === "text" && isReadOnly === false;
|
||||
const [ dropdownActive, setDropdownActive ] = useState(false);
|
||||
|
||||
usePositioningOniOS(!inPopupEditor, containerRef);
|
||||
|
||||
useEffect(() => {
|
||||
noteContext?.isReadOnly().then(isReadOnly => {
|
||||
setShouldDisplay(note?.type === "text" && !isReadOnly);
|
||||
});
|
||||
}, [ note ]);
|
||||
|
||||
// Attach the toolbar from the CKEditor.
|
||||
useTriliumEvent("textEditorRefreshed", ({ ntxId: eventNtxId, editor }) => {
|
||||
if (eventNtxId !== ntxId || !containerRef.current) return;
|
||||
@@ -62,15 +60,15 @@ export default function MobileEditorToolbar({ inPopupEditor }: MobileEditorToolb
|
||||
|
||||
return (
|
||||
<div className={`classic-toolbar-outer-container ${!shouldDisplay ? "hidden-ext" : "visible"} ${isIOS() ? "ios" : ""}`}>
|
||||
<div ref={containerRef} className={`classic-toolbar-widget ${dropdownActive ? "dropdown-active" : ""}`}></div>
|
||||
<div ref={containerRef} className={`classic-toolbar-widget ${dropdownActive ? "dropdown-active" : ""}`} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function usePositioningOniOS(enabled: boolean, wrapperRef: MutableRef<HTMLDivElement | null>) {
|
||||
const adjustPosition = useCallback(() => {
|
||||
if (!wrapperRef.current) return;
|
||||
let bottom = window.innerHeight - (window.visualViewport?.height || 0);
|
||||
const bottom = window.innerHeight - (window.visualViewport?.height || 0);
|
||||
wrapperRef.current.style.bottom = `${bottom}px`;
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"e2e": "playwright test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "17.2.3"
|
||||
"dotenv": "17.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,6 +362,31 @@ paths:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/notes/{noteId}/attachments:
|
||||
parameters:
|
||||
- name: noteId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/components/schemas/EntityId"
|
||||
get:
|
||||
description: Returns all attachments for a note identified by its ID
|
||||
operationId: getNoteAttachments
|
||||
responses:
|
||||
"200":
|
||||
description: list of attachments
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Attachment"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json; charset=utf-8:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/notes/{noteId}/undelete:
|
||||
parameters:
|
||||
- name: noteId
|
||||
|
||||
82
apps/server/spec/etapi/get-note-attachments.spec.ts
Normal file
82
apps/server/spec/etapi/get-note-attachments.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Application } from "express";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import supertest from "supertest";
|
||||
import { createNote, login } from "./utils.js";
|
||||
import config from "../../src/services/config.js";
|
||||
|
||||
let app: Application;
|
||||
let token: string;
|
||||
|
||||
const USER = "etapi";
|
||||
let createdNoteId: string;
|
||||
let createdAttachmentId: string;
|
||||
|
||||
describe("etapi/get-note-attachments", () => {
|
||||
beforeAll(async () => {
|
||||
config.General.noAuthentication = false;
|
||||
const buildApp = (await (import("../../src/app.js"))).default;
|
||||
app = await buildApp();
|
||||
token = await login(app);
|
||||
|
||||
createdNoteId = await createNote(app, token);
|
||||
|
||||
// Create an attachment for the note
|
||||
const response = await supertest(app)
|
||||
.post(`/etapi/attachments`)
|
||||
.auth(USER, token, { "type": "basic" })
|
||||
.send({
|
||||
"ownerId": createdNoteId,
|
||||
"role": "file",
|
||||
"mime": "text/plain",
|
||||
"title": "test-attachment.txt",
|
||||
"content": "test content",
|
||||
"position": 10
|
||||
});
|
||||
createdAttachmentId = response.body.attachmentId;
|
||||
expect(createdAttachmentId).toBeTruthy();
|
||||
});
|
||||
|
||||
it("gets attachments for a note", async () => {
|
||||
const response = await supertest(app)
|
||||
.get(`/etapi/notes/${createdNoteId}/attachments`)
|
||||
.auth(USER, token, { "type": "basic" })
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body.length).toBeGreaterThan(0);
|
||||
|
||||
const attachment = response.body[0];
|
||||
expect(attachment).toHaveProperty("attachmentId", createdAttachmentId);
|
||||
expect(attachment).toHaveProperty("ownerId", createdNoteId);
|
||||
expect(attachment).toHaveProperty("role", "file");
|
||||
expect(attachment).toHaveProperty("mime", "text/plain");
|
||||
expect(attachment).toHaveProperty("title", "test-attachment.txt");
|
||||
expect(attachment).toHaveProperty("position", 10);
|
||||
expect(attachment).toHaveProperty("blobId");
|
||||
expect(attachment).toHaveProperty("dateModified");
|
||||
expect(attachment).toHaveProperty("utcDateModified");
|
||||
expect(attachment).toHaveProperty("contentLength");
|
||||
});
|
||||
|
||||
it("returns empty array for note with no attachments", async () => {
|
||||
// Create a new note without any attachments
|
||||
const newNoteId = await createNote(app, token, "Note without attachments");
|
||||
|
||||
const response = await supertest(app)
|
||||
.get(`/etapi/notes/${newNoteId}/attachments`)
|
||||
.auth(USER, token, { "type": "basic" })
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body.length).toBe(0);
|
||||
});
|
||||
|
||||
it("returns 404 for non-existent note", async () => {
|
||||
const response = await supertest(app)
|
||||
.get("/etapi/notes/nonexistentnote/attachments")
|
||||
.auth(USER, token, { "type": "basic" })
|
||||
.expect(404);
|
||||
|
||||
expect(response.body.code).toStrictEqual("NOTE_NOT_FOUND");
|
||||
});
|
||||
});
|
||||
@@ -354,6 +354,13 @@
|
||||
<td>Hides the <a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a> for
|
||||
that particular note.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>subtreeHidden</code>
|
||||
</td>
|
||||
<td>Hides all child notes of this note from the tree, displaying a badge with
|
||||
the count of hidden children. Children remain accessible via search or
|
||||
direct links.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>printLandscape</code>
|
||||
</td>
|
||||
@@ -372,6 +379,11 @@
|
||||
<a
|
||||
class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map</a>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>map:*</code>
|
||||
</td>
|
||||
<td>Defines specific options for the <a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map</a>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>calendar:*</code>
|
||||
</td>
|
||||
|
||||
@@ -26,10 +26,35 @@
|
||||
<h2>Automatically download and install the latest nightly</h2>
|
||||
<p>This is pretty useful if you are a beta tester that wants to periodically
|
||||
update their version:</p>
|
||||
<p>On Ubuntu:</p><pre><code class="language-text-x-trilium-auto">#!/usr/bin/env bash
|
||||
<h2>On Ubuntu (Bash)</h2><pre><code class="language-text-x-sh">#!/usr/bin/env bash
|
||||
|
||||
name=TriliumNotes-linux-x64-nightly.deb
|
||||
rm -f $name*
|
||||
wget https://github.com/TriliumNext/Trilium/releases/download/nightly/$name
|
||||
sudo apt-get install ./$name
|
||||
rm $name</code></pre>
|
||||
rm $name</code></pre>
|
||||
<h2>On Windows (PowerShell)</h2><pre><code class="language-application-x-powershell">if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
|
||||
$arch = "arm64";
|
||||
} else {
|
||||
$arch = "x64";
|
||||
}
|
||||
|
||||
$exeUrl = "https://github.com/TriliumNext/Trilium/releases/download/nightly/TriliumNotes-main-windows-$($arch).exe";
|
||||
Write-Host "Downloading $($exeUrl)"
|
||||
|
||||
# Generate a unique path in the temp dir
|
||||
$guid = [guid]::NewGuid().ToString()
|
||||
$destination = Join-Path -Path $env:TEMP -ChildPath "$guid.exe"
|
||||
|
||||
try {
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Invoke-WebRequest -Uri $exeUrl -OutFile $destination
|
||||
$process = Start-Process -FilePath $destination
|
||||
} catch {
|
||||
Write-Error "An error occurred: $_"
|
||||
} finally {
|
||||
# Clean up
|
||||
if (Test-Path $destination) {
|
||||
Remove-Item -Path $destination -Force
|
||||
}
|
||||
}</code></pre>
|
||||
@@ -1,12 +1,18 @@
|
||||
<p>To easily access selected notes, you can bookmark them. See demo:</p>
|
||||
<p>
|
||||
<img src="Bookmarks_bookmarks.gif">
|
||||
</p>
|
||||
<p>Frequently used notes can be bookmarked, which will make them appear in
|
||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a> for
|
||||
easy access.</p>
|
||||
<h2>Configuring the launch bar</h2>
|
||||
<p>If bookmarks don't appear in the launch bar, then most likely the bookmark
|
||||
section has been hidden. Go to the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a> configuration
|
||||
from the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a> and
|
||||
ensure <em>Bookmarks</em> is in the <em>Visible Launchers</em> section.</p>
|
||||
<h2>Bookmark folder</h2>
|
||||
<p>Space in the left panel is limited, and you might want to bookmark many
|
||||
items. One possible solution is to bookmark a folder, so it shows its children:</p>
|
||||
<p>
|
||||
<img src="Bookmarks_bookmark-folder.png">
|
||||
</p>
|
||||
<p>To do this, you need to add a <code spellcheck="false">#bookmarkFolder</code> label
|
||||
to the note.</p>
|
||||
<p>To do this, bookmark a folder and assign it the <code spellcheck="false">#bookmarkFolder</code> label.</p>
|
||||
<h2>Mobile</h2>
|
||||
<p>On mobile, bookmarks are only displayed starting with v0.102.0. Because
|
||||
of the more constrained screen size, the bookmarks are grouped under a
|
||||
single icon instead of displaying them as separate icons.</p>
|
||||
<p>When pressed, a menu will appear listing all the bookmarks. Bookmark folders
|
||||
are also supported and will appear as sub-menus.</p>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 305 KiB |
@@ -1,8 +1,11 @@
|
||||
<h2>Position of the Launch bar</h2>
|
||||
<p>Depending on the layout selected, the launcher bar will either be on the
|
||||
left side of the screen with buttons displayed vertically or at the top
|
||||
of the screen. See <a href="#root/_help_x0JgW8UqGXvq">Vertical and horizontal layout</a> for
|
||||
<p>On desktop, depending on the layout selected, the launcher bar will either
|
||||
be on the left side of the screen with buttons displayed vertically or
|
||||
at the top of the screen. See <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x0JgW8UqGXvq">Vertical and horizontal layout</a> for
|
||||
more information.</p>
|
||||
<p>On mobile, the launch bar will always be at the bottom.</p>
|
||||
<p>If there are too many items in the launch bar to fit the screen, it will
|
||||
become scrollable.</p>
|
||||
<h2>Terminology</h2>
|
||||
<ul>
|
||||
<li><strong>Launcher</strong>: a button that can be (or is) displayed on the
|
||||
@@ -12,16 +15,16 @@
|
||||
<li><strong>Visible Launcher</strong>: a launcher that is currently displayed
|
||||
on the launch bar.</li>
|
||||
</ul>
|
||||
<h2>Configuring the Launch bar</h2>
|
||||
<h2>Configuring the desktop Launch bar</h2>
|
||||
<p>There are two ways to configure the launch bar:</p>
|
||||
<ul>
|
||||
<li>Right click in the empty space between launchers on the launch bar and
|
||||
select <em>Configure Launchbar.</em>
|
||||
</li>
|
||||
<li>Click on the <a href="#root/_help_x3i7MxGccDuM">Global menu</a> and
|
||||
<li>Click on the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a> and
|
||||
select <em>Configure Launchbar</em>.</li>
|
||||
</ul>
|
||||
<p>This will open a new tab with the <a href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> listing
|
||||
<p>This will open a new tab with the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> listing
|
||||
the launchers.</p>
|
||||
<p>
|
||||
<img src="Launch Bar_image.png">
|
||||
@@ -29,6 +32,19 @@
|
||||
<p>Expanding <em>Available Launchers</em> section will show the list of launchers
|
||||
that are not displayed on the launch bar. The <em>Visible Launchers</em> will
|
||||
show the ones that are currently displayed.</p>
|
||||
<h2>Configuring the mobile launch bar</h2>
|
||||
<p>The launch bar on mobile uses a different configuration from the desktop
|
||||
one. The reasoning is that not all desktop icons are available on mobile,
|
||||
and fewer icons fit on a mobile screen.</p>
|
||||
<p>To configure the launch bar on mobile, go to <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a> and
|
||||
select <em>Configure Launchbar</em>.</p>
|
||||
<p>The configure the mobile launch bar while on the desktop (especially useful
|
||||
to configure more complicated launchers such as scripts or custom widgets),
|
||||
go to <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a> →
|
||||
Advanced → Show Hidden Subtree and look for the <em>Mobile Launch Bar</em> section.
|
||||
While in the hidden subtree, it's also possible to drag launchers between
|
||||
the <em>Mobile Launch Bar</em> and (Desktop) <em>Launch Bar</em> sections.</p>
|
||||
<h3>Adding/removing and reordering launchers</h3>
|
||||
<p>To display a new launcher in the launch bar, first look for it in the <em>Available Launchers</em> section.
|
||||
Then right click it and select <em>Move to visible launchers</em>. It is
|
||||
@@ -36,13 +52,13 @@
|
||||
<p>Similarly, to remove it from the launch bar, simply look for it in <em>Visible Launchers</em> then
|
||||
right click it and select <em>Move to available launchers</em> or use drag-and-drop.</p>
|
||||
<p>Drag-and-drop the items in the tree in order to change their
|
||||
order. See <a href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> for more
|
||||
interaction options, including using keyboard shortcuts.</p>
|
||||
order. See <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> for
|
||||
more interaction options, including using keyboard shortcuts.</p>
|
||||
<h2>Customizing the launcher</h2>
|
||||
<ul>
|
||||
<li>The icon of a launcher can be changed just like a normal note. See
|
||||
<a
|
||||
href="#root/_help_p9kXRFAkwN4o">Note Icons</a> for more information.</li>
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_p9kXRFAkwN4o">Note Icons</a> for more information.</li>
|
||||
<li>The title of the launcher can also be changed.</li>
|
||||
</ul>
|
||||
<h3>Resetting</h3>
|
||||
@@ -54,42 +70,39 @@
|
||||
<p>Right click either the <em>Available launchers</em> or <em>Visible launchers</em> sections
|
||||
and select one of the options:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p><strong>Note Launcher</strong>
|
||||
<br>A note launcher will simply navigate to a specified note.</p>
|
||||
<li><strong>Note Launcher</strong>
|
||||
<br>A note launcher will simply navigate to a specified note.
|
||||
<ol>
|
||||
<li>Set the <code spellcheck="false">target</code> promoted attribute to the
|
||||
note to navigate to.</li>
|
||||
<li>Optionally, set <code spellcheck="false">hoistedNote</code> to hoist a particular
|
||||
note. See <a href="#root/_help_OR8WJ7Iz9K4U">Note Hoisting</a> for
|
||||
note. See <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_OR8WJ7Iz9K4U">Note Hoisting</a> for
|
||||
more information.</li>
|
||||
<li>Optionally, set a <code spellcheck="false">keyboardShortcut</code> to trigger
|
||||
the launcher.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Script Launcher</strong>
|
||||
<br>An advanced launcher which will run a script upon pressing. See
|
||||
<a
|
||||
href="#root/_help_CdNpE2pqjmI6">Scripts</a> for more information.</p>
|
||||
<ol>
|
||||
<li>Set <code spellcheck="false">script</code> to point to the desired script
|
||||
to run.</li>
|
||||
<li>Optionally, set a <code spellcheck="false">keyboardShortcut</code> to trigger
|
||||
the launcher.</li>
|
||||
</ol>
|
||||
<li><strong>Script Launcher</strong>
|
||||
<br>An advanced launcher which will run a script upon pressing. See
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">Scripting</a> for more information.
|
||||
<ol>
|
||||
<li>Set <code spellcheck="false">script</code> to point to the desired script
|
||||
to run.</li>
|
||||
<li>Optionally, set a <code spellcheck="false">keyboardShortcut</code> to trigger
|
||||
the launcher.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<li class="ck-list-marker-bold">
|
||||
<p><strong>Custom Widget</strong>
|
||||
</p>
|
||||
<p>Allows defining a custom widget to be rendered inside the launcher. See
|
||||
<a
|
||||
href="#root/_help_SynTBQiBsdYJ">Widget Basics</a> for more information.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Spacers</strong>
|
||||
<br>Launchers that create some distance between other launchers for better
|
||||
visual distinction.</p>
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/MgibgPcfeuGz/_help_SynTBQiBsdYJ">Widget Basics</a> for more information.</p>
|
||||
</li>
|
||||
<li><strong>Spacers</strong>
|
||||
<br>Launchers that create some distance between other launchers for better
|
||||
visual distinction.</li>
|
||||
</ol>
|
||||
<p>Launchers are configured via predefined <a href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.</p>
|
||||
<p>Launchers are configured via predefined <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a>.</p>
|
||||
@@ -4,8 +4,7 @@ class="image image-style-align-center">
|
||||
<img style="aspect-ratio:1398/1015;" src="Split View_2_Split View_im.png"
|
||||
width="1398" height="1015">
|
||||
</figure>
|
||||
|
||||
<h2><strong>Interactions</strong></h2>
|
||||
<h2><strong>Interactions</strong></h2>
|
||||
<ul>
|
||||
<li>Press the
|
||||
<img src="Split View_Split View_imag.png">button to the right of a note's title to open a new split to the right
|
||||
@@ -51,11 +50,12 @@ class="image image-style-align-center">
|
||||
<ul>
|
||||
<li>On smartphones, the split views are laid out vertically (one on the top
|
||||
and one on the bottom), instead of horizontally as on the desktop.</li>
|
||||
<li>There can be only one split open per tab.</li>
|
||||
<li>It's not possible to resize the two split panes.</li>
|
||||
<li>When the keyboard is opened, the active note will be “maximized”, thus
|
||||
allowing for more space even when a split is open. When the keyboard is
|
||||
closed, the splits become equal in size again.</li>
|
||||
<li
|
||||
>There can be only one split open per tab.</li>
|
||||
<li>It's not possible to resize the two split panes.</li>
|
||||
<li>When the keyboard is opened, the active note will be “maximized”, thus
|
||||
allowing for more space even when a split is open. When the keyboard is
|
||||
closed, the splits become equal in size again.</li>
|
||||
</ul>
|
||||
<p>Interaction:</p>
|
||||
<ul>
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<ul>
|
||||
<li>For the vertical layout, the tabs will be placed at the top but to the
|
||||
right of the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
||||
<li>For the horizontal layout, the tabs will be placed at the top in full-width,
|
||||
<li
|
||||
>For the horizontal layout, the tabs will be placed at the top in full-width,
|
||||
above the <a href="#root/_help_oPVyFC7WL2Lp">note tree</a>, allowing for
|
||||
more tabs to be comfortably displayed.</li>
|
||||
</ul>
|
||||
@@ -22,7 +23,8 @@
|
||||
href="#root/_help_luNhaphA37EO">Split View</a>. Each tab can have one or more
|
||||
notes, displayed horizontally.</li>
|
||||
<li>Tabs can be reordered by drag-and-dropping it into a new position.</li>
|
||||
<li>An existing tab can be displayed in a new window by dragging the tab upwards
|
||||
<li
|
||||
>An existing tab can be displayed in a new window by dragging the tab upwards
|
||||
or downwards. It is not possible to combine tabs back into another window.</li>
|
||||
</ul>
|
||||
<h2>Keyboard interaction</h2>
|
||||
@@ -36,7 +38,29 @@
|
||||
<li><kbd>Ctrl</kbd>+<kbd>Tab</kbd> and <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Tab</kbd> to
|
||||
go to the next or previous tab.</li>
|
||||
<li><kbd>Ctrl</kbd>+<kbd>1</kbd>, <kbd>Ctrl</kbd>+<kbd>2</kbd>, up to <kbd>Ctrl</kbd>+<kbd>9</kbd> to
|
||||
activate the first, second and up til ninth tab.</li>
|
||||
activate the first, second and up to ninth tab.</li>
|
||||
<li>There is also a shortcut to go to the last tab, but it is not assigned
|
||||
a key by default.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
<h2>Mobile</h2>
|
||||
<figure class="image image-style-align-right image_resized" style="width:34.12%;">
|
||||
<img style="aspect-ratio:1242/2688;" src="Tabs_IMG_1767.PNG"
|
||||
width="1242" height="2688">
|
||||
</figure>
|
||||
<p>Tabs are also supported on the <a class="reference-link" href="#root/pOsGYCXsbNQG/Otzi9La2YAUX/_help_RDslemsQ6gCp">Mobile Frontend</a>.</p>
|
||||
<p>Since v0.102.0, the tabs are displayed by pressing the dedicated tab switcher
|
||||
button in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.
|
||||
In this view the tabs are laid out on a grid with a preview of the note
|
||||
content.</p>
|
||||
<p>The context menu button at the top-right of the popup allows creating
|
||||
a new tab, reopening the last closed tab and closing all the tabs.</p>
|
||||
<p><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_luNhaphA37EO">Split View</a>s
|
||||
are also indicated in the tab switcher, with two titles displayed in a
|
||||
tab.</p>
|
||||
<aside class="admonition note">
|
||||
<p>Versions prior to v0.102.0 also supported tabs, but they were displayed
|
||||
directly above the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.
|
||||
The decision to use a more mobile-like tab switcher was taken because the
|
||||
original tab bar could not support many tabs at once and the new design
|
||||
better aligns with how mobile applications handle tabs.</p>
|
||||
</aside>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs_IMG_1767.PNG
generated
vendored
Normal file
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs_IMG_1767.PNG
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 195 KiB |
@@ -1,35 +1,168 @@
|
||||
<p>Trilium (<a href="#root/_help_WOcw2SLH6tbX">server edition</a>) has a mobile
|
||||
web frontend which is optimized for touch based devices - smartphones and
|
||||
tablets. It is activated automatically during login process based on browser
|
||||
detection.</p>
|
||||
<p>Mobile frontend is limited in features compared to full desktop frontend.
|
||||
<figure class="image image_resized image-style-align-right" style="width:33.52%;">
|
||||
<img style="aspect-ratio:1242/2688;" src="Mobile Frontend_IMG_1765.PNG"
|
||||
width="1242" height="2688">
|
||||
</figure>
|
||||
<p>Trilium has a mobile web frontend which is optimized for touch based devices
|
||||
- smartphones and tablets. It is activated automatically during login process
|
||||
based on browser detection.</p>
|
||||
<p>Mobile frontend is limited in features compared to the full desktop version.
|
||||
See below for more details on this.</p>
|
||||
<p>Note that this is not an Android/iOS app, this is just mobile friendly
|
||||
web page served on the <a href="#root/_help_WOcw2SLH6tbX">server edition</a>.</p>
|
||||
<h2>Layout basics</h2>
|
||||
<p>Unlike the desktop version, the mobile version has a slightly different
|
||||
UI meant to better fit the constrained screens of a mobile phone.</p>
|
||||
<p>Here is a non-exhaustive list of differences between the desktop version
|
||||
and the mobile one:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>The <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> is
|
||||
displayed as a sidebar. To display the sidebar, press the button in the
|
||||
top-left of the screen.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>There is also a swipe gesture that can be done from the left of the screen,
|
||||
but the browser's navigation gesture interferes with it most of the time
|
||||
(depending on the platform).</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Press and hold a note to display the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/oPVyFC7WL2Lp/_help_YtSN43OrfzaA">Note tree contextual menu</a>.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_Ms1nauBra7gq">Quick search</a> bar
|
||||
is also displayed at the top of the note tree.</li>
|
||||
<li>The full <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a> function
|
||||
can be triggered either from either the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a> or
|
||||
from the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>,
|
||||
if configured.</li>
|
||||
<li>
|
||||
<p>The <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a> is
|
||||
displayed at the bottom of the screen.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>The launch bar uses a different configuration for icons than the desktop
|
||||
version. See the dedicated page for more information on how to configure
|
||||
it.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>Most of the note-related actions are grouped in the horizontal dots icon
|
||||
on the top-right of the note.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_3seOhtN8uLIY">Tabs</a> are
|
||||
grouped under a tab switcher in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>,
|
||||
where the tabs are displayed in a full-screen grid with preview for easy
|
||||
switching, as well as additional options such as reopening closed tabs.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Since v0.100.0, <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_luNhaphA37EO">Split View</a> can
|
||||
also be used in mobile view, but with a maximum of two panes at once. The
|
||||
splits are displayed vertically instead of horizontally.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Starting with v0.102.0, the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_IjZS7iK5EXtb">New Layout</a> is
|
||||
enforced on mobile. This brings features such as the note badges, note
|
||||
type switcher or collection properties which would otherwise not be available.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Installing as a PWA</h2>
|
||||
<p>The mobile view can be set up as a PWA. While this does not offer any
|
||||
offline capabilities, it will display the application in full-screen and
|
||||
makes it easy to access via your mobile phone's home screen.</p>
|
||||
<h3>On iOS with Safari</h3>
|
||||
<ol>
|
||||
<li>Open your default web browser and access your Trilium instance.</li>
|
||||
<li
|
||||
>Login.</li>
|
||||
<li>
|
||||
<p>Press the […] button in the bottom-right of the screen and select Share.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Scroll down to reveal the full list of items and choose “Add to Home Screen”.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Press “Add” and the web app will be available.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<h3>On Android with Google Chrome</h3>
|
||||
<aside class="admonition important">
|
||||
<p>Google Chrome requires the server to be served over HTTPS in order to
|
||||
display in full-screen. If using HTTP, the app will appear like a normal
|
||||
web page (similar to a bookmark).</p>
|
||||
</aside>
|
||||
<ol>
|
||||
<li>Open your default web browser and access your Trilium instance.</li>
|
||||
<li
|
||||
>Login.</li>
|
||||
<li>
|
||||
<p>Press the three vertical dots icon in the top-right of the screen and
|
||||
select <em>Add to Home screen.</em>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Select the <em>Install</em> option.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Select an appropriate name.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The web app will appear as an application, not on the home screen.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<h3>On Android with Brave</h3>
|
||||
<aside class="admonition important">
|
||||
<p>Brave requires the server to be served over HTTPS in order to display
|
||||
in full-screen. If using HTTP, the app will appear like a normal web page
|
||||
(similar to a bookmark).</p>
|
||||
</aside>
|
||||
<ol>
|
||||
<li>Open your default web browser and access your Trilium instance.</li>
|
||||
<li
|
||||
>Login.</li>
|
||||
<li>
|
||||
<p>Press the three vertical dots icon in the bottom-right of the screen and
|
||||
select <em>Add to Home screen</em>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Press the <em>Install</em> option.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>The web app will appear as an application, not on the home screen.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<h3>On Samsung Browser</h3>
|
||||
<ol>
|
||||
<li>Open your default web browser and access your Trilium instance.</li>
|
||||
<li
|
||||
>Login.</li>
|
||||
<li>
|
||||
<p>Press the hamburger menu in the bottom-right of the screen.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Select <em>Add to</em>, followed by <em>Home screen</em>.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Press <em>Add</em> and the web app will appear on the home page.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<h2>Testing via the desktop application</h2>
|
||||
<p>If you are running Trilium without a dedicated <a href="#root/_help_WOcw2SLH6tbX">server installation</a>,
|
||||
you can still test the mobile application using the desktop application.
|
||||
For more information, see <a class="reference-link" href="#root/_help_nRqcgfTb97uV">Using the desktop application as a server</a>.
|
||||
To access it go to <code spellcheck="false">http://<ip>:37840/login?mobile</code> .</p>
|
||||
<h2>Limitations</h2>
|
||||
<p>Mobile frontend provides only some of the features of the full desktop
|
||||
frontend:</p>
|
||||
<ul>
|
||||
<li>it is possible to browse the whole note tree, read and edit all types
|
||||
of notes, but you can create only text notes</li>
|
||||
<li>reading and editing <a href="#root/_help_bwg0e8ewQMak">protected notes</a> is
|
||||
possible, but creating them is not supported</li>
|
||||
<li>editing options is not supported</li>
|
||||
<li>cloning notes is not supported</li>
|
||||
<li>uploading file attachments is not supported</li>
|
||||
</ul>
|
||||
<h2>Forcing mobile/desktop frontend</h2>
|
||||
<p>Trilium decides automatically whether to use mobile or desktop frontend.
|
||||
<p>Trilium decides automatically whether to use mobile or desktop front-end.
|
||||
If this is not appropriate, you can use <code spellcheck="false">?mobile</code> or
|
||||
<code
|
||||
spellcheck="false">?desktop</code>query param on <strong>login</strong> page (Note: you might
|
||||
need to log out).</p>
|
||||
<p>Alternatively, simply select <em>Switch to Mobile/Desktop Version</em> in
|
||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>.</p>
|
||||
<h2>Scripting</h2>
|
||||
<p>You can alter the behavior with <a href="#root/_help_CdNpE2pqjmI6">scripts</a> just
|
||||
like for normal frontend. For script notes to be executed, they need to
|
||||
have labeled <code spellcheck="false">#run=mobileStartup</code>.</p>
|
||||
<p>You can alter the behavior with <a class="reference-link" href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">Scripting</a>,
|
||||
just like for normal frontend. For script notes to be executed, they need
|
||||
to have labeled <code spellcheck="false">#run=mobileStartup</code>.</p>
|
||||
<p>Custom <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a> widgets
|
||||
are also supported.</p>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Mobile Frontend_IMG_1765.PNG
generated
vendored
Normal file
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Installation & Setup/Mobile Frontend_IMG_1765.PNG
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
@@ -24,8 +24,7 @@
|
||||
<li>click on an image or link and save it through context menu</li>
|
||||
<li>save whole page from the popup or context menu</li>
|
||||
<li>save screenshot (with crop tool) from either popup or context menu</li>
|
||||
<li
|
||||
>create short text note from popup</li>
|
||||
<li>create short text note from popup</li>
|
||||
</ul>
|
||||
<h2>Location of clippings</h2>
|
||||
<p>Trilium will save these clippings as a new child note under a "clipper
|
||||
@@ -40,10 +39,8 @@
|
||||
<p>Keyboard shortcuts are available for most functions:</p>
|
||||
<ul>
|
||||
<li>Save selected text: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> (Mac: <kbd>⌘</kbd>+<kbd>⇧</kbd>+<kbd>S</kbd>)</li>
|
||||
<li
|
||||
>Save whole page: <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> (Mac: <kbd>⌥</kbd>+<kbd>⇧</kbd>+<kbd>S</kbd>)</li>
|
||||
<li
|
||||
>Save screenshot: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>E</kbd> (Mac: <kbd>⌘</kbd>+<kbd>⇧</kbd>+<kbd>E</kbd>)</li>
|
||||
<li>Save whole page: <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> (Mac: <kbd>⌥</kbd>+<kbd>⇧</kbd>+<kbd>S</kbd>)</li>
|
||||
<li>Save screenshot: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>E</kbd> (Mac: <kbd>⌘</kbd>+<kbd>⇧</kbd>+<kbd>E</kbd>)</li>
|
||||
</ul>
|
||||
<p>To set custom shortcuts, follow the directions for your browser.</p>
|
||||
<ul>
|
||||
@@ -72,20 +69,19 @@
|
||||
<li><a href="https://github.com/TriliumNext/Trilium/releases">GitHub Releases</a> by
|
||||
looking for releases starting with <em>Web Clipper.</em>
|
||||
</li>
|
||||
<li>Artifacts in GitHub Actions, by looking for the <a href="https://github.com/TriliumNext/Trilium/actions/workflows/web-clipper.yml"><em>Deploy web clipper extension </em>workflow</a>.
|
||||
<li>Artifacts in GitHub Actions, by looking for the <a href="https://github.com/TriliumNext/Trilium/actions/workflows/web-clipper.yml"><em>Deploy web clipper extension</em> workflow</a>.
|
||||
Once a workflow run is selected, the ZIP files are available in the <em>Artifacts</em> section,
|
||||
under the name <code spellcheck="false">web-clipper-extension</code>.</li>
|
||||
</ul>
|
||||
<h3>For Chrome</h3>
|
||||
<ol>
|
||||
<li>Download <code spellcheck="false">trilium-web-clipper-[x.y.z]-chrome.zip</code>.</li>
|
||||
<li
|
||||
>Extract the archive.</li>
|
||||
<li>In Chrome, navigate to <code spellcheck="false">chrome://extensions/</code>
|
||||
</li>
|
||||
<li>Toggle <em>Developer Mode</em> in top-right of the page.</li>
|
||||
<li>Press the <em>Load unpacked</em> button near the header.</li>
|
||||
<li>Point to the extracted directory from step (2).</li>
|
||||
<li>Extract the archive.</li>
|
||||
<li>In Chrome, navigate to <code spellcheck="false">chrome://extensions/</code>
|
||||
</li>
|
||||
<li>Toggle <em>Developer Mode</em> in top-right of the page.</li>
|
||||
<li>Press the <em>Load unpacked</em> button near the header.</li>
|
||||
<li>Point to the extracted directory from step (2).</li>
|
||||
</ol>
|
||||
<h3>For Firefox</h3>
|
||||
<aside class="admonition warning">
|
||||
@@ -100,12 +96,10 @@
|
||||
<li>Navigate to <code spellcheck="false">about:addons</code>.</li>
|
||||
<li>Select <em>Extensions</em> in the left-side navigation.</li>
|
||||
<li>Press the <em>Gear</em> icon on the right of the <em>Manage Your Extensions</em> title.</li>
|
||||
<li
|
||||
>Select <em>Install Add-on From File…</em>
|
||||
</li>
|
||||
<li>Point it to <code spellcheck="false">trilium-web-clipper-[x.y.z]-firefox.zip</code>.</li>
|
||||
<li
|
||||
>Press the <em>Add</em> button to confirm.</li>
|
||||
<li>Select <em>Install Add-on From File…</em>
|
||||
</li>
|
||||
<li>Point it to <code spellcheck="false">trilium-web-clipper-[x.y.z]-firefox.zip</code>.</li>
|
||||
<li>Press the <em>Add</em> button to confirm.</li>
|
||||
</ol>
|
||||
<h2>Credits</h2>
|
||||
<p>Some parts of the code are based on the <a href="https://github.com/laurent22/joplin/tree/master/Clipper">Joplin Notes browser extension</a>.</p>
|
||||
@@ -60,7 +60,7 @@
|
||||
</p>
|
||||
<h2>Step 5. Making changes</h2>
|
||||
<p>Simply go back to the note and change according to needs. To apply the
|
||||
changes to the current window, press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>R </kbd> to
|
||||
changes to the current window, press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>R</kbd> to
|
||||
refresh.</p>
|
||||
<p>It's a good idea to keep two windows, one for editing and the other one
|
||||
for previewing the changes.</p>
|
||||
@@ -1,5 +1,5 @@
|
||||
<aside class="admonition note">
|
||||
<p>e This page describes how to create custom icon packs. For a general description
|
||||
<p>This page describes how to create custom icon packs. For a general description
|
||||
of how to use already existing icon packs, see <a class="reference-link"
|
||||
href="#root/_help_gOKqSJgXLcIj">Icon Packs</a>.</p>
|
||||
</aside>
|
||||
@@ -74,9 +74,9 @@
|
||||
"bx-ball": {
|
||||
"glyph": "\ue9c2",
|
||||
"terms": [ "ball" ]
|
||||
},
|
||||
},
|
||||
"bxs-party": {
|
||||
"glyph": "\uec92"
|
||||
"glyph": "\uec92",
|
||||
"terms": [ "party" ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
</ul>
|
||||
<h2>Overrides</h2>
|
||||
<p>Do note that the TriliumNext theme has a few more overrides than the legacy
|
||||
theme, so you might need to suffix <code spellcheck="false">!important</code> if
|
||||
the style changes are not applied.</p><pre><code class="language-text-css">:root {
|
||||
--launcher-pane-background-color: #0d6efd !important;
|
||||
theme. Due to that, it is recommended to use <code spellcheck="false">#trilium-app</code> with
|
||||
a next theme instead of the <code spellcheck="false">:root</code> of a legacy
|
||||
theme.</p><pre><code class="language-text-css">#trilium-app {
|
||||
--launcher-pane-background-color: #0d6efd;
|
||||
}</code></pre>
|
||||
@@ -259,7 +259,8 @@
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"inbox-title": "收件箱",
|
||||
"command-palette": "打开命令面板",
|
||||
"zen-mode": "禅模式"
|
||||
"zen-mode": "禅模式",
|
||||
"tab-switcher-title": "标签切换器"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "新建笔记",
|
||||
|
||||
@@ -257,7 +257,8 @@
|
||||
"localization": "Sprache & Region",
|
||||
"inbox-title": "Posteingang",
|
||||
"zen-mode": "Zen-Modus",
|
||||
"command-palette": "Befehlspalette öffnen"
|
||||
"command-palette": "Befehlspalette öffnen",
|
||||
"tab-switcher-title": "Tabauswahl"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Neue Notiz",
|
||||
|
||||
@@ -259,7 +259,8 @@
|
||||
"inbox-title": "Bandeja",
|
||||
"jump-to-note-title": "Saltar a...",
|
||||
"command-palette": "Abrir paleta de comandos",
|
||||
"zen-mode": "Modo Zen"
|
||||
"zen-mode": "Modo Zen",
|
||||
"tab-switcher-title": "Conmutador de pestañas"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nueva nota",
|
||||
|
||||
443
apps/server/src/assets/translations/ga/server.json
Normal file
443
apps/server/src/assets/translations/ga/server.json
Normal file
@@ -0,0 +1,443 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "Téigh go dtí an nóta roimhe seo sa stair",
|
||||
"forward-in-note-history": "Téigh go dtí an chéad nóta eile sa stair",
|
||||
"open-jump-to-note-dialog": "Oscail an dialóg \"Léim go dtí an nóta\"",
|
||||
"open-command-palette": "Oscail pailéad orduithe",
|
||||
"scroll-to-active-note": "Scrollaigh crann na nótaí go dtí an nóta gníomhach",
|
||||
"quick-search": "Gníomhachtaigh an barra cuardaigh thapa",
|
||||
"search-in-subtree": "Cuardaigh nótaí i bhfo-chrann an nóta ghníomhaigh",
|
||||
"expand-subtree": "Leathnaigh fo-chrann an nóta reatha",
|
||||
"collapse-tree": "Laghdaíonn sé an crann nótaí iomlán",
|
||||
"collapse-subtree": "Laghdaíonn sé fo-chrann an nóta reatha",
|
||||
"sort-child-notes": "Sórtáil nótaí leanaí",
|
||||
"creating-and-moving-notes": "Nótaí a chruthú agus a bhogadh",
|
||||
"create-note-after": "Cruthaigh nóta i ndiaidh nóta gníomhach",
|
||||
"create-note-into": "Cruthaigh nóta mar leanbh den nóta gníomhach",
|
||||
"create-note-into-inbox": "Cruthaigh nóta sa bhosca isteach (más sainithe) nó nóta lae",
|
||||
"delete-note": "Scrios nóta",
|
||||
"move-note-up": "Bog nóta suas",
|
||||
"move-note-down": "Bog nóta síos",
|
||||
"move-note-up-in-hierarchy": "Bog nóta suas san ordlathas",
|
||||
"move-note-down-in-hierarchy": "Bog nóta síos san ordlathas",
|
||||
"edit-note-title": "Léim ón gcrann go dtí sonraí an nóta agus cuir an teideal in eagar",
|
||||
"edit-branch-prefix": "Taispeáin an dialóg \"Cuir réimír na brainse in eagar\"",
|
||||
"clone-notes-to": "Clónáil nótaí roghnaithe",
|
||||
"move-notes-to": "Bog nótaí roghnaithe",
|
||||
"note-clipboard": "Gearrthaisce nótaí",
|
||||
"copy-notes-to-clipboard": "Cóipeáil na nótaí roghnaithe chuig an ghearrthaisce",
|
||||
"paste-notes-from-clipboard": "Greamaigh nótaí ón ghearrthaisce isteach sa nóta gníomhach",
|
||||
"cut-notes-to-clipboard": "Gearr nótaí roghnaithe chuig an ghearrthaisce",
|
||||
"select-all-notes-in-parent": "Roghnaigh na nótaí go léir ón leibhéal nóta reatha",
|
||||
"add-note-above-to-the-selection": "Cuir nóta thuas leis an rogha",
|
||||
"add-note-below-to-selection": "Cuir nóta leis an rogha thíos",
|
||||
"duplicate-subtree": "Fo-chrann dúblach",
|
||||
"tabs-and-windows": "Cluaisíní agus Fuinneoga",
|
||||
"open-new-tab": "Oscail cluaisín nua",
|
||||
"close-active-tab": "Dún an cluaisín gníomhach",
|
||||
"reopen-last-tab": "Athoscail an cluaisín deireanach a dúnadh",
|
||||
"activate-next-tab": "Gníomhachtaigh an cluaisín ar dheis",
|
||||
"activate-previous-tab": "Gníomhachtaigh an cluaisín ar chlé",
|
||||
"open-new-window": "Oscail fuinneog nua folamh",
|
||||
"toggle-tray": "Taispeáin/folaigh an feidhmchlár ón tráidire córais",
|
||||
"first-tab": "Gníomhachtaigh an chéad chluaisín sa liosta",
|
||||
"second-tab": "Gníomhachtaigh an dara cluaisín sa liosta",
|
||||
"third-tab": "Gníomhachtaigh an tríú cluaisín sa liosta",
|
||||
"fourth-tab": "Gníomhachtaigh an ceathrú cluaisín sa liosta",
|
||||
"fifth-tab": "Gníomhachtaigh an cúigiú cluaisín sa liosta",
|
||||
"sixth-tab": "Gníomhachtaigh an séú cluaisín sa liosta",
|
||||
"seventh-tab": "Gníomhachtaigh an seachtú cluaisín sa liosta",
|
||||
"eight-tab": "Gníomhachtaigh an t-ochtú cluaisín sa liosta",
|
||||
"ninth-tab": "Gníomhachtaigh an naoú cluaisín sa liosta",
|
||||
"last-tab": "Gníomhachtaigh an cluaisín deireanach sa liosta",
|
||||
"dialogs": "Dialóga",
|
||||
"show-note-source": "Taispeáin an dialóg \"Foinse an Nóta\"",
|
||||
"show-options": "Oscail an leathanach \"Roghanna\"",
|
||||
"show-revisions": "Taispeáin an dialóg \"Athbhreithnithe Nóta\"",
|
||||
"show-recent-changes": "Taispeáin an dialóg \"Athruithe Le Déanaí\"",
|
||||
"show-sql-console": "Oscail an leathanach \"Consól SQL\"",
|
||||
"show-backend-log": "Oscail an leathanach \"Log Backend\"",
|
||||
"show-help": "Oscail an Treoir Úsáideora ionsuite",
|
||||
"show-cheatsheet": "Taispeáin modal le hoibríochtaí coitianta méarchláir",
|
||||
"text-note-operations": "Oibríochtaí nótaí téacs",
|
||||
"add-link-to-text": "Oscail an dialóg chun nasc a chur leis an téacs",
|
||||
"follow-link-under-cursor": "Lean an nasc ina bhfuil an caret curtha",
|
||||
"insert-date-and-time-to-text": "Cuir an dáta agus an t-am reatha isteach sa téacs",
|
||||
"paste-markdown-into-text": "Greamaigh Markdown ón ghearrthaisce isteach i nóta téacs",
|
||||
"cut-into-note": "Gearrann sé an rogha ón nóta reatha agus cruthaíonn sé fo-nóta leis an téacs roghnaithe",
|
||||
"add-include-note-to-text": "Osclaíonn an dialóg chun nóta a chur san áireamh",
|
||||
"edit-readonly-note": "Cuir nóta inléite amháin in eagar",
|
||||
"attributes-labels-and-relations": "Tréithe (lipéid & caidrimh)",
|
||||
"add-new-label": "Cruthaigh lipéad nua",
|
||||
"create-new-relation": "Cruthaigh caidreamh nua",
|
||||
"ribbon-tabs": "Cluaisíní ribín",
|
||||
"toggle-basic-properties": "Airíonna Bunúsacha a Athrú",
|
||||
"toggle-file-properties": "Airíonna Comhaid a Athrú",
|
||||
"toggle-image-properties": "Airíonna Íomhá a Athrú",
|
||||
"toggle-owned-attributes": "Tréithe faoi Úinéireacht a Athrú",
|
||||
"toggle-inherited-attributes": "Tréithe Oidhreachta a Athrú",
|
||||
"toggle-promoted-attributes": "Tréithe Curtha Chun Cinn a Athrú",
|
||||
"toggle-link-map": "Léarscáil Nasc a Athsholáthar",
|
||||
"toggle-note-info": "Eolas Nóta a Athrú",
|
||||
"toggle-note-paths": "Cosáin Nótaí a Athrú",
|
||||
"toggle-similar-notes": "Nótaí Cosúla a Athsholáthar",
|
||||
"other": "Eile",
|
||||
"toggle-right-pane": "Athraigh taispeáint an phainéil dheis, lena n-áirítear Clár Ábhair agus Buaicphointí",
|
||||
"print-active-note": "Priontáil nóta gníomhach",
|
||||
"open-note-externally": "Oscail nóta mar chomhad leis an bhfeidhmchlár réamhshocraithe",
|
||||
"render-active-note": "Rindreáil (ath-rindreáil) nóta gníomhach",
|
||||
"run-active-note": "Rith nóta cóid JavaScript gníomhach (frontend/backend)",
|
||||
"toggle-note-hoisting": "Scoránaigh ardú nóta an nóta ghníomhaigh",
|
||||
"unhoist": "Dí-ardaigh ó áit ar bith",
|
||||
"reload-frontend-app": "Athlódáil an tosaigh",
|
||||
"open-dev-tools": "Uirlisí forbróra oscailte",
|
||||
"find-in-text": "Painéal cuardaigh a scoránaigh",
|
||||
"toggle-left-note-tree-panel": "Scoránaigh an painéal ar chlé (crann nótaí)",
|
||||
"toggle-full-screen": "Scoraigh an scáileán iomlán",
|
||||
"zoom-out": "Zúmáil Amach",
|
||||
"zoom-in": "Zúmáil Isteach",
|
||||
"note-navigation": "Nascleanúint nótaí",
|
||||
"reset-zoom-level": "Athshocraigh leibhéal súmála",
|
||||
"copy-without-formatting": "Cóipeáil téacs roghnaithe gan fhormáidiú",
|
||||
"force-save-revision": "Cruthú/sábháil nóta nua den nóta gníomhach i bhfeidhm",
|
||||
"toggle-book-properties": "Airíonna an Bhailiúcháin a Athrú",
|
||||
"toggle-classic-editor-toolbar": "Athraigh an cluaisín Formáidithe don eagarthóir leis an mbarra uirlisí socraithe",
|
||||
"export-as-pdf": "Easpórtáil an nóta reatha mar PDF",
|
||||
"toggle-zen-mode": "Cumasaíonn/díchumasaíonn sé an mód zen (comhad úsáideora íosta le haghaidh eagarthóireacht níos dírithe)"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"back-in-note-history": "Ar ais i Stair na Nótaí",
|
||||
"forward-in-note-history": "Ar Aghaidh i Stair na Nótaí",
|
||||
"jump-to-note": "Léim go...",
|
||||
"command-palette": "Pailéad Ordú",
|
||||
"scroll-to-active-note": "Scrollaigh go dtí an Nóta Gníomhach",
|
||||
"quick-search": "Cuardach Tapa",
|
||||
"search-in-subtree": "Cuardaigh i bhFo-chrann",
|
||||
"expand-subtree": "Leathnaigh an Fo-Chrann",
|
||||
"collapse-tree": "Laghdaigh Crann",
|
||||
"collapse-subtree": "Laghdaigh Fo-chrann",
|
||||
"sort-child-notes": "Sórtáil Nótaí Leanaí",
|
||||
"create-note-after": "Cruthaigh Nóta Tar éis",
|
||||
"create-note-into": "Cruthaigh Nóta Isteach",
|
||||
"create-note-into-inbox": "Cruthaigh Nóta sa Bhosca Isteach",
|
||||
"delete-notes": "Scrios Nótaí",
|
||||
"move-note-up": "Bog Nóta Suas",
|
||||
"move-note-down": "Bog Nóta Síos",
|
||||
"move-note-up-in-hierarchy": "Bog Nóta Suas san Ordlathas",
|
||||
"move-note-down-in-hierarchy": "Bog Nóta Síos san Ordlathas",
|
||||
"edit-note-title": "Cuir Teideal an Nóta in Eagar",
|
||||
"edit-branch-prefix": "Cuir Réimír na Brainse in Eagar",
|
||||
"clone-notes-to": "Nótaí Clónála Chuig",
|
||||
"move-notes-to": "Bog Nótaí Chuig",
|
||||
"copy-notes-to-clipboard": "Cóipeáil Nótaí chuig an nGearrthaisce",
|
||||
"paste-notes-from-clipboard": "Greamaigh Nótaí ón nGearrthaisce",
|
||||
"cut-notes-to-clipboard": "Gearr Nótaí chuig an nGearrthaisce",
|
||||
"select-all-notes-in-parent": "Roghnaigh Gach Nóta sa Tuismitheoir",
|
||||
"add-note-above-to-selection": "Cuir Nóta Thuas leis an Roghnú",
|
||||
"add-note-below-to-selection": "Cuir Nóta Thíos leis an Roghnú",
|
||||
"duplicate-subtree": "Fo-chrann Dúblach",
|
||||
"open-new-tab": "Oscail Cluaisín Nua",
|
||||
"close-active-tab": "Dún an Cluaisín Gníomhach",
|
||||
"reopen-last-tab": "Athoscail an Cluaisín Deireanach",
|
||||
"activate-next-tab": "Gníomhachtaigh an Chluaisín Eile",
|
||||
"activate-previous-tab": "Gníomhachtaigh an Cluaisín Roimhe Seo",
|
||||
"open-new-window": "Oscail Fuinneog Nua",
|
||||
"toggle-system-tray-icon": "Deilbhín Tráidire an Chórais a Athrú",
|
||||
"toggle-zen-mode": "Mód Zen a athrú",
|
||||
"switch-to-first-tab": "Athraigh go dtí an Chéad Chluaisín",
|
||||
"switch-to-second-tab": "Athraigh go dtí an Dara Cluaisín",
|
||||
"switch-to-third-tab": "Athraigh go dtí an Tríú Cluaisín",
|
||||
"switch-to-fourth-tab": "Athraigh go dtí an Ceathrú Cluaisín",
|
||||
"switch-to-fifth-tab": "Athraigh go dtí an Cúigiú Cluaisín",
|
||||
"switch-to-sixth-tab": "Athraigh go dtí an Séú Cluaisín",
|
||||
"switch-to-seventh-tab": "Athraigh go dtí an Seachtú Cluaisín",
|
||||
"switch-to-eighth-tab": "Athraigh go dtí an tOchtú Cluaisín",
|
||||
"switch-to-ninth-tab": "Athraigh go dtí an Naoú Cluaisín",
|
||||
"switch-to-last-tab": "Athraigh go dtí an Cluaisín Deireanach",
|
||||
"show-note-source": "Taispeáin Foinse an Nóta",
|
||||
"show-options": "Taispeáin Roghanna",
|
||||
"show-revisions": "Taispeáin Athbhreithnithe",
|
||||
"show-recent-changes": "Taispeáin Athruithe Le Déanaí",
|
||||
"show-sql-console": "Taispeáin Consól SQL",
|
||||
"show-backend-log": "Taispeáin Logáil an Chúil",
|
||||
"show-help": "Taispeáin Cabhair",
|
||||
"show-cheatsheet": "Taispeáin Bileog Leideanna",
|
||||
"add-link-to-text": "Cuir Nasc leis an Téacs",
|
||||
"follow-link-under-cursor": "Lean an Nasc Faoin gCúrsóir",
|
||||
"insert-date-and-time-to-text": "Cuir Dáta agus Am isteach sa Téacs",
|
||||
"paste-markdown-into-text": "Greamaigh Markdown isteach sa Téacs",
|
||||
"cut-into-note": "Gearr isteach i Nóta",
|
||||
"add-include-note-to-text": "Cuir Nóta le Téacs",
|
||||
"edit-read-only-note": "Cuir Nóta Léite Amháin in Eagar",
|
||||
"add-new-label": "Cuir Lipéad Nua leis",
|
||||
"add-new-relation": "Cuir Gaol Nua leis",
|
||||
"toggle-ribbon-tab-classic-editor": "Eagarthóir Clasaiceach Cluaisín Ribín a Athrú",
|
||||
"toggle-ribbon-tab-basic-properties": "Airíonna Bunúsacha an Chluaisín Ribín a Athrú",
|
||||
"toggle-ribbon-tab-book-properties": "Airíonna Leabhar an Chluaisín Ribín a Athrú",
|
||||
"toggle-ribbon-tab-file-properties": "Airíonna Comhaid Tab Ribín a Athrú",
|
||||
"toggle-ribbon-tab-image-properties": "Airíonna Íomhá an Chluaisín Ribín a Athrú",
|
||||
"toggle-ribbon-tab-owned-attributes": "Tréithe atá faoi úinéireacht ag an gcluaisín ribín",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Tréithe Oidhreachta Cluaisín Ribín a Scor",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Tréithe Curtha Chun Cinn sa Chluaisín Ribín",
|
||||
"toggle-ribbon-tab-note-map": "Léarscáil Nótaí Tab Ribín a Athrú",
|
||||
"toggle-ribbon-tab-note-info": "Eolas Nóta Cluaisín Ribín a Athrú",
|
||||
"toggle-ribbon-tab-note-paths": "Cosáin Nóta Cluaisín Ribín a Athrú",
|
||||
"toggle-ribbon-tab-similar-notes": "Nótaí Cosúla a Athraigh an Cluaisín Ribín",
|
||||
"toggle-right-pane": "Scoránaigh an Phána Ar Dheis",
|
||||
"print-active-note": "Priontáil Nóta Gníomhach",
|
||||
"export-active-note-as-pdf": "Easpórtáil Nóta Gníomhach mar PDF",
|
||||
"open-note-externally": "Oscail Nóta go Seachtrach",
|
||||
"render-active-note": "Rindreáil Nóta Gníomhach",
|
||||
"run-active-note": "Rith Nóta Gníomhach",
|
||||
"toggle-note-hoisting": "Ardú Nótaí a Athrú",
|
||||
"unhoist-note": "Nóta Dí-Ardaithe",
|
||||
"reload-frontend-app": "Athlódáil an Aip Tosaigh",
|
||||
"open-developer-tools": "Oscail Uirlisí Forbróra",
|
||||
"find-in-text": "Aimsigh sa Téacs",
|
||||
"toggle-left-pane": "Scoránaigh an Phána Chlé",
|
||||
"toggle-full-screen": "Athraigh an Scáileán Lán",
|
||||
"zoom-out": "Zúmáil Amach",
|
||||
"zoom-in": "Zúmáil Isteach",
|
||||
"reset-zoom-level": "Athshocraigh Leibhéal Súmála",
|
||||
"copy-without-formatting": "Cóipeáil Gan Formáidiú",
|
||||
"force-save-revision": "Athbhreithniú Sábháilte Fórsála"
|
||||
},
|
||||
"login": {
|
||||
"title": "Logáil Isteach",
|
||||
"heading": "Logáil Isteach Trilium",
|
||||
"incorrect-totp": "Tá an TOTP mícheart. Déan iarracht arís.",
|
||||
"incorrect-password": "Tá an focal faire mícheart. Déan iarracht arís.",
|
||||
"password": "Pasfhocal",
|
||||
"remember-me": "Cuimhnigh orm",
|
||||
"button": "Logáil Isteach",
|
||||
"sign_in_with_sso": "Sínigh isteach le {{ ssoIssuerName }}"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Socraigh Pasfhocal",
|
||||
"heading": "Socraigh pasfhocal",
|
||||
"description": "Sula dtosaíonn tú ag úsáid Trilium ón ngréasán, ní mór duit pasfhocal a shocrú ar dtús. Úsáidfidh tú an pasfhocal seo ansin chun logáil isteach.",
|
||||
"password": "Pasfhocal",
|
||||
"password-confirmation": "Deimhniú pasfhocail",
|
||||
"button": "Socraigh pasfhocal"
|
||||
},
|
||||
"setup": {
|
||||
"heading": "Socrú Trilium Notes",
|
||||
"new-document": "Is úsáideoir nua mé, agus ba mhaith liom doiciméad Trilium nua a chruthú do mo nótaí",
|
||||
"sync-from-desktop": "Tá cás deisce agam cheana féin, agus ba mhaith liom sioncrónú a shocrú leis",
|
||||
"sync-from-server": "Tá sampla freastalaí agam cheana féin, agus ba mhaith liom sioncrónú a shocrú leis",
|
||||
"next": "Ar Aghaidh",
|
||||
"init-in-progress": "Túsú doiciméad ar siúl",
|
||||
"redirecting": "Atreorófar chuig an bhfeidhmchlár thú go luath.",
|
||||
"title": "Socrú"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Sioncrónaigh ón Deasc",
|
||||
"description": "Ní mór an socrú seo a thionscnamh ón deasc:",
|
||||
"step1": "Oscail sampla de Trilium Notes ar do dheasc.",
|
||||
"step2": "Ón Roghchlár Trilium, cliceáil Roghanna.",
|
||||
"step3": "Cliceáil ar an gcatagóir Sioncrónaigh.",
|
||||
"step4": "Athraigh seoladh an fhreastalaí go: {{- host}} agus cliceáil Sábháil.",
|
||||
"step5": "Cliceáil an cnaipe \"Tástáil sioncrónaithe\" chun a fhíorú go bhfuil an nasc rathúil.",
|
||||
"step6": "Nuair a bheidh na céimeanna seo críochnaithe agat, cliceáil {{- link}}.",
|
||||
"step6-here": "anseo"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Sioncrónaigh ón bhFreastalaí",
|
||||
"instructions": "Cuir isteach seoladh agus dintiúir freastalaí Trilium thíos le do thoil. Íoslódálfaidh sé seo an doiciméad Trilium iomlán ón bhfreastalaí agus socróidh sé sioncrónú leis. Ag brath ar mhéid an doiciméid agus luas do nasc, d'fhéadfadh sé seo tamall a thógáil.",
|
||||
"server-host": "Seoladh freastalaí Trilium",
|
||||
"server-host-placeholder": "https://<ainm óstach>:<port>",
|
||||
"proxy-server": "Freastalaí seachfhreastalaí (roghnach)",
|
||||
"proxy-server-placeholder": "https://<ainm óstach>:<port>",
|
||||
"note": "Nóta:",
|
||||
"proxy-instruction": "Má fhágann tú an socrú seachfhreastalaí bán, úsáidfear seachfhreastalaí an chórais (baineann sé leis an bhfeidhmchlár deisce amháin)",
|
||||
"password": "Pasfhocal",
|
||||
"password-placeholder": "Pasfhocal",
|
||||
"back": "Ar ais",
|
||||
"finish-setup": "Críochnaigh an socrú"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Sioncrónú ar siúl",
|
||||
"successful": "Tá an sioncrónú socraithe i gceart. Tógfaidh sé tamall go mbeidh an sioncrónú tosaigh críochnaithe. Nuair a bheidh sé déanta, atreorófar chuig an leathanach logála isteach thú.",
|
||||
"outstanding-items": "Míreanna sioncrónaithe gan réiteach:",
|
||||
"outstanding-items-default": "N/B"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Níor aimsíodh",
|
||||
"heading": "Níor aimsíodh"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "tuismitheoir:",
|
||||
"clipped-from": "Gearradh an nóta seo ó {{- url}} ar dtús",
|
||||
"child-notes": "Nótaí leanaí:",
|
||||
"no-content": "Níl aon ábhar sa nóta seo."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Dé Luain",
|
||||
"tuesday": "Dé Máirt",
|
||||
"wednesday": "Dé Céadaoin",
|
||||
"thursday": "Déardaoin",
|
||||
"friday": "Dé hAoine",
|
||||
"saturday": "Dé Sathairn",
|
||||
"sunday": "Dé Domhnaigh"
|
||||
},
|
||||
"weekdayNumber": "Seachtain {weekNumber}",
|
||||
"months": {
|
||||
"january": "Eanáir",
|
||||
"february": "Feabhra",
|
||||
"march": "Márta",
|
||||
"april": "Aibreán",
|
||||
"may": "Bealtaine",
|
||||
"june": "Meitheamh",
|
||||
"july": "Iúil",
|
||||
"august": "Lúnasa",
|
||||
"september": "Meán Fómhair",
|
||||
"october": "Deireadh Fómhair",
|
||||
"november": "Samhain",
|
||||
"december": "Nollaig"
|
||||
},
|
||||
"quarterNumber": "Ráithe {quarterNumber}",
|
||||
"special_notes": {
|
||||
"search_prefix": "Cuardaigh:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Níl an freastalaí sioncrónaithe cumraithe. Cumraigh an sioncrónú ar dtús.",
|
||||
"successful": "Tá croitheadh láimhe an fhreastalaí sioncrónaithe tar éis a bheith rathúil, tá tús curtha leis an sioncrónú."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Nótaí Folaithe",
|
||||
"search-history-title": "Stair Chuardaigh",
|
||||
"note-map-title": "Léarscáil Nótaí",
|
||||
"sql-console-history-title": "Stair Chonsól SQL",
|
||||
"shared-notes-title": "Nótaí Comhroinnte",
|
||||
"bulk-action-title": "Gníomh Bulc",
|
||||
"backend-log-title": "Logáil Cúil",
|
||||
"user-hidden-title": "Úsáideoir i bhfolach",
|
||||
"launch-bar-templates-title": "Teimpléid Barra Seolta",
|
||||
"base-abstract-launcher-title": "Tosaitheoir Bunúsach Teibí",
|
||||
"command-launcher-title": "Tosaitheoir Ordú",
|
||||
"note-launcher-title": "Tosaitheoir Nótaí",
|
||||
"script-launcher-title": "Tosaitheoir Scripte",
|
||||
"built-in-widget-title": "Giuirléid Tógtha isteach",
|
||||
"spacer-title": "Spásaire",
|
||||
"custom-widget-title": "Giuirléid Saincheaptha",
|
||||
"launch-bar-title": "Barra Lainseáil",
|
||||
"available-launchers-title": "Lainseálaithe atá ar Fáil",
|
||||
"go-to-previous-note-title": "Téigh go dtí an Nóta Roimhe Seo",
|
||||
"go-to-next-note-title": "Téigh go dtí an chéad Nóta Eile",
|
||||
"new-note-title": "Nóta Nua",
|
||||
"search-notes-title": "Cuardaigh Nótaí",
|
||||
"jump-to-note-title": "Léim go...",
|
||||
"calendar-title": "Féilire",
|
||||
"recent-changes-title": "Athruithe Le Déanaí",
|
||||
"bookmarks-title": "Leabharmharcanna",
|
||||
"command-palette": "Oscail an Pailéad Ordaithe",
|
||||
"zen-mode": "Mód Zen",
|
||||
"open-today-journal-note-title": "Oscail Nóta Dialainne an Lae Inniu",
|
||||
"quick-search-title": "Cuardach Tapa",
|
||||
"protected-session-title": "Seisiún faoi Chosaint",
|
||||
"sync-status-title": "Stádas Sioncrónaithe",
|
||||
"settings-title": "Socruithe",
|
||||
"llm-chat-title": "Comhrá le Nótaí",
|
||||
"options-title": "Roghanna",
|
||||
"appearance-title": "Dealramh",
|
||||
"shortcuts-title": "Aicearraí",
|
||||
"text-notes": "Nótaí Téacs",
|
||||
"code-notes-title": "Nótaí Cód",
|
||||
"images-title": "Íomhánna",
|
||||
"spellcheck-title": "Seiceáil litrithe",
|
||||
"password-title": "Pasfhocal",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Cúltaca",
|
||||
"sync-title": "Sioncrónaigh",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"other": "Eile",
|
||||
"advanced-title": "Ardleibhéil",
|
||||
"visible-launchers-title": "Lainseálaithe Infheicthe",
|
||||
"user-guide": "Treoir Úsáideora",
|
||||
"localization": "Teanga & Réigiún",
|
||||
"inbox-title": "Bosca isteach",
|
||||
"tab-switcher-title": "Athraitheoir Cluaisíní"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nóta nua",
|
||||
"duplicate-note-suffix": "(dúpáil)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Níl an comhad loga cúil '{{ fileName }}' ann (go fóill).",
|
||||
"reading-log-failed": "Theip ar an gcomhad loga cúil '{{ fileName }}' a léamh."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Ní féidir an cineál nóta seo a thaispeáint."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Doiciméad PDF (*.pdf)",
|
||||
"unable-to-export-message": "Níorbh fhéidir an nóta reatha a easpórtáil mar PDF.",
|
||||
"unable-to-export-title": "Ní féidir a onnmhairiú mar PDF",
|
||||
"unable-to-save-message": "Níorbh fhéidir scríobh chuig an gcomhad roghnaithe. Déan iarracht eile nó roghnaigh ceann scríbe eile.",
|
||||
"unable-to-print": "Ní féidir an nóta a phriontáil"
|
||||
},
|
||||
"tray": {
|
||||
"close": "Scoir de Trilium",
|
||||
"recents": "Nótaí le déanaí",
|
||||
"bookmarks": "Leabharmharcanna",
|
||||
"today": "Oscail nóta dialainne an lae inniu",
|
||||
"new-note": "Nóta nua",
|
||||
"show-windows": "Taispeáin fuinneoga",
|
||||
"open_new_window": "Oscail fuinneog nua",
|
||||
"tooltip": "Trilium Notes"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "Ní thacaítear le haistriú díreach ó do leagan reatha. Uasghrádaigh go dtí an leagan is déanaí v0.60.4 ar dtús agus ansin go dtí an leagan seo amháin.",
|
||||
"error_message": "Earráid le linn imirce go leagan {{version}}: {{stack}}",
|
||||
"wrong_db_version": "Tá leagan an bhunachair shonraí ({{version}}) níos nuaí ná mar a bhfuil súil ag an bhfeidhmchlár leis ({{targetVersion}}), rud a chiallaíonn gur cruthaíodh é le leagan níos nuaí agus neamh-chomhoiriúnach de Trilium. Uasghrádaigh go dtí an leagan is déanaí de Trilium chun an fhadhb seo a réiteach."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Earráid"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "Téama an tSuímh",
|
||||
"search_placeholder": "Cuardaigh...",
|
||||
"image_alt": "Íomhá an Airteagail",
|
||||
"last-updated": "Nuashonraithe go deireanach ar {{- date}}",
|
||||
"subpages": "Fo-leathanaigh:",
|
||||
"on-this-page": "Ar an Leathanach seo",
|
||||
"expand": "Leathnaigh"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"text-snippet": "Sleachta Téacs",
|
||||
"description": "Cur síos",
|
||||
"list-view": "Amharc Liosta",
|
||||
"grid-view": "Radharc Eangaí",
|
||||
"calendar": "Féilire",
|
||||
"table": "Tábla",
|
||||
"geo-map": "Léarscáil Gheografach",
|
||||
"start-date": "Dáta Tosaigh",
|
||||
"end-date": "Dáta Deiridh",
|
||||
"start-time": "Am Tosaigh",
|
||||
"end-time": "Am Deiridh",
|
||||
"geolocation": "Geoshuíomh",
|
||||
"built-in-templates": "Teimpléid ionsuite",
|
||||
"board": "Bord Kanban",
|
||||
"status": "Stádas",
|
||||
"board_note_first": "An chéad nóta",
|
||||
"board_note_second": "An dara nóta",
|
||||
"board_note_third": "An tríú nóta",
|
||||
"board_status_todo": "Le Déanamh",
|
||||
"board_status_progress": "Ar Siúl",
|
||||
"board_status_done": "Déanta",
|
||||
"presentation": "Cur i Láthair",
|
||||
"presentation_slide": "Sleamhnán cur i láthair",
|
||||
"presentation_slide_first": "An chéad sleamhnán",
|
||||
"presentation_slide_second": "An dara sleamhnán",
|
||||
"background": "Cúlra"
|
||||
},
|
||||
"sql_init": {
|
||||
"db_not_initialized_desktop": "Níl an bunachar sonraí tosaithe, lean na treoracha ar an scáileán le do thoil.",
|
||||
"db_not_initialized_server": "Níl an bunachar sonraí tosaithe, tabhair cuairt ar an leathanach socraithe - http://[your-server-host]:{{port}} le treoracha a fheiceáil maidir le conas Trilium a thosú."
|
||||
},
|
||||
"desktop": {
|
||||
"instance_already_running": "Tá sampla ag rith cheana féin, agus tá fócas á chur ar an sampla sin ina ionad."
|
||||
}
|
||||
}
|
||||
@@ -344,7 +344,8 @@
|
||||
"inbox-title": "Inbox",
|
||||
"base-abstract-launcher-title": "ベース アブストラクトランチャー",
|
||||
"command-palette": "コマンドパレットを開く",
|
||||
"zen-mode": "禅モード"
|
||||
"zen-mode": "禅モード",
|
||||
"tab-switcher-title": "タブ切り替え"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "新しいノート",
|
||||
|
||||
@@ -257,7 +257,8 @@
|
||||
"localization": "Limbă și regiune",
|
||||
"inbox-title": "Inbox",
|
||||
"command-palette": "Deschide paleta de comenzi",
|
||||
"zen-mode": "Mod zen"
|
||||
"zen-mode": "Mod zen",
|
||||
"tab-switcher-title": "Schimbător de taburi"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Notiță nouă",
|
||||
|
||||
@@ -8,6 +8,12 @@ import type { AttachmentRow } from "@triliumnext/commons";
|
||||
import type { ValidatorMap } from "./etapi-interface.js";
|
||||
|
||||
function register(router: Router) {
|
||||
eu.route(router, "get", "/etapi/notes/:noteId/attachments", (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
const attachments = note.getAttachments();
|
||||
res.json(attachments.map((attachment) => mappers.mapAttachmentToPojo(attachment)));
|
||||
});
|
||||
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT: ValidatorMap = {
|
||||
ownerId: [v.notNull, v.isNoteId],
|
||||
role: [v.notNull, v.isString],
|
||||
|
||||
@@ -2,7 +2,7 @@ import type BNote from "../becca/entities/bnote.js";
|
||||
|
||||
import attributeService from "./attributes.js";
|
||||
import cloningService from "./cloning.js";
|
||||
import { dayjs, Dayjs, getFirstDayOfWeek1, getWeekInfo, WeekSettings } from "@triliumnext/commons";
|
||||
import { dayjs, Dayjs } from "@triliumnext/commons";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
import noteService from "./notes.js";
|
||||
import optionService from "./options.js";
|
||||
@@ -63,8 +63,7 @@ function getJournalNoteTitle(
|
||||
rootNote: BNote,
|
||||
timeUnit: TimeUnit,
|
||||
dateObj: Dayjs,
|
||||
number: number,
|
||||
weekYear?: number // Optional: the week year for cross-year weeks
|
||||
number: number
|
||||
) {
|
||||
const patterns = {
|
||||
year: rootNote.getOwnedLabelValue("yearPattern") || "{year}",
|
||||
@@ -80,14 +79,9 @@ function getJournalNoteTitle(
|
||||
const numberStr = number.toString();
|
||||
const ordinalStr = ordinal(dateObj);
|
||||
|
||||
// For week notes, use the weekYear if provided (handles cross-year weeks)
|
||||
const yearForDisplay = (timeUnit === "week" && weekYear !== undefined)
|
||||
? weekYear.toString()
|
||||
: dateObj.format("YYYY");
|
||||
|
||||
const allReplacements: Record<string, string> = {
|
||||
// Common date formats
|
||||
"{year}": yearForDisplay,
|
||||
"{year}": dateObj.format("YYYY"),
|
||||
|
||||
// Month related
|
||||
"{isoMonth}": dateObj.format("YYYY-MM"),
|
||||
@@ -292,14 +286,6 @@ function getMonthNote(dateStr: string, _rootNote: BNote | null = null): BNote {
|
||||
return monthNote as unknown as BNote;
|
||||
}
|
||||
|
||||
function getWeekSettings(): WeekSettings {
|
||||
return {
|
||||
firstDayOfWeek: parseInt(optionService.getOptionOrNull("firstDayOfWeek") ?? "1", 10),
|
||||
firstWeekOfYear: parseInt(optionService.getOptionOrNull("firstWeekOfYear") ?? "0", 10),
|
||||
minDaysInFirstWeek: parseInt(optionService.getOptionOrNull("minDaysInFirstWeek") ?? "4", 10)
|
||||
};
|
||||
}
|
||||
|
||||
function getWeekStartDate(date: Dayjs): Dayjs {
|
||||
const firstDayISO = parseInt(optionService.getOptionOrNull("firstDayOfWeek") ?? "1", 10);
|
||||
const day = date.isoWeekday();
|
||||
@@ -308,8 +294,9 @@ function getWeekStartDate(date: Dayjs): Dayjs {
|
||||
}
|
||||
|
||||
function getWeekNumberStr(date: Dayjs): string {
|
||||
const { weekYear, weekNumber } = getWeekInfo(date, getWeekSettings());
|
||||
return `${weekYear}-W${weekNumber.toString().padStart(2, "0")}`;
|
||||
const isoYear = date.isoWeekYear();
|
||||
const isoWeekNum = date.isoWeek();
|
||||
return `${isoYear}-W${isoWeekNum.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function getWeekFirstDayNote(dateStr: string, rootNote: BNote | null = null) {
|
||||
@@ -342,19 +329,17 @@ function getWeekNote(weekStr: string, _rootNote: BNote | null = null): BNote | n
|
||||
|
||||
const [ yearStr, weekNumStr ] = weekStr.trim().split("-W");
|
||||
const weekNumber = parseInt(weekNumStr);
|
||||
const weekYear = parseInt(yearStr);
|
||||
|
||||
// Calculate week start date based on user's first week of year settings.
|
||||
// This correctly handles cross-year weeks based on user preferences.
|
||||
const firstDayOfWeek1 = getFirstDayOfWeek1(weekYear, getWeekSettings());
|
||||
const startDate = firstDayOfWeek1.add(weekNumber - 1, "week");
|
||||
const endDate = startDate.add(6, "day");
|
||||
const firstDayOfYear = dayjs().year(parseInt(yearStr)).month(0).date(1);
|
||||
const weekStartDate = firstDayOfYear.add(weekNumber - 1, "week");
|
||||
const startDate = getWeekStartDate(weekStartDate);
|
||||
const endDate = dayjs(startDate).add(6, "day");
|
||||
|
||||
const startMonth = startDate.month();
|
||||
const endMonth = endDate.month();
|
||||
|
||||
const monthNote = getMonthNote(startDate.format("YYYY-MM-DD"), rootNote);
|
||||
const noteTitle = getJournalNoteTitle(rootNote, "week", startDate, weekNumber, weekYear);
|
||||
const noteTitle = getJournalNoteTitle(rootNote, "week", startDate, weekNumber);
|
||||
|
||||
sql.transactional(() => {
|
||||
weekNote = createNote(monthNote, noteTitle);
|
||||
|
||||
@@ -40,18 +40,24 @@ export default function buildLaunchBarConfig() {
|
||||
type: "launcher",
|
||||
command: "showRecentChanges",
|
||||
icon: "bx bx-history"
|
||||
}
|
||||
},
|
||||
searchNotes: {
|
||||
title: t("hidden-subtree.search-notes-title"),
|
||||
type: "launcher",
|
||||
command: "searchNotes",
|
||||
icon: "bx bx-search",
|
||||
},
|
||||
bookmarks: {
|
||||
title: t("hidden-subtree.bookmarks-title"),
|
||||
type: "launcher",
|
||||
builtinWidget: "bookmarks",
|
||||
icon: "bx bx-bookmark"
|
||||
},
|
||||
};
|
||||
|
||||
const desktopAvailableLaunchers: HiddenSubtreeItem[] = [
|
||||
{
|
||||
id: "_lbBackInHistory",
|
||||
...sharedLaunchers.backInHistory
|
||||
},
|
||||
{
|
||||
id: "_lbForwardInHistory",
|
||||
...sharedLaunchers.forwardInHistory
|
||||
},
|
||||
{ id: "_lbBackInHistory", ...sharedLaunchers.backInHistory },
|
||||
{ id: "_lbForwardInHistory", ...sharedLaunchers.forwardInHistory },
|
||||
{
|
||||
id: "_commandPalette",
|
||||
title: t("hidden-subtree.command-palette"),
|
||||
@@ -82,11 +88,7 @@ export default function buildLaunchBarConfig() {
|
||||
},
|
||||
{
|
||||
id: "_lbSearch",
|
||||
title: t("hidden-subtree.search-notes-title"),
|
||||
type: "launcher",
|
||||
command: "searchNotes",
|
||||
icon: "bx bx-search",
|
||||
attributes: [{ type: "label", name: "desktopOnly" }]
|
||||
...sharedLaunchers.searchNotes
|
||||
},
|
||||
{
|
||||
id: "_lbJumpTo",
|
||||
@@ -128,13 +130,7 @@ export default function buildLaunchBarConfig() {
|
||||
baseSize: "50",
|
||||
growthFactor: "0"
|
||||
},
|
||||
{
|
||||
id: "_lbBookmarks",
|
||||
title: t("hidden-subtree.bookmarks-title"),
|
||||
type: "launcher",
|
||||
builtinWidget: "bookmarks",
|
||||
icon: "bx bx-bookmark"
|
||||
},
|
||||
{ id: "_lbBookmarks", ...sharedLaunchers.bookmarks },
|
||||
{
|
||||
id: "_lbToday",
|
||||
...sharedLaunchers.openToday
|
||||
@@ -179,22 +175,15 @@ export default function buildLaunchBarConfig() {
|
||||
|
||||
const mobileAvailableLaunchers: HiddenSubtreeItem[] = [
|
||||
{ id: "_lbMobileNewNote", ...sharedLaunchers.newNote },
|
||||
{ id: "_lbMobileSearchNotes", ...sharedLaunchers.searchNotes },
|
||||
{ id: "_lbMobileToday", ...sharedLaunchers.openToday },
|
||||
{
|
||||
id: "_lbMobileRecentChanges",
|
||||
...sharedLaunchers.recentChanges
|
||||
}
|
||||
{ id: "_lbMobileRecentChanges", ...sharedLaunchers.recentChanges },
|
||||
{ id: "_lbMobileBookmarks", ...sharedLaunchers.bookmarks }
|
||||
];
|
||||
|
||||
const mobileVisibleLaunchers: HiddenSubtreeItem[] = [
|
||||
{
|
||||
id: "_lbMobileBackInHistory",
|
||||
...sharedLaunchers.backInHistory
|
||||
},
|
||||
{
|
||||
id: "_lbMobileForwardInHistory",
|
||||
...sharedLaunchers.forwardInHistory
|
||||
},
|
||||
{ id: "_lbMobileBackInHistory", ...sharedLaunchers.backInHistory },
|
||||
{ id: "_lbMobileForwardInHistory", ...sharedLaunchers.forwardInHistory },
|
||||
{
|
||||
id: "_lbMobileJumpTo",
|
||||
title: t("hidden-subtree.jump-to-note-title"),
|
||||
@@ -210,7 +199,8 @@ export default function buildLaunchBarConfig() {
|
||||
id: "_lbMobileTabSwitcher",
|
||||
title: t("hidden-subtree.tab-switcher-title"),
|
||||
type: "launcher",
|
||||
builtinWidget: "mobileTabSwitcher"
|
||||
builtinWidget: "mobileTabSwitcher",
|
||||
icon: "bx bx-rectangle"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -41,7 +41,12 @@
|
||||
"search_title": "البحث القوي",
|
||||
"web_clipper_title": "اداة قص الويب",
|
||||
"title": "الانتاجية والسلامة",
|
||||
"jump_to_title": "الاوامر والبحث السريع"
|
||||
"jump_to_title": "الاوامر والبحث السريع",
|
||||
"revisions_content": "تُحفظ الملاحظات دوريًا في الخلفية، ويمكن استخدام التعديلات للمراجعة أو للتراجع عن التغييرات غير المقصودة. كما يمكن إنشاء التعديلات عند الطلب.",
|
||||
"sync_content": "استخدم نسخة مستضافة ذاتيًا أو نسخة سحابية لمزامنة ملاحظاتك بسهولة عبر أجهزة متعددة، وللوصول إليها من هاتفك المحمول باستخدام تطبيق ويب تقدمي (PWA).",
|
||||
"protected_notes_content": "احمِ معلوماتك الشخصية الحساسة عبر تشفير الملاحظات وقفلها خلف جلسة محمية بكلمة مرور.",
|
||||
"jump_to_content": "انتقل بسرعة إلى الملاحظات أو أوامر واجهة المستخدم عبر التسلسل الهرمي من خلال البحث عن عناوينها، مع ميزة المطابقة التقريبية لتجاوز الأخطاء الإملائية أو الاختلافات البسيطة.",
|
||||
"search_content": "أو ابحث عن نص داخل الملاحظات وضيّق نطاق البحث عبر التصفية حسب الملاحظة الرئيسية، أو حسب مستوى التفرع."
|
||||
},
|
||||
"note_types": {
|
||||
"canvas_title": "مساحة العمل",
|
||||
|
||||
200
apps/website/src/translations/ga/translation.json
Normal file
200
apps/website/src/translations/ga/translation.json
Normal file
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"get-started": {
|
||||
"title": "Tosaigh",
|
||||
"desktop_title": "Íoslódáil an feidhmchlár deisce (v{{version}})",
|
||||
"architecture": "Ailtireacht:",
|
||||
"older_releases": "Féach ar eisiúintí níos sine",
|
||||
"server_title": "Socraigh freastalaí le haghaidh rochtana ar ilghléasanna"
|
||||
},
|
||||
"hero_section": {
|
||||
"title": "Eagraigh do chuid smaointe. Tóg do bhunachar eolais pearsanta.",
|
||||
"subtitle": "Is réiteach foinse oscailte é Trilium chun nótaí a thógáil agus bunachar eolais pearsanta a eagrú. Bain úsáid as go háitiúil ar do dheasc, nó sioncrónaigh é le do fhreastalaí féinóstáilte chun do nótaí a choinneáil cibé áit a théann tú.",
|
||||
"get_started": "Tosaigh",
|
||||
"github": "GitHub",
|
||||
"dockerhub": "Docker Hub",
|
||||
"screenshot_alt": "Scáileán den fheidhmchlár deisce Trilium Notes"
|
||||
},
|
||||
"organization_benefits": {
|
||||
"title": "Eagraíocht",
|
||||
"note_structure_title": "Struchtúr nótaí",
|
||||
"note_structure_description": "Is féidir nótaí a shocrú go hiarlathach. Níl aon ghá le fillteáin, ós rud é gur féidir fo-nótaí a bheith i ngach nóta. Is féidir nóta aonair a chur leis i roinnt áiteanna san ordlathas.",
|
||||
"attributes_title": "Lipéid nótaí agus caidrimh",
|
||||
"attributes_description": "Bain úsáid as caidrimh idir nótaí nó cuir lipéid leis le haghaidh catagóiriú éasca. Bain úsáid as tréithe ardaithe chun faisnéis struchtúrtha a iontráil ar féidir a úsáid i dtáblaí agus i gcláir.",
|
||||
"hoisting_title": "Spásanna oibre agus ardaitheoir",
|
||||
"hoisting_description": "Deighil do nótaí pearsanta agus oibre go héasca trí iad a ghrúpáil faoi spás oibre, rud a dhíríonn ar do chrann nótaí chun sraith nótaí ar leith amháin a thaispeáint."
|
||||
},
|
||||
"productivity_benefits": {
|
||||
"title": "Táirgiúlacht agus sábháilteacht",
|
||||
"revisions_title": "Athbhreithnithe nóta",
|
||||
"revisions_content": "Sábháiltear nótaí go tréimhsiúil sa chúlra agus is féidir athbhreithnithe a úsáid le haghaidh athbhreithnithe nó chun athruithe de thaisme a chealú. Is féidir athbhreithnithe a chruthú ar éileamh freisin.",
|
||||
"sync_title": "Sioncrónú",
|
||||
"sync_content": "Bain úsáid as cás féinóstáilte nó scamall chun do nótaí a shioncrónú go héasca ar fud ilghléasanna, agus chun rochtain a fháil orthu ó do ghuthán póca ag baint úsáide as PWA.",
|
||||
"protected_notes_title": "Nótaí faoi chosaint",
|
||||
"protected_notes_content": "Cosain faisnéis phearsanta íogair trí na nótaí a chriptiú agus iad a ghlasáil taobh thiar de sheisiún atá cosanta ag pasfhocal.",
|
||||
"jump_to_title": "Cuardach tapa agus orduithe",
|
||||
"jump_to_content": "Léim go tapa chuig nótaí nó orduithe UI ar fud an ordlathais trí chuardach a dhéanamh ar a dteideal, le meaitseáil doiléir chun clóscríobh nó difríochtaí beaga a chur san áireamh.",
|
||||
"search_title": "Cuardach cumhachtach",
|
||||
"search_content": "Nó déan cuardach ar théacs laistigh de nótaí agus caolaigh an cuardach trí scagadh a dhéanamh de réir an nóta tuismitheora, nó de réir doimhneachta.",
|
||||
"web_clipper_title": "Gearrthóir gréasáin",
|
||||
"web_clipper_content": "Gabh leathanaigh ghréasáin (nó scáileáin) agus cuir iad go díreach i Trilium ag baint úsáide as síneadh brabhsálaí an ghearrthóra gréasáin."
|
||||
},
|
||||
"note_types": {
|
||||
"title": "Ilbhealaí chun d’fhaisnéis a léiriú",
|
||||
"text_title": "Nótaí téacs",
|
||||
"text_description": "Déantar na nótaí a chur in eagar ag baint úsáide as eagarthóir amhairc (WYSIWYG), a thacaíonn le táblaí, íomhánna, nathanna matamaitice, bloic chóid le haibhsiú comhréire. Formáidigh an téacs go tapa ag baint úsáide as comhréir cosúil le Markdown nó ag baint úsáide as orduithe slaise.",
|
||||
"code_title": "Nótaí cóid",
|
||||
"code_description": "Úsáideann samplaí móra de chód foinse nó scripteanna eagarthóir tiomnaithe, le haibhsiú comhréire do go leor teangacha ríomhchlárúcháin agus le téamaí dathanna éagsúla.",
|
||||
"file_title": "Nótaí comhaid",
|
||||
"file_description": "Cuir comhaid ilmheán ar nós PDFanna, íomhánna, físeáin le chéile le réamhamharc san fheidhmchlár.",
|
||||
"canvas_title": "Canbhás",
|
||||
"canvas_description": "Socraigh cruthanna, íomhánna agus téacs ar chanbhás gan teorainn, ag baint úsáide as an teicneolaíocht chéanna atá taobh thiar de excalidraw.com. Oiriúnach do léaráidí, sceitsí agus pleanáil amhairc.",
|
||||
"mermaid_title": "Léaráidí maighdeana mara",
|
||||
"mermaid_description": "Cruthaigh léaráidí ar nós cairteacha sreafa, léaráidí ranga agus seicheamhacha, cairteacha Gantt agus go leor eile, ag baint úsáide as comhréir Mermaid.",
|
||||
"mindmap_title": "Léarscáil intinne",
|
||||
"mindmap_description": "Eagraigh do chuid smaointe go hamhairc nó déan seisiún smaointeoireachta.",
|
||||
"others_list": "agus cinn eile: <0>léarscáil nótaí</0>, <1>léarscáil gaoil</1>, <2>cuardaigh shábháilte</2>, <3>nóta rindreála</3>, agus <4>radhairc ghréasáin</4>."
|
||||
},
|
||||
"extensibility_benefits": {
|
||||
"title": "Comhroinnt & inleathnú",
|
||||
"import_export_title": "Iompórtáil/onnmhairiú",
|
||||
"import_export_description": "Idirghníomhaigh go héasca le feidhmchláir eile ag baint úsáide as formáidí Markdown, ENEX, OML.",
|
||||
"share_title": "Comhroinn nótaí ar an ngréasán",
|
||||
"share_description": "Má tá freastalaí agat, is féidir é a úsáid chun fo-thacar de do nótaí a roinnt le daoine eile.",
|
||||
"scripting_title": "Scriptiú ardleibhéil",
|
||||
"scripting_description": "Tóg do chomhtháthú féin laistigh de Trilium le giuirléidí saincheaptha, nó loighic taobh an fhreastalaí.",
|
||||
"api_title": "REST API",
|
||||
"api_description": "Idirghníomhaigh le Trilium go ríomhchláraitheach ag baint úsáide as a REST API ionsuite."
|
||||
},
|
||||
"collections": {
|
||||
"title": "Bailiúcháin",
|
||||
"calendar_title": "Féilire",
|
||||
"calendar_description": "Eagraigh d’imeachtaí pearsanta nó gairmiúla ag baint úsáide as féilire, le tacaíocht d’imeachtaí uile-lae agus il-lae. Féach ar d’imeachtaí go tapa leis na radhairc seachtaine, míosa agus bliana. Idirghníomhaíocht éasca chun imeachtaí a chur leis nó a tharraingt.",
|
||||
"table_title": "Tábla",
|
||||
"table_description": "Taispeáin agus cuir in eagar faisnéis faoi nótaí i struchtúr táblach, le cineálacha éagsúla colún amhail téacs, uimhir, boscaí seiceála, dáta & am, naisc agus dathanna agus tacaíocht do chaidrimh. De rogha air sin, taispeáin na nótaí laistigh de ordlathas crainn taobh istigh den tábla.",
|
||||
"board_title": "Bord Kanban",
|
||||
"board_description": "Eagraigh stádas do thascanna nó do thionscadail i mbord Kanban le bealach éasca chun míreanna agus colúin nua a chruthú agus a stádas a athrú go simplí trí tharraingt trasna an chláir.",
|
||||
"geomap_title": "Geo-léarscáil",
|
||||
"geomap_description": "Pleanáil do laethanta saoire nó marcáil do phointí spéise go díreach ar léarscáil gheografach ag baint úsáide as marcóirí saincheaptha. Taispeáin rianta GPX taifeadta chun bealaí taistil a rianú.",
|
||||
"presentation_title": "Cur i Láthair",
|
||||
"presentation_description": "Eagraigh faisnéis i sleamhnáin agus cuir i láthair iad i lánscáileán le haistrithe réidhe. Is féidir na sleamhnáin a onnmhairiú go PDF freisin le go mbeidh sé éasca iad a roinnt."
|
||||
},
|
||||
"faq": {
|
||||
"title": "Ceisteanna Coitianta",
|
||||
"mobile_question": "An bhfuil feidhmchlár soghluaiste ann?",
|
||||
"mobile_answer": "Faoi láthair níl aon aip shoghluaiste oifigiúil ann. Mar sin féin, má tá freastalaí agat is féidir leat rochtain a fháil air trí bhrabhsálaí gréasáin a úsáid agus fiú é a shuiteáil mar PWA. I gcás Android, tá aip neamhoifigiúil ann ar a dtugtar TriliumDroid a oibríonn as líne fiú (cosúil le cliant deisce).",
|
||||
"database_question": "Cá bhfuil na sonraí stóráilte?",
|
||||
"database_answer": "Stórálfar do nótaí go léir i mbunachar sonraí SQLite i bhfillteán feidhmchláir. Is é an chúis a n-úsáideann Trilium bunachar sonraí in ionad comhaid téacs simplí ná feidhmíocht agus go mbeadh roinnt gnéithe i bhfad níos deacra a chur i bhfeidhm amhail clóin (an nóta céanna in áiteanna éagsúla sa chrann). Chun an fillteán feidhmchláir a aimsiú, téigh go dtí an fhuinneog Maidir Linn.",
|
||||
"server_question": "An bhfuil freastalaí ag teastáil uaim le Trilium a úsáid?",
|
||||
"server_answer": "Ní hea, ceadaíonn an freastalaí rochtain trí bhrabhsálaí gréasáin agus bainistíonn sé an sioncrónú má tá ilghléasanna agat. Chun tús a chur leis, is leor an feidhmchlár deisce a íoslódáil agus tosú ag baint úsáide as.",
|
||||
"scaling_question": "Cé chomh maith agus a scálaíonn an feidhmchlár le líon mór nótaí?",
|
||||
"scaling_answer": "Ag brath ar úsáid, ba cheart go mbeadh an feidhmchlár in ann 100,000 nóta ar a laghad a láimhseáil gan fadhb. Tabhair faoi deara go bhféadfadh teip a bheith ar an bpróiseas sioncrónaithe uaireanta má tá go leor comhad mór á uaslódáil (1 GB in aghaidh an chomhaid) ós rud é go bhfuil Trilium beartaithe níos mó mar fheidhmchlár bonn eolais seachas stór comhad (cosúil le NextCloud, mar shampla).",
|
||||
"network_share_question": "An féidir liom mo bhunachar sonraí a roinnt thar thiomántán líonra?",
|
||||
"network_share_answer": "Ní hea, ní smaoineamh maith é bunachar sonraí SQLite a roinnt thar thiomántán líonra i gcoitinne. Cé go bhféadfadh sé oibriú uaireanta, tá seans ann go ndéanfar an bunachar sonraí a thruailliú mar gheall ar ghlasanna comhad neamhfhoirfe thar líonra.",
|
||||
"security_question": "Conas a chosnaítear mo chuid sonraí?",
|
||||
"security_answer": "De réir réamhshocraithe, ní chriptítear nótaí agus is féidir iad a léamh go díreach ón mbunachar sonraí. Nuair a mharcáiltear nóta mar chriptithe, déantar an nóta a chriptiú ag baint úsáide as AES-128-CBC."
|
||||
},
|
||||
"final_cta": {
|
||||
"title": "Réidh le tosú le Trilium Notes?",
|
||||
"description": "Tóg do bhunachar eolais pearsanta le gnéithe cumhachtacha agus príobháideacht iomlán.",
|
||||
"get_started": "Tosaigh"
|
||||
},
|
||||
"components": {
|
||||
"link_learn_more": "Foghlaim níos mó..."
|
||||
},
|
||||
"download_now": {
|
||||
"text": "Íoslódáil anois ",
|
||||
"platform_big": "v{{version}} do {{platform}}",
|
||||
"platform_small": "do {{platform}}",
|
||||
"linux_big": "v{{version}} do Linux",
|
||||
"linux_small": "do Linux",
|
||||
"more_platforms": "Tuilleadh ardán & socrú freastalaí"
|
||||
},
|
||||
"header": {
|
||||
"get-started": "Tosaigh",
|
||||
"documentation": "Doiciméadú",
|
||||
"support-us": "Tacaigh linn"
|
||||
},
|
||||
"footer": {
|
||||
"copyright_and_the": " agus an ",
|
||||
"copyright_community": "pobal"
|
||||
},
|
||||
"social_buttons": {
|
||||
"github": "GitHub",
|
||||
"github_discussions": "Pléanna GitHub",
|
||||
"matrix": "Maitrís",
|
||||
"reddit": "Reddit"
|
||||
},
|
||||
"support_us": {
|
||||
"title": "Tacaigh linn",
|
||||
"financial_donations_title": "Síntiúis airgeadais",
|
||||
"financial_donations_description": "Tógtar agus cothaítear Trilium le <Link>na céadta uair an chloig oibre</Link>. Coinníonn do thacaíocht é foinse oscailte, feabhsaíonn sé gnéithe, agus clúdaíonn sé costais amhail óstáil.",
|
||||
"financial_donations_cta": "Smaoinigh ar thacaíocht a thabhairt don phríomhfhorbróir (<Link>eliandoran</Link>) den fheidhmchlár trí:",
|
||||
"github_sponsors": "Urraitheoirí GitHub",
|
||||
"paypal": "PayPal",
|
||||
"buy_me_a_coffee": "Ceannaigh Caife Dom"
|
||||
},
|
||||
"contribute": {
|
||||
"title": "Bealaí eile chun ranníocaíocht a dhéanamh",
|
||||
"way_translate": "Aistrigh an feidhmchlár go do theanga dhúchais trí <Link>Weblate</Link>.",
|
||||
"way_community": "Déan idirghníomhú leis an bpobal ar <Discussions>GitHub Discussions</Discussions> nó ar <Matrix>Matrix</Matrix>.",
|
||||
"way_reports": "Tuairiscigh fabhtanna trí <Link>Fadhbanna GitHub</Link>.",
|
||||
"way_document": "Feabhas a chur ar an doiciméadacht trí eolas a thabhairt dúinn faoi bhearnaí sa doiciméadacht nó trí threoracha, Ceisteanna Coitianta nó ranganna teagaisc a chur ar fáil.",
|
||||
"way_market": "Scaip an scéal: Roinn Nótaí Trilium le cairde, nó ar bhlaganna agus ar na meáin shóisialta."
|
||||
},
|
||||
"404": {
|
||||
"title": "404: Níor aimsíodh",
|
||||
"description": "Níorbh fhéidir an leathanach a bhí á lorg agat a aimsiú. B’fhéidir gur scriosadh é nó go bhfuil an URL mícheart."
|
||||
},
|
||||
"download_helper_desktop_windows": {
|
||||
"title_x64": "Windows 64-bit",
|
||||
"title_arm64": "Windows ar ARM",
|
||||
"description_x64": "Ag luí le gléasanna Intel nó AMD a bhfuil Windows 10 agus 11 á rith acu.",
|
||||
"description_arm64": "Ag luí le gléasanna ARM (m.sh. le Qualcomm Snapdragon).",
|
||||
"quick_start": "Chun a shuiteáil trí Winget:",
|
||||
"download_exe": "Íoslódáil an Suiteálaí (.exe)",
|
||||
"download_zip": "Iniompartha (.zip)",
|
||||
"download_scoop": "Scúp"
|
||||
},
|
||||
"download_helper_desktop_linux": {
|
||||
"title_x64": "Linux 64-bit",
|
||||
"title_arm64": "Linux ar ARM",
|
||||
"description_x64": "Don chuid is mó de na dáiltí Linux, comhoiriúnach le hailtireacht x86_64.",
|
||||
"description_arm64": "I gcás dáiltí Linux bunaithe ar ARM, comhoiriúnach le hailtireacht aarch64.",
|
||||
"quick_start": "Roghnaigh formáid phacáiste chuí, ag brath ar do dháileadh:",
|
||||
"download_deb": ".deb",
|
||||
"download_rpm": ".rpm",
|
||||
"download_flatpak": ".flatpak",
|
||||
"download_zip": "Iniompartha (.zip)",
|
||||
"download_nixpkgs": "nixpkgs",
|
||||
"download_aur": "AUR"
|
||||
},
|
||||
"download_helper_desktop_macos": {
|
||||
"title_x64": "macOS do Intel",
|
||||
"title_arm64": "macOS do Apple Silicon",
|
||||
"description_x64": "Do Macs bunaithe ar Intel a bhfuil macOS Monterey nó níos déanaí á rith acu.",
|
||||
"description_arm64": "Do ríomhairí Mac Apple Silicon ar nós iad siúd a bhfuil sceallóga M1 agus M2 acu.",
|
||||
"quick_start": "Chun a shuiteáil trí Homebrew:",
|
||||
"download_dmg": "Íoslódáil an Suiteálaí (.dmg)",
|
||||
"download_homebrew_cask": "Homebrew Cask",
|
||||
"download_zip": "Iniompartha (.zip)"
|
||||
},
|
||||
"download_helper_server_docker": {
|
||||
"title": "Féinóstáilte ag baint úsáide as Docker",
|
||||
"description": "Imscaradh go héasca ar Windows, Linux nó macOS ag baint úsáide as coimeádán Docker.",
|
||||
"download_dockerhub": "Docker Hub",
|
||||
"download_ghcr": "ghcr.io"
|
||||
},
|
||||
"download_helper_server_linux": {
|
||||
"title": "Féinóstáilte ar Linux",
|
||||
"description": "Imscar Trilium Notes ar do fhreastalaí nó VPS féin, atá comhoiriúnach leis an gcuid is mó de na dáileacháin.",
|
||||
"download_tar_x64": "x64 (.tar.xz)",
|
||||
"download_tar_arm64": "ARM (.tar.xz)",
|
||||
"download_nixos": "Modúl NixOS"
|
||||
},
|
||||
"download_helper_server_hosted": {
|
||||
"title": "Óstáil íoctha",
|
||||
"description": "Nótaí Trilium atá á n-óstáil ar PikaPods, seirbhís íoctha le haghaidh rochtana agus bainistíochta éasca. Níl baint dhíreach aige le foireann Trilium.",
|
||||
"download_pikapod": "Socraigh ar PikaPods",
|
||||
"download_triliumcc": "Nó féach ar trilium.cc"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Documentation
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/jaeLd6kJGrRa/Documentation_image.png" width="205" height="162">
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/qIpo9UiTZNdm/Documentation_image.png" width="205" height="162">
|
||||
|
||||
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
||||
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user