Merge branch 'develop' into quick_search_in_autocomplete

This commit is contained in:
SiriusXT 2024-11-26 15:49:11 +08:00
commit 0d9e13c1e0
49 changed files with 860 additions and 272 deletions

View File

@ -3,6 +3,12 @@ description: Report a bug
title: "(Bug report) "
labels: "Type: Bug"
body:
- type: textarea
attributes:
label: Description
description: A clear and concise description of the bug and any additional information.
validations:
required: true
- type: input
attributes:
label: TriliumNext Version
@ -38,12 +44,6 @@ body:
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
validations:
required: true
- type: textarea
attributes:
label: Description
description: A clear and concise description of the bug and any additional information.
validations:
required: true
- type: textarea
attributes:
label: Error logs

View File

@ -17,17 +17,45 @@
#
# --------------------------------------------------------------------------------------------------
number_of_keys() {
[ -f "$1" ] && jq 'path(..) | select(length == 2) | .[1]' "$1" | wc -l || echo "0"
}
stats() {
# Print the number of existing strings on the JSON files for each locale
s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/en/server.json" | wc -l)
c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/en/translation.json" | wc -l)
echo "|locale |server strings |client strings |"
echo "|-------|---------------|---------------|"
echo "| en | ${s} | ${c} |"
s=$(number_of_keys "${paths[0]}/en/server.json")
c=$(number_of_keys "${paths[1]}/en/translation.json")
echo "| locale |server strings |client strings |"
echo "|--------|---------------|---------------|"
echo "| en | ${s} | ${c} |"
for locale in "${locales[@]}"; do
s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/${locale}/server.json" | wc -l)
c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/${locale}/translation.json" | wc -l)
echo "| ${locale} | ${s} | ${c} |"
s=$(number_of_keys "${paths[0]}/${locale}/server.json")
c=$(number_of_keys "${paths[1]}/${locale}/translation.json")
n1=$(((8 - ${#locale}) / 2))
n2=$((n1 == 1 ? n1 + 1 : n1))
echo "|$(printf "%${n1}s")${locale}$(printf "%${n2}s")| ${s} | ${c} |"
done
}
update_1() {
# Update PO files from English and localized JSON files as source
# NOTE: if you want a new language you need to first create the JSON files
# on their corresponding place with `{}` as content to avoid error on `json2po`
local locales=("$@")
for path in "${paths[@]}"; do
for locale in "${locales[@]}"; do
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
done
done
}
update_2() {
# Recover translation from PO files to localized JSON files
local locales=("$@")
for path in "${paths[@]}"; do
for locale in "${locales[@]}"; do
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
done
done
}
@ -35,11 +63,11 @@ help() {
echo -e "\nDescription:"
echo -e "\tCreate PO files to make easier the labor of translation"
echo -e "\nUsage:"
echo -e "\t./translation.sh [--stats] [--update <OPT_LOCALE>] [--update2 <OPT_LOCALE>]"
echo -e "\t./translation.sh [--stats] [--update1 <OPT_LOCALE>] [--update2 <OPT_LOCALE>]"
echo -e "\nFlags:"
echo -e " --clear\n\tClear all po-* directories"
echo -e " --stats\n\tPrint the number of existing strings on the JSON files for each locale"
echo -e " --update <LOCALE>\n\tUpdate PO files from English and localized JSON files as source"
echo -e " --update1 <LOCALE>\n\tUpdate PO files from English and localized JSON files as source"
echo -e " --update2 <LOCALE>\n\tRecover translation from PO files to localized JSON files"
}
@ -51,7 +79,7 @@ file_path="$(
pwd -P
)"
paths=("${file_path}/../translations/" "${file_path}/../src/public/translations/")
locales=(cn es fr ro)
locales=(cn de es fr pt_br ro tw)
if [ $# -eq 1 ]; then
if [ "$1" == "--clear" ]; then
@ -62,34 +90,18 @@ if [ $# -eq 1 ]; then
done
elif [ "$1" == "--stats" ]; then
stats
elif [ "$1" == "--update" ]; then
# Update PO files from English and localized JSON files as source
for path in "${paths[@]}"; do
for locale in "${locales[@]}"; do
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
done
done
elif [ "$1" == "--update1" ]; then
update_1 "${locales[@]}"
elif [ "$1" == "--update2" ]; then
# Recover translation from PO files to localized JSON files
for path in "${paths[@]}"; do
for locale in "${locales[@]}"; do
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
done
done
update_2 "${locales[@]}"
else
help
fi
elif [ $# -eq 2 ]; then
if [ "$1" == "--update" ]; then
locale="$2"
for path in "${paths[@]}"; do
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
done
if [ "$1" == "--update1" ]; then
update_1 "$2"
elif [ "$1" == "--update2" ]; then
locale="$2"
for path in "${paths[@]}"; do
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
done
update_2 "$2"
else
help
fi

Binary file not shown.

1
libraries/mermaid-elk/elk.min.js vendored Normal file

File diff suppressed because one or more lines are too long

13
libraries/mermaid-elk/package-lock.json generated Normal file
View File

@ -0,0 +1,13 @@
{
"name": "mermaid-elk",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mermaid-elk",
"version": "1.0.0",
"license": "ISC"
}
}
}

View File

@ -0,0 +1,13 @@
{
"name": "mermaid-elk",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "cross-env node --import ../../loader-register.js ../../node_modules/webpack/bin/webpack.js -c webpack.config.cjs"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {}
}

View File

@ -0,0 +1,19 @@
const path = require("path");
const webpack = require("webpack");
module.exports = {
mode: "production",
entry: "../../node_modules/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs",
output: {
library: "MERMAID_ELK",
filename: "elk.min.js",
path: path.resolve(__dirname),
libraryTarget: "umd",
libraryExport: "default"
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
]
}

22
package-lock.json generated
View File

@ -1,18 +1,19 @@
{
"name": "trilium",
"version": "0.90.11-beta",
"version": "0.90.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.90.11-beta",
"version": "0.90.12",
"license": "AGPL-3.0-only",
"dependencies": {
"@braintree/sanitize-url": "7.1.0",
"@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "0.17.6",
"@highlightjs/cdn-assets": "11.10.0",
"@mermaid-js/layout-elk": "0.1.5",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"autocomplete.js": "0.38.1",
@ -3072,6 +3073,18 @@
"url": "https://github.com/malept/cross-spawn-promise?sponsor=1"
}
},
"node_modules/@mermaid-js/layout-elk": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@mermaid-js/layout-elk/-/layout-elk-0.1.5.tgz",
"integrity": "sha512-6ML4iWdVdyIkSW47KiID9runHzaomLxdMfNo9U60LJvfcQkB/FAjg0Vjc4AZEQnnBq7ibAoAknAWlT1XetwXSg==",
"dependencies": {
"d3": "^7.9.0",
"elkjs": "^0.9.3"
},
"peerDependencies": {
"mermaid": "^11.0.0"
}
},
"node_modules/@mermaid-js/parser": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz",
@ -7904,6 +7917,11 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/elkjs": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz",
"integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="
},
"node_modules/emitter-listener": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz",

View File

@ -2,7 +2,7 @@
"name": "trilium",
"productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.90.11-beta",
"version": "0.90.12",
"license": "AGPL-3.0-only",
"main": "./dist/electron-main.js",
"author": {
@ -54,6 +54,7 @@
"@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "0.17.6",
"@highlightjs/cdn-assets": "11.10.0",
"@mermaid-js/layout-elk": "0.1.5",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"autocomplete.js": "0.38.1",

View File

@ -88,8 +88,11 @@ export default class Component {
if (fun) {
return this.callMethod(fun, data);
}
else {
} else {
if (!this.parent) {
throw new Error(`Component "${this.componentId}" does not have a parent attached to propagate a command.`);
}
return this.parent.triggerCommand(name, data);
}
}

View File

@ -83,6 +83,7 @@ import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js";
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
import options from "../services/options.js";
export default class DesktopLayout {
constructor(customWidgets) {
@ -92,112 +93,120 @@ export default class DesktopLayout {
getRootWidget(appContext) {
appContext.noteTreeWidget = new NoteTreeWidget();
return new RootContainer()
const launcherPaneIsHorizontal = (options.get("layoutOrientation") === "horizontal");
const launcherPane = this.#buildLauncherPane(launcherPaneIsHorizontal);
return new RootContainer(launcherPaneIsHorizontal)
.setParent(appContext)
.child(new FlexContainer("column")
.id("launcher-pane")
.css("width", "53px")
.child(new GlobalMenuWidget())
.child(new LauncherContainer())
.child(new LeftPaneToggleWidget())
.optChild(launcherPaneIsHorizontal, new FlexContainer('row')
.child(new TabRowWidget().class("full-width"))
.child(new TitleBarButtonsWidget())
.css('height', '40px')
.css('background-color', 'var(--launcher-pane-background-color)')
.setParent(appContext)
)
.child(new LeftPaneContainer()
.child(new QuickSearchWidget())
.child(appContext.noteTreeWidget)
.child(...this.customWidgets.get('left-pane'))
)
.child(new FlexContainer('column')
.id('rest-pane')
.optChild(launcherPaneIsHorizontal, launcherPane)
.child(new FlexContainer('row')
.css("flex-grow", "1")
.child(new FlexContainer('row')
.child(new TabRowWidget())
.child(new TitleBarButtonsWidget())
.css('height', '40px')
.optChild(!launcherPaneIsHorizontal, launcherPane)
.child(new LeftPaneContainer()
.optChild(!launcherPaneIsHorizontal, new QuickSearchWidget())
.child(appContext.noteTreeWidget)
.child(...this.customWidgets.get('left-pane'))
)
.child(new FlexContainer('row')
.filling()
.collapsible()
.child(new FlexContainer('column')
.child(new FlexContainer('column')
.id('rest-pane')
.css("flex-grow", "1")
.optChild(!launcherPaneIsHorizontal, new FlexContainer('row')
.child(new TabRowWidget())
.child(new TitleBarButtonsWidget())
.css('height', '40px')
)
.child(new FlexContainer('row')
.filling()
.collapsible()
.id('center-pane')
.child(new SplitNoteContainer(() =>
new NoteWrapperWidget()
.child(new FlexContainer('row').class('title-row')
.css("height", "50px")
.css("min-height", "50px")
.css('align-items', "center")
.cssBlock('.title-row > * { margin: 5px; }')
.child(new NoteIconWidget())
.child(new NoteTitleWidget())
.child(new SpacerWidget(0, 1))
.child(new MovePaneButton(true))
.child(new MovePaneButton(false))
.child(new ClosePaneButton())
.child(new CreatePaneButton())
)
.child(
new RibbonContainer()
// the order of the widgets matter. Some of these want to "activate" themselves
// when visible. When this happens to multiple of them, the first one "wins".
// promoted attributes should always win.
.ribbon(new ClassicEditorToolbar())
.ribbon(new PromotedAttributesWidget())
.ribbon(new ScriptExecutorWidget())
.ribbon(new SearchDefinitionWidget())
.ribbon(new EditedNotesWidget())
.ribbon(new BookPropertiesWidget())
.ribbon(new NotePropertiesWidget())
.ribbon(new FilePropertiesWidget())
.ribbon(new ImagePropertiesWidget())
.ribbon(new BasicPropertiesWidget())
.ribbon(new OwnedAttributeListWidget())
.ribbon(new InheritedAttributesWidget())
.ribbon(new NotePathsWidget())
.ribbon(new NoteMapRibbonWidget())
.ribbon(new SimilarNotesWidget())
.ribbon(new NoteInfoWidget())
.button(new RevisionsButton())
.button(new NoteActionsWidget())
)
.child(new SharedInfoWidget())
.child(new WatchedFileUpdateStatusWidget())
.child(new FloatingButtons()
.child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new CopyImageReferenceButton())
.child(new SvgExportButton())
.child(new BacklinksWidget())
.child(new HideFloatingButtonsButton())
)
.child(new MermaidWidget())
.child(
new ScrollingContainer()
.filling()
.child(new SqlTableSchemasWidget())
.child(new NoteDetailWidget())
.child(new NoteListWidget())
.child(new SearchResultWidget())
.child(new SqlResultWidget())
.child(new ScrollPaddingWidget())
)
.child(new ApiLogWidget())
.child(new FindWidget())
.child(
...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC
...this.customWidgets.get('note-detail-pane')
)
.child(new FlexContainer('column')
.filling()
.collapsible()
.id('center-pane')
.child(new SplitNoteContainer(() =>
new NoteWrapperWidget()
.child(new FlexContainer('row').class('title-row')
.css("height", "50px")
.css("min-height", "50px")
.css('align-items', "center")
.cssBlock('.title-row > * { margin: 5px; }')
.child(new NoteIconWidget())
.child(new NoteTitleWidget())
.child(new SpacerWidget(0, 1))
.child(new MovePaneButton(true))
.child(new MovePaneButton(false))
.child(new ClosePaneButton())
.child(new CreatePaneButton())
)
.child(
new RibbonContainer()
// the order of the widgets matter. Some of these want to "activate" themselves
// when visible. When this happens to multiple of them, the first one "wins".
// promoted attributes should always win.
.ribbon(new ClassicEditorToolbar())
.ribbon(new PromotedAttributesWidget())
.ribbon(new ScriptExecutorWidget())
.ribbon(new SearchDefinitionWidget())
.ribbon(new EditedNotesWidget())
.ribbon(new BookPropertiesWidget())
.ribbon(new NotePropertiesWidget())
.ribbon(new FilePropertiesWidget())
.ribbon(new ImagePropertiesWidget())
.ribbon(new BasicPropertiesWidget())
.ribbon(new OwnedAttributeListWidget())
.ribbon(new InheritedAttributesWidget())
.ribbon(new NotePathsWidget())
.ribbon(new NoteMapRibbonWidget())
.ribbon(new SimilarNotesWidget())
.ribbon(new NoteInfoWidget())
.button(new RevisionsButton())
.button(new NoteActionsWidget())
)
.child(new SharedInfoWidget())
.child(new WatchedFileUpdateStatusWidget())
.child(new FloatingButtons()
.child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new CopyImageReferenceButton())
.child(new SvgExportButton())
.child(new BacklinksWidget())
.child(new HideFloatingButtonsButton())
)
.child(new MermaidWidget())
.child(
new ScrollingContainer()
.filling()
.child(new SqlTableSchemasWidget())
.child(new NoteDetailWidget())
.child(new NoteListWidget())
.child(new SearchResultWidget())
.child(new SqlResultWidget())
.child(new ScrollPaddingWidget())
)
.child(new ApiLogWidget())
.child(new FindWidget())
.child(
...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC
...this.customWidgets.get('note-detail-pane')
)
)
)
.child(...this.customWidgets.get('center-pane'))
)
.child(new RightPaneContainer()
.child(new TocWidget())
.child(new HighlightsListWidget())
.child(...this.customWidgets.get('right-pane'))
)
.child(...this.customWidgets.get('center-pane'))
)
.child(new RightPaneContainer()
.child(new TocWidget())
.child(new HighlightsListWidget())
.child(...this.customWidgets.get('right-pane'))
)
)
)
@ -225,4 +234,27 @@ export default class DesktopLayout {
.child(new ConfirmDialog())
.child(new PromptDialog());
}
#buildLauncherPane(isHorizontal) {
let launcherPane;
if (isHorizontal) {
launcherPane = new FlexContainer("row")
.css("height", "53px")
.class("horizontal")
.child(new LeftPaneToggleWidget(true))
.child(new LauncherContainer(true))
.child(new GlobalMenuWidget(true))
} else {
launcherPane = new FlexContainer("column")
.css("width", "53px")
.class("vertical")
.child(new GlobalMenuWidget(false))
.child(new LauncherContainer(false))
.child(new LeftPaneToggleWidget(false));
}
launcherPane.id("launcher-pane");
return launcherPane;
}
}

View File

@ -24,6 +24,7 @@ import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.js";
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
import options from "../services/options.js";
const MOBILE_CSS = `
<style>
@ -112,15 +113,12 @@ span.fancytree-expander {
export default class MobileLayout {
getRootWidget(appContext) {
return new RootContainer()
const launcherPaneIsHorizontal = (options.get("layoutOrientation") === "horizontal");
return new RootContainer(launcherPaneIsHorizontal)
.setParent(appContext)
.cssBlock(MOBILE_CSS)
.child(new FlexContainer("column")
.id("launcher-pane")
.css("width", "53px")
.child(new GlobalMenuWidget())
.child(new LauncherContainer())
)
.child(this.#buildLauncherPane(launcherPaneIsHorizontal))
.child(new FlexContainer("row")
.filling()
.child(new ScreenContainer("tree", 'column')
@ -140,12 +138,14 @@ export default class MobileLayout {
.child(new FlexContainer('row').contentSized()
.css('font-size', 'larger')
.css('align-items', 'center')
.child(new MobileDetailMenuWidget().contentSized())
.optChild(!launcherPaneIsHorizontal, new MobileDetailMenuWidget(false).contentSized())
.child(new NoteTitleWidget()
.contentSized()
.css("position: relative;")
.css("top: 5px;")
.optCss(launcherPaneIsHorizontal, "padding-left", "0.5em")
)
.optChild(launcherPaneIsHorizontal, new MobileDetailMenuWidget(true).contentSized())
.child(new CloseDetailButtonWidget().contentSized()))
.child(new SharedInfoWidget())
.child(new FloatingButtons()
@ -174,4 +174,25 @@ export default class MobileLayout {
.child(new ConfirmDialog())
);
}
#buildLauncherPane(isHorizontal) {
let launcherPane;
if (isHorizontal) {
launcherPane = new FlexContainer("row")
.class("horizontal")
.css("height", "53px")
.child(new LauncherContainer(true))
.child(new GlobalMenuWidget(true));
} else {
launcherPane = new FlexContainer("column")
.class("vertical")
.css("width", "53px")
.child(new GlobalMenuWidget(false))
.child(new LauncherContainer(false));
}
launcherPane.id("launcher-pane");
return launcherPane;
}
}

View File

@ -1,3 +1,4 @@
import { t } from '../services/i18n.js';
import utils from "../services/utils.js";
import contextMenu from "./context_menu.js";
import imageService from "../services/image.js";
@ -18,14 +19,14 @@ function setupContextMenu($image) {
y: e.pageY,
items: [
{
title: "Copy reference to clipboard",
title: t("image_context_menu.copy_reference_to_clipboard"),
command: "copyImageReferenceToClipboard",
uiIcon: "bx bx-directions"
},
{
title: "Copy image to clipboard",
{
title: t("image_context_menu.copy_image_to_clipboard"),
command: "copyImageToClipboard",
uiIcon: "bx bx-copy"
uiIcon: "bx bx-copy"
},
],
selectMenuItemHandler: async ({ command }) => {
@ -61,4 +62,4 @@ function setupContextMenu($image) {
export default {
setupContextMenu
};
};

View File

@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import contextMenu from "./context_menu.js";
import appContext from "../components/app_context.js";
@ -6,9 +7,9 @@ function openContextMenu(notePath, e, viewScope = {}, hoistedNoteId = null) {
x: e.pageX,
y: e.pageY,
items: [
{title: "Open note in a new tab", command: "openNoteInNewTab", uiIcon: "bx bx-link-external"},
{title: "Open note in a new split", command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right"},
{title: "Open note in a new window", command: "openNoteInNewWindow", uiIcon: "bx bx-window-open"}
{title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external"},
{title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right"},
{title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open"}
],
selectMenuItemHandler: ({command}) => {
if (!hoistedNoteId) {

View File

@ -12,6 +12,7 @@ import FAttachment from "../entities/fattachment.js";
import imageContextMenuService from "../menus/image_context_menu.js";
import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js";
import mime_types from "./mime_types.js";
import { loadElkIfNeeded } from "./mermaid.js";
let idCounter = 1;
@ -237,6 +238,7 @@ async function renderMermaid(note, $renderedContent) {
mermaid.mermaidAPI.initialize({startOnLoad: false, theme: mermaidTheme.trim(), securityLevel: 'antiscript'});
try {
await loadElkIfNeeded(content);
const {svg} = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content);
$renderedContent.append($(svg));

View File

@ -58,7 +58,19 @@ const FORCE_GRAPH = {
};
const MERMAID = {
js: [ "node_modules/mermaid/dist/mermaid.min.js" ]
js: [
"node_modules/mermaid/dist/mermaid.min.js"
]
}
/**
* The ELK extension of Mermaid.js, which supports more advanced layouts.
* See https://www.npmjs.com/package/@mermaid-js/layout-elk for more information.
*/
const MERMAID_ELK = {
js: [
"libraries/mermaid-elk/elk.min.js"
]
}
const EXCALIDRAW = {
@ -197,6 +209,7 @@ export default {
WHEEL_ZOOM,
FORCE_GRAPH,
MERMAID,
MERMAID_ELK,
EXCALIDRAW,
MARKJS,
I18NEXT,

View File

@ -0,0 +1,28 @@
import library_loader from "./library_loader.js";
let elkLoaded = false;
/**
* Determines whether the ELK extension of Mermaid.js needs to be loaded (which is a relatively large library), based on the
* front-matter of the diagram and loads the library if needed.
*
* <p>
* If the library has already been loaded or the diagram does not require it, the method will exit immediately.
*
* @param mermaidContent the plain text of the mermaid diagram, potentially including a frontmatter.
*/
export async function loadElkIfNeeded(mermaidContent) {
if (elkLoaded) {
// Exit immediately since the ELK library is already loaded.
return;
}
const parsedContent = await mermaid.parse(mermaidContent, {
suppressErrors: true
});
if (parsedContent?.config?.layout === "elk") {
elkLoaded = true;
await library_loader.requireLibrary(library_loader.MERMAID_ELK);
mermaid.registerLayoutLoaders(MERMAID_ELK);
}
}

View File

@ -6,7 +6,7 @@ function parse(value) {
if (token === 'promoted') {
defObj.isPromoted = true;
}
else if (['text', 'number', 'boolean', 'date', 'datetime', 'url'].includes(token)) {
else if (['text', 'number', 'boolean', 'date', 'datetime', 'time', 'url'].includes(token)) {
defObj.labelType = token;
}
else if (['single', 'multi'].includes(token)) {

View File

@ -125,6 +125,7 @@ const TPL = `
<option value="boolean">${t('attribute_detail.boolean')}</option>
<option value="date">${t('attribute_detail.date')}</option>
<option value="datetime">${t('attribute_detail.date_time')}</option>
<option value="time">${t('attribute_detail.time')}</option>
<option value="url">${t('attribute_detail.url')}</option>
</select>
</td>

View File

@ -40,6 +40,21 @@ class BasicWidget extends Component {
return this;
}
/**
* Conditionally adds the given components as children to this component.
*
* @param {boolean} condition whether to add the components.
* @param {...any} components the components to be added as children to this component provided the condition is truthy.
* @returns self for chaining.
*/
optChild(condition, ...components) {
if (condition) {
return this.child(...components);
} else {
return this;
}
}
id(id) {
this.attrs.id = id;
return this;
@ -50,11 +65,34 @@ class BasicWidget extends Component {
return this;
}
/**
* Sets the CSS attribute of the given name to the given value.
*
* @param {string} name the name of the CSS attribute to set (e.g. `padding-left`).
* @param {string} value the value of the CSS attribute to set (e.g. `12px`).
* @returns self for chaining.
*/
css(name, value) {
this.attrs.style += `${name}: ${value};`;
return this;
}
/**
* Sets the CSS attribute of the given name to the given value, but only if the condition provided is truthy.
*
* @param {boolean} condition `true` in order to apply the CSS, `false` to ignore it.
* @param {string} name the name of the CSS attribute to set (e.g. `padding-left`).
* @param {string} value the value of the CSS attribute to set (e.g. `12px`).
* @returns self for chaining.
*/
optCss(condition, name, value) {
if (condition) {
return this.css(name, value);
}
return this;
}
contentSized() {
this.css("contain", "none");

View File

@ -4,8 +4,8 @@ import BookmarkFolderWidget from "./buttons/bookmark_folder.js";
import froca from "../services/froca.js";
export default class BookmarkButtons extends FlexContainer {
constructor() {
super("column");
constructor(isHorizontalLayout) {
super(isHorizontalLayout ? "row" : "column");
this.contentSized();
}

View File

@ -23,7 +23,11 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.tooltip = new bootstrap.Tooltip(this.$widget, {
html: true, title: () => this.getTitle(), trigger: 'hover'
html: true,
title: () => this.getTitle(),
trigger: 'hover',
placement: this.settings.titlePlacement,
fallbackPlacements: [ this.settings.titlePlacement ]
})
if (this.settings.onContextMenu) {
@ -36,8 +40,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
});
}
this.$widget.attr("data-placement", this.settings.titlePlacement);
super.doRender();
}

View File

@ -5,7 +5,7 @@ import UpdateAvailableWidget from "./update_available.js";
import options from "../../services/options.js";
const TPL = `
<div class="dropdown global-menu dropend">
<div class="dropdown global-menu">
<style>
.global-menu {
width: 53px;
@ -107,22 +107,6 @@ const TPL = `
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action global-menu-button">
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}">
<g>
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
<path class="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/>
<path class="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/>
<path class="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/>
<path class="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/>
<path class="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/>
<path class="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/>
<path class="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
</g>
</svg>
<div class="global-menu-button-update-available"></div>
</button>
@ -235,7 +219,7 @@ const TPL = `
${t('global_menu.options')}
</li>
<div class="dropdown-divider"></div>
<div class="dropdown-divider desktop-only"></div>
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
<span class="bx bx-help-circle"></span>
@ -265,18 +249,46 @@ const TPL = `
`;
export default class GlobalMenuWidget extends BasicWidget {
constructor() {
constructor(isHorizontalLayout) {
super();
this.updateAvailableWidget = new UpdateAvailableWidget();
this.isHorizontalLayout = isHorizontalLayout;
}
doRender() {
this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
if (!this.isHorizontalLayout) {
this.$widget.addClass("dropend");
}
this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" });
const $globalMenuButton = this.$widget.find(".global-menu-button")
if (!this.isHorizontalLayout) {
$globalMenuButton.prepend($(`\
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}">
<g>
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
<path class="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/>
<path class="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/>
<path class="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/>
<path class="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/>
<path class="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/>
<path class="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/>
<path class="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
</g>
</svg>`));
this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" });
} else {
$globalMenuButton.toggleClass("bx bx-menu");
}
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"), {
alignment: "bottom"
});
this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog"));
@ -300,7 +312,7 @@ export default class GlobalMenuWidget extends BasicWidget {
if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass('dropdown-toggle')) {
e.stopPropagation();
}
})
})
this.$widget.find(".global-menu-button-update-available").append(
this.updateAvailableWidget.render()
@ -316,10 +328,14 @@ export default class GlobalMenuWidget extends BasicWidget {
this.$zoomState = this.$widget.find(".zoom-state");
this.$widget.on('show.bs.dropdown', () => {
this.updateZoomState();
this.tooltip.hide();
this.tooltip.disable();
if (this.tooltip) {
this.tooltip.hide();
this.tooltip.disable();
}
});
this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable());
if (this.tooltip) {
this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable());
}
this.$widget.find(".zoom-buttons").on("click",
// delay to wait for the actual zoom change

View File

@ -4,7 +4,7 @@ import CommandButtonWidget from "./command_button.js";
import { t } from "../../services/i18n.js";
export default class LeftPaneToggleWidget extends CommandButtonWidget {
constructor() {
constructor(isHorizontalLayout) {
super();
this.class("launcher-button");
@ -20,6 +20,10 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget {
this.settings.command = () => options.is('leftPaneVisible')
? "hideLeftPane"
: "showLeftPane";
if (isHorizontalLayout) {
this.settings.titlePlacement = "bottom";
}
}
refreshIcon() {

View File

@ -2,13 +2,7 @@ import BasicWidget from "../basic_widget.js";
const TPL = `
<div class="dropdown right-dropdown-widget dropend">
<style>
.right-dropdown-widget {
height: 53px;
}
</style>
<button type="button" data-bs-toggle="dropdown" data-placement="right"
<button type="button" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"
class="bx right-dropdown-button launcher-button"></button>
@ -25,6 +19,10 @@ export default class RightDropdownButtonWidget extends BasicWidget {
this.iconClass = iconClass;
this.title = title;
this.dropdownTpl = dropdownTpl;
this.settings = {
titlePlacement: "right"
};
}
doRender() {
@ -33,7 +31,10 @@ export default class RightDropdownButtonWidget extends BasicWidget {
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
this.tooltip = new bootstrap.Tooltip(this.$tooltip);
this.tooltip = new bootstrap.Tooltip(this.$tooltip, {
placement: this.settings.titlePlacement,
fallbackPlacements: [ this.settings.titlePlacement ]
});
this.$widget.find(".right-dropdown-button")
.addClass(this.iconClass)

View File

@ -10,12 +10,14 @@ import CommandButtonWidget from "../buttons/command_button.js";
import utils from "../../services/utils.js";
import TodayLauncher from "../buttons/launcher/today_launcher.js";
import HistoryNavigationButton from "../buttons/history_navigation.js";
import QuickSearchLauncherWidget from "../quick_search_launcher.js";
export default class LauncherWidget extends BasicWidget {
constructor() {
constructor(isHorizontalLayout) {
super();
this.innerWidget = null;
this.isHorizontalLayout = isHorizontalLayout;
}
isEnabled() {
@ -63,6 +65,9 @@ export default class LauncherWidget extends BasicWidget {
}
this.child(this.innerWidget);
if (this.isHorizontalLayout && this.innerWidget.settings) {
this.innerWidget.settings.titlePlacement = "bottom";
}
return true;
}
@ -86,29 +91,31 @@ export default class LauncherWidget extends BasicWidget {
initBuiltinWidget(note) {
const builtinWidget = note.getLabelValue("builtinWidget");
if (builtinWidget === 'calendar') {
return new CalendarWidget(note.title, note.getIcon());
} else if (builtinWidget === 'spacer') {
// || has to be inside since 0 is a valid value
const baseSize = parseInt(note.getLabelValue("baseSize") || "40");
const growthFactor = parseInt(note.getLabelValue("growthFactor") || "100");
return new SpacerWidget(baseSize, growthFactor);
} else if (builtinWidget === 'bookmarks') {
return new BookmarkButtons();
} else if (builtinWidget === 'protectedSession') {
return new ProtectedSessionStatusWidget();
} else if (builtinWidget === 'syncStatus') {
return new SyncStatusWidget();
} else if (builtinWidget === 'backInHistoryButton') {
return new HistoryNavigationButton(note, "backInNoteHistory");
} else if (builtinWidget === 'forwardInHistoryButton') {
return new HistoryNavigationButton(note, "forwardInNoteHistory");
} else if (builtinWidget === 'todayInJournal') {
return new TodayLauncher(note);
} else {
throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`);
switch (builtinWidget) {
case "calendar":
return new CalendarWidget(note.title, note.getIcon());
case "spacer":
// || has to be inside since 0 is a valid value
const baseSize = parseInt(note.getLabelValue("baseSize") || "40");
const growthFactor = parseInt(note.getLabelValue("growthFactor") || "100");
return new SpacerWidget(baseSize, growthFactor);
case "bookmarks":
return new BookmarkButtons(this.isHorizontalLayout);
case "protectedSession":
return new ProtectedSessionStatusWidget();
case "syncStatus":
return new SyncStatusWidget();
case "backInHistoryButton":
return new HistoryNavigationButton(note, "backInNoteHistory");
case "forwardInHistoryButton":
return new HistoryNavigationButton(note, "forwardInNoteHistory");
case "todayInJournal":
return new TodayLauncher(note);
case "quickSearch":
return new QuickSearchLauncherWidget(this.isHorizontalLayout);
default:
throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`);
}
}
}

View File

@ -4,12 +4,13 @@ import appContext from "../../components/app_context.js";
import LauncherWidget from "./launcher.js";
export default class LauncherContainer extends FlexContainer {
constructor() {
super('column');
constructor(isHorizontalLayout) {
super(isHorizontalLayout ? "row" : "column");
this.id('launcher-container');
this.css('height', '100%');
this.css(isHorizontalLayout ? "width" : 'height', '100%');
this.filling();
this.isHorizontalLayout = isHorizontalLayout;
this.load();
}
@ -29,7 +30,7 @@ export default class LauncherContainer extends FlexContainer {
for (const launcherNote of await visibleLaunchersRoot.getChildNotes()) {
try {
const launcherWidget = new LauncherWidget();
const launcherWidget = new LauncherWidget(this.isHorizontalLayout);
const success = await launcherWidget.initLauncher(launcherNote);
if (success) {

View File

@ -1,8 +1,8 @@
import FlexContainer from "./flex_container.js";
export default class RootContainer extends FlexContainer {
constructor() {
super('row');
constructor(isHorizontalLayout) {
super(isHorizontalLayout ? "column" : "row");
this.id('root-widget');
this.css('height', '100%');

View File

@ -3,6 +3,7 @@ import libraryLoader from "../services/library_loader.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import server from "../services/server.js";
import utils from "../services/utils.js";
import { loadElkIfNeeded } from "../services/mermaid.js";
const TPL = `<div class="mermaid-widget">
<style>
@ -57,10 +58,10 @@ export default class MermaidWidget extends NoteContextAwareWidget {
this.$errorContainer.hide();
await libraryLoader.requireLibrary(libraryLoader.MERMAID);
const documentStyle = window.getComputedStyle(document.documentElement);
const mermaidTheme = documentStyle.getPropertyValue('--mermaid-theme');
const mermaidTheme = documentStyle.getPropertyValue('--mermaid-theme');
mermaid.mermaidAPI.initialize({
startOnLoad: false,
theme: mermaidTheme.trim(),
@ -111,6 +112,7 @@ export default class MermaidWidget extends NoteContextAwareWidget {
zoomOnClick: false
});
} catch (e) {
console.warn(e);
this.$errorMessage.text(e.message);
this.$errorContainer.show();
}
@ -122,6 +124,7 @@ export default class MermaidWidget extends NoteContextAwareWidget {
const blob = await this.note.getBlob();
const content = blob.content || "";
await loadElkIfNeeded(content);
const {svg} = await mermaid.mermaidAPI.render(`mermaid-graph-${idCounter}`, content);
return svg;
}

View File

@ -6,12 +6,20 @@ import branchService from "../../services/branches.js";
import treeService from "../../services/tree.js";
import { t } from "../../services/i18n.js";
const TPL = `<button type="button" class="action-button bx bx-menu" style="padding-top: 10px;"></button>`;
const TPL = `<button type="button" class="action-button bx" style="padding-top: 10px;"></button>`;
class MobileDetailMenuWidget extends BasicWidget {
constructor(isHorizontalLayout) {
super();
this.isHorizontalLayout = isHorizontalLayout;
}
doRender() {
this.$widget = $(TPL);
this.$widget.addClass(this.isHorizontalLayout ? "bx-dots-vertical-rounded" : "bx-menu");
this.$widget.on("click", async e => {
const note = appContext.tabManager.getActiveContextNote();

View File

@ -0,0 +1,33 @@
import utils from "../services/utils.js";
import QuickSearchWidget from "./quick_search.js";
/**
* Similar to the {@link QuickSearchWidget} but meant to be included inside the launcher bar.
*
* <p>
* Adds specific tweaks such as:
*
* - Hiding the widget on mobile.
*/
export default class QuickSearchLauncherWidget extends QuickSearchWidget {
constructor(isHorizontalLayout) {
super();
this.isHorizontalLayout = isHorizontalLayout;
}
isEnabled() {
if (!this.isHorizontalLayout) {
// The quick search widget is added somewhere else on the vertical layout.
return false;
}
if (utils.isMobile()) {
// The widget takes too much spaces to be included in the mobile layout.
return false;
}
return super.isEnabled();
}
}

View File

@ -225,6 +225,9 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
else if (definition.labelType === 'datetime') {
$input.prop('type', 'datetime-local')
}
else if (definition.labelType === 'time') {
$input.prop('type', 'time')
}
else if (definition.labelType === 'url') {
$input.prop("placeholder", t("promoted_attributes.url_placeholder"));

View File

@ -55,6 +55,10 @@ const TAB_ROW_TPL = `
background: var(--main-background-color);
overflow: hidden;
}
.tab-row-widget.full-width {
background: var(--launcher-pane-background-color);
}
.tab-row-widget * {
box-sizing: inherit;

View File

@ -1,45 +1,53 @@
import NoteContextAwareWidget from "../../note_context_aware_widget.js";
import server from "../../../services/server.js";
import { t } from "../../../services/i18n.js";
import AbstractCodeTypeWidget from "../abstract_code_type_widget.js";
const TPL = `<div style="height: 100%; display: flex; flex-direction: column;">
<style>
.backend-log-textarea {
.backend-log-editor {
flex-grow: 1;
width: 100%;
border: none;
resize: none;
}
</style>
<textarea class="backend-log-textarea" readonly="readonly"></textarea>
<pre class="backend-log-editor"></pre>
<div style="display: flex; justify-content: space-around; margin-top: 10px;">
<button class="refresh-backend-log-button btn btn-primary">${t("backend_log.refresh")}</button>
</div>
</div>`;
export default class BackendLogWidget extends NoteContextAwareWidget {
export default class BackendLogWidget extends AbstractCodeTypeWidget {
doRender() {
super.doRender();
this.$widget = $(TPL);
this.$backendLogTextArea = this.$widget.find(".backend-log-textarea");
this.$editor = this.$widget.find(".backend-log-editor");
this.$refreshBackendLog = this.$widget.find(".refresh-backend-log-button");
this.$refreshBackendLog.on('click', () => this.load());
}
scrollToBottom() {
this.$backendLogTextArea.scrollTop(this.$backendLogTextArea[0].scrollHeight);
}
async refresh() {
await this.load();
}
getExtraOpts() {
return {
lineWrapping: false,
readOnly: true
};
}
async load() {
const backendLog = await server.get('backend-log');
const content = await server.get('backend-log');
await this.initialized;
this.$backendLogTextArea.text(backendLog);
this.scrollToBottom();
this._update({
mime: "text/plain"
}, content);
this.show();
this.scrollToEnd();
}
}

View File

@ -5,6 +5,26 @@ import { t } from "../../../../services/i18n.js";
const TPL = `
<div class="options-section">
<h4>${t("theme.layout")}</h4>
<div class="form-group row">
<div>
<label>
<input type="radio" name="layout-orientation" value="vertical" />
<strong>${t("theme.layout-vertical-title")}</strong>
- ${t("theme.layout-vertical-description")}
</label>
</div>
<div>
<label>
<input type="radio" name="layout-orientation" value="horizontal" />
<strong>${t("theme.layout-horizontal-title")}</strong>
- ${t("theme.layout-horizontal-description")}
</label>
</div>
</div>
<h4>${t("theme.title")}</h4>
<div class="form-group row">
@ -19,7 +39,7 @@ const TPL = `
${t("theme.override_theme_fonts_label")}
</label>
</div>
</div>
</div>
</div>`;
export default class ThemeOptions extends OptionsWidget {
@ -27,6 +47,11 @@ export default class ThemeOptions extends OptionsWidget {
this.$widget = $(TPL);
this.$themeSelect = this.$widget.find(".theme-select");
this.$overrideThemeFonts = this.$widget.find(".override-theme-fonts");
this.$layoutOrientation = this.$widget.find(`input[name="layout-orientation"]`).on("change", async () => {
const newLayoutOrientation = this.$widget.find(`input[name="layout-orientation"]:checked`).val();
await this.updateOption("layoutOrientation", newLayoutOrientation);
utils.reloadFrontendApp("layout orientation change");
});
this.$themeSelect.on('change', async () => {
const newTheme = this.$themeSelect.val();
@ -57,5 +82,8 @@ export default class ThemeOptions extends OptionsWidget {
this.$themeSelect.val(options.theme);
this.setCheckboxState(this.$overrideThemeFonts, options.overrideThemeFonts);
this.$widget.find(`input[name="layout-orientation"][value="${options.layoutOrientation}"]`)
.prop("checked", "true");
}
}

View File

@ -40,6 +40,10 @@ body {
font-size: var(--main-font-size);
}
body.mobile .desktop-only {
display: none !important;
}
a {
text-decoration: none;
}
@ -1022,6 +1026,18 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
overflow: auto;
}
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
left: calc(-100% + 10px);
}
#launcher-pane.horizontal .right-dropdown-widget {
width: 53px;
}
#launcher-pane.vertical .right-dropdown-widget {
height: 53px;
}
/* rotate caret on hover */
.dropdown-menu > li > a:hover:after {
text-decoration: underline;
@ -1123,9 +1139,21 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
cursor: pointer;
border: none;
color: var(--launcher-pane-text-color);
background-color: var(--launcher-pane-background-color);
height: 53px;
background-color: var(--launcher-pane-background-color);
}
#launcher-pane.vertical .launcher-button {
width: 100%;
height: 53px;
}
#launcher-pane.horizontal .launcher-button {
width: 53px;
height: 100%;
}
#launcher-pane.horizontal .quick-search {
width: 350px;
}
#launcher-pane .icon-action:hover {

View File

@ -15,7 +15,13 @@
"message": "发生了严重错误,导致客户端应用程序无法启动:\n\n{{message}}\n\n这很可能是由于脚本以意外的方式失败引起的。请尝试以安全模式启动应用程序并解决问题。"
},
"widget-error": {
"title": "小部件初始化失败"
"title": "小部件初始化失败",
"message-custom": "来自 ID 为 \"{{id}}\"、标题为 \"{{title}}\" 的笔记的自定义小部件因以下原因无法初始化:\n\n{{message}}",
"message-unknown": "未知小部件因以下原因无法初始化:\n\n{{message}}"
},
"bundle-error": {
"title": "加载自定义脚本失败",
"message": "来自 ID 为 \"{{id}}\"、标题为 \"{{title}}\" 的笔记的脚本因以下原因无法执行:\n\n{{message}}"
}
},
"add_link": {
@ -45,7 +51,11 @@
"chosen_actions": "选择的操作",
"execute_bulk_actions": "执行批量操作",
"bulk_actions_executed": "批量操作已成功执行。",
"none_yet": "暂无操作 ... 通过点击上方的可用操作添加一个操作。"
"none_yet": "暂无操作 ... 通过点击上方的可用操作添加一个操作。",
"labels": "标签",
"relations": "关联关系",
"notes": "笔记",
"other": "其它"
},
"clone_to": {
"clone_notes_to": "克隆笔记到...",
@ -164,7 +174,8 @@
"textImportedAsText": "如果元数据不明确将HTML、Markdown和TXT导入为文本笔记",
"codeImportedAsCode": "如果元数据不明确,将识别的代码文件(例如<code>.json</code>)导入为代码笔记",
"replaceUnderscoresWithSpaces": "在导入的笔记名称中将下划线替换为空格",
"import": "导入"
"import": "导入",
"failed": "导入失败: {{message}}."
},
"include_note": {
"dialog_title": "包含笔记",
@ -304,6 +315,7 @@
"boolean": "布尔值",
"date": "日期",
"date_time": "日期和时间",
"time": "时间",
"url": "网址",
"precision_title": "值设置界面中浮点数后的位数。",
"precision": "精度",
@ -630,7 +642,10 @@
"export_note": "导出笔记",
"delete_note": "删除笔记",
"print_note": "打印笔记",
"save_revision": "保存笔记历史"
"save_revision": "保存笔记历史",
"convert_into_attachment_failed": "笔记 '{{title}}' 转换失败。",
"convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。",
"convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?"
},
"onclick_button": {
"no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序"
@ -885,7 +900,8 @@
"label_rock_or_pop": "只需一个标签存在即可",
"label_year_comparison": "数字比较(也包括>>=<)。",
"label_date_created": "上个月创建的笔记",
"error": "搜索错误:{{error}}"
"error": "搜索错误:{{error}}",
"search_prefix": "搜索:"
},
"attachment_detail": {
"open_help_page": "打开附件帮助页面",
@ -919,7 +935,15 @@
},
"protected_session": {
"enter_password_instruction": "显示受保护的笔记需要输入您的密码:",
"start_session_button": "开始受保护的会话"
"start_session_button": "开始受保护的会话",
"started": "受保护的会话已启动。",
"wrong_password": "密码错误。",
"protecting-finished-successfully": "保护操作已成功完成。",
"unprotecting-finished-successfully": "解除保护操作已成功完成。",
"protecting-in-progress": "保护进行中:{{count}}",
"unprotecting-in-progress-count": "解除保护进行中:{{count}}",
"protecting-title": "保护状态",
"unprotecting-title": "解除保护状态"
},
"relation_map": {
"open_in_new_tab": "在新标签页中打开",
@ -990,7 +1014,9 @@
"fill_entity_changes_button": "填充实体变更记录",
"full_sync_triggered": "全量同步已触发",
"filling_entity_changes": "正在填充实体变更行...",
"sync_rows_filled_successfully": "同步行填充成功"
"sync_rows_filled_successfully": "同步行填充成功",
"finished-successfully": "同步已完成。",
"failed": "同步失败:{{message}}"
},
"vacuum_database": {
"title": "数据库清理",
@ -1036,8 +1062,13 @@
"theme_label": "主题",
"override_theme_fonts_label": "覆盖主题字体",
"light_theme": "浅色",
"dark_theme": "深色"
},
"dark_theme": "深色",
"layout": "布局",
"layout-vertical-title": "垂直",
"layout-horizontal-title": "水平",
"layout-vertical-description": "启动栏位于左侧(默认)",
"layout-horizontal-description": "启动栏位于标签栏下方,标签栏现在是全宽的。"
},
"zoom_factor": {
"title": "缩放系数(仅桌面客户端有效)",
"description": "缩放也可以通过 CTRL+- 和 CTRL+= 快捷键进行控制。"
@ -1162,6 +1193,8 @@
"backup_now": "立即备份",
"backup_database_now": "立即备份数据库",
"existing_backups": "现有备份",
"date-and-time": "日期和时间",
"path": "路径",
"database_backed_up_to": "数据库已备份到",
"no_backup_yet": "尚无备份"
},
@ -1309,7 +1342,9 @@
"duplicate-subtree": "复制子树",
"export": "导出",
"import-into-note": "导入到笔记",
"apply-bulk-actions": "应用批量操作"
"apply-bulk-actions": "应用批量操作",
"converted-to-attachments": "{{count}} 个笔记已被转换为附件。",
"convert-to-attachment-confirm": "确定要将选中的笔记转换为其父笔记的附件吗?"
},
"shared_info": {
"shared_publicly": "此笔记已公开分享在",
@ -1332,7 +1367,8 @@
"image": "图片",
"launcher": "启动器",
"doc": "文档",
"widget": "小部件"
"widget": "小部件",
"confirm-change": "当笔记内容不为空时,不建议更改笔记类型。您仍然要继续吗?"
},
"protect_note": {
"toggle-on": "保护笔记",
@ -1355,7 +1391,11 @@
"open-help-page": "打开帮助页面",
"find": {
"case_sensitive": "区分大小写",
"match_words": "匹配单词"
"match_words": "匹配单词",
"find_placeholder": "在文本中查找...",
"replace_placeholder": "替换为...",
"replace": "替换",
"replace_all": "全部替换"
},
"highlights_list_2": {
"title": "高亮列表",
@ -1378,7 +1418,9 @@
"hide-archived-notes": "隐藏已归档笔记",
"automatically-collapse-notes": "自动折叠笔记",
"automatically-collapse-notes-title": "笔记在一段时间内未使用将被折叠,以减少树形结构的杂乱。",
"save-changes": "保存并应用更改"
"save-changes": "保存并应用更改",
"auto-collapsing-notes-after-inactivity": "在不活动后自动折叠笔记...",
"saved-search-note-refreshed": "已保存的搜索笔记已刷新。"
},
"title_bar_buttons": {
"window-on-top": "保持此窗口置顶"
@ -1407,8 +1449,11 @@
"add_new_tab": "添加新标签页",
"close": "关闭",
"close_other_tabs": "关闭其他标签页",
"close_right_tabs": "关闭右侧标签页",
"close_all_tabs": "关闭所有标签页",
"reopen_last_tab": "重新打开最后一个关闭的标签页",
"move_tab_to_new_window": "将此标签页移动到新窗口",
"copy_tab_to_new_window": "将此标签页复制到新窗口",
"new_tab": "新标签页"
},
"toc": {
@ -1422,5 +1467,101 @@
},
"app_context": {
"please_wait_for_save": "请等待几秒钟以完成保存,然后您可以尝试再操作一次。"
},
"note_create": {
"duplicated": "笔记 \"{{title}}\" 已被复制。"
},
"image": {
"copied-to-clipboard": "图片的引用已复制到剪贴板,可以粘贴到任何文本笔记中。",
"cannot-copy": "无法将图片引用复制到剪贴板。"
},
"clipboard": {
"cut": "笔记已剪切到剪贴板。",
"copied": "笔记已复制到剪贴板。"
},
"entrypoints": {
"note-revision-created": "笔记修订已创建。",
"note-executed": "笔记已执行。",
"sql-error": "执行 SQL 查询时发生错误:{{message}}"
},
"branches": {
"cannot-move-notes-here": "无法将笔记移动到这里。",
"delete-status": "删除状态",
"delete-notes-in-progress": "正在删除笔记:{{count}}",
"delete-finished-successfully": "删除成功完成。",
"undeleting-notes-in-progress": "正在恢复删除的笔记:{{count}}",
"undeleting-notes-finished-successfully": "恢复删除的笔记已成功完成。"
},
"frontend_script_api": {
"async_warning": "您正在将一个异步函数传递给 `api.runOnBackend()`,这可能无法按预期工作。\\n要么使该函数同步通过移除 `async` 关键字),要么使用 `api.runAsyncOnBackendWithManualTransactionHandling()`。",
"sync_warning": "您正在将一个同步函数传递给 `api.runAsyncOnBackendWithManualTransactionHandling()`\\n而您可能应该使用 `api.runOnBackend()`。"
},
"ws": {
"sync-check-failed": "同步检查失败!",
"consistency-checks-failed": "一致性检查失败!请查看日志了解详细信息。",
"encountered-error": "遇到错误 \"{{message}}\",请查看控制台。"
},
"hoisted_note": {
"confirm_unhoisting": "请求的笔记 '{{requestedNote}}' 位于提升的笔记 '{{hoistedNote}}' 的子树之外,您必须取消提升才能访问该笔记。是否继续取消提升?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "您确定要重置 \"{{title}}\" 吗?此笔记(及其子项)中的所有数据/设置将丢失,且启动器将恢复到其原始位置。",
"add-note-launcher": "添加笔记启动器",
"add-script-launcher": "添加脚本启动器",
"add-custom-widget": "添加自定义小部件",
"add-spacer": "添加间隔",
"delete": "删除",
"reset": "重置",
"move-to-visible-launchers": "移动到可见启动器",
"move-to-available-launchers": "移动到可用启动器",
"duplicate-launcher": "复制启动器"
},
"editable-text": {
"auto-detect-language": "自动检测"
},
"highlighting": {
"title": "文本笔记的代码语法高亮",
"description": "控制文本笔记中代码块的语法高亮,代码笔记不会受到影响。",
"color-scheme": "颜色方案"
},
"code_block": {
"word_wrapping": "自动换行"
},
"classic_editor_toolbar": {
"title": "格式化"
},
"editor": {
"title": "编辑器"
},
"editing": {
"editor_type": {
"label": "格式化工具栏",
"floating": {
"title": "浮动",
"description": "编辑工具出现在光标附近;"
},
"fixed": {
"title": "固定",
"description": "编辑工具出现在 \"格式化\" 功能区标签中。"
}
}
},
"electron_context_menu": {
"add-term-to-dictionary": "将 \"{{term}}\" 添加到字典",
"cut": "剪切",
"copy": "复制",
"copy-link": "复制链接",
"paste": "粘贴",
"paste-as-plain-text": "以纯文本粘贴",
"search_online": "用 {{searchEngine}} 搜索 \"{{term}}\""
},
"image_context_menu": {
"copy_reference_to_clipboard": "复制引用到剪贴板",
"copy_image_to_clipboard": "复制图片到剪贴板"
},
"link_context_menu": {
"open_note_in_new_tab": "在新标签页中打开笔记",
"open_note_in_new_split": "在新分屏中打开笔记",
"open_note_in_new_window": "在新窗口中打开笔记"
}
}

View File

@ -310,6 +310,7 @@
"boolean": "Boolescher Wert",
"date": "Datum",
"date_time": "Datum und Uhrzeit",
"time": "Uhrzeit",
"url": "URL",
"precision_title": "Wie viele Nachkommastellen im Wert-Einstellungs-Interface verfügbar sein sollen.",
"precision": "Präzision",

View File

@ -315,6 +315,7 @@
"boolean": "Boolean",
"date": "Date",
"date_time": "Date & Time",
"time": "Time",
"url": "URL",
"precision_title": "What number of digits after floating point should be available in the value setting interface.",
"precision": "Precision",
@ -1061,7 +1062,12 @@
"theme_label": "Theme",
"override_theme_fonts_label": "Override theme fonts",
"light_theme": "Light",
"dark_theme": "Dark"
"dark_theme": "Dark",
"layout": "Layout",
"layout-vertical-title": "Vertical",
"layout-horizontal-title": "Horizontal",
"layout-vertical-description": "launcher bar is on the left (default)",
"layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width."
},
"zoom_factor": {
"title": "Zoom Factor (desktop build only)",
@ -1548,5 +1554,14 @@
"paste": "Paste",
"paste-as-plain-text": "Paste as plain text",
"search_online": "Search for \"{{term}}\" with {{searchEngine}}"
},
"image_context_menu": {
"copy_reference_to_clipboard": "Copy reference to clipboard",
"copy_image_to_clipboard": "Copy image to clipboard"
},
"link_context_menu": {
"open_note_in_new_tab": "Open note in a new tab",
"open_note_in_new_split": "Open note in a new split",
"open_note_in_new_window": "Open note in a new window"
}
}

View File

@ -51,7 +51,11 @@
"chosen_actions": "Acciones elegidas",
"execute_bulk_actions": "Ejecutar acciones en bloque",
"bulk_actions_executed": "Las acciones en bloque se han ejecutado con éxito.",
"none_yet": "Ninguna todavía... agrega una acción haciendo clic en una de las disponibles arriba."
"none_yet": "Ninguna todavía... agregue una acción haciendo clic en una de las disponibles arriba.",
"labels": "Etiquetas",
"relations": "Relaciones",
"notes": "Notas",
"other": "Otro"
},
"clone_to": {
"clone_notes_to": "Clonar notas a...",
@ -311,6 +315,7 @@
"boolean": "Booleano",
"date": "Fecha",
"date_time": "Fecha y hora",
"time": "Hora",
"url": "URL",
"precision_title": "Cantidad de dígitos después del punto flotante que deben estar disponibles en la interfaz de configuración del valor.",
"precision": "Precisión",
@ -1057,7 +1062,12 @@
"theme_label": "Tema",
"override_theme_fonts_label": "Sobreescribir fuentes de tema",
"light_theme": "Claro",
"dark_theme": "Oscuro"
"dark_theme": "Oscuro",
"layout": "Disposición",
"layout-vertical-title": "Vertical",
"layout-horizontal-title": "Horizontal",
"layout-vertical-description": "la barra del lanzador está en la izquierda (por defecto)",
"layout-horizontal-description": "la barra de lanzamiento está debajo de la barra de pestañas, la barra de pestañas ahora tiene ancho completo."
},
"zoom_factor": {
"title": "Factor de zoom (solo versión de escritorio)",
@ -1183,6 +1193,8 @@
"backup_now": "Realizar copia de seguridad ahora",
"backup_database_now": "Realizar copia de seguridad de la base de datos ahora",
"existing_backups": "Copias de seguridad existentes",
"date-and-time": "Fecha y hora",
"path": "Ruta",
"database_backed_up_to": "Se ha realizado una copia de seguridad de la base de datos en",
"no_backup_yet": "no hay copia de seguridad todavía"
},
@ -1439,7 +1451,9 @@
"close_other_tabs": "Cerrar otras pestañas",
"close_right_tabs": "Cerrar pestañas a la derecha",
"close_all_tabs": "Cerras todas las pestañas",
"reopen_last_tab": "Reabrir última pestaña cerrada",
"move_tab_to_new_window": "Mover esta pestaña a una nueva ventana",
"copy_tab_to_new_window": "Copiar esta pestaña a una ventana nueva",
"new_tab": "Nueva pestaña"
},
"toc": {
@ -1531,5 +1545,14 @@
"description": "las herramientas de edición aparecen en la pestaña de la cinta \"Formato\")."
}
}
},
"electron_context_menu": {
"add-term-to-dictionary": "Agregar \"{{term}}\" al diccionario.",
"cut": "Cortar",
"copy": "Copiar",
"copy-link": "Copiar enlace",
"paste": "Pegar",
"paste-as-plain-text": "Pegar como texto plano",
"search_online": "Buscar \"{{term}}\" con {{searchEngine}}"
}
}

View File

@ -311,6 +311,7 @@
"boolean": "Booléen",
"date": "Date",
"date_time": "Date et heure",
"time": "Heure",
"url": "URL",
"precision_title": "Nombre de chiffres après la virgule devant être disponible dans l'interface définissant la valeur.",
"precision": "Précision",

View File

@ -0,0 +1 @@
{}

View File

@ -127,6 +127,7 @@
"custom_resource_provider": "a se vedea <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Custom request handler</a>",
"date": "Dată",
"date_time": "Dată și timp",
"time": "Timp",
"delete": "Șterge",
"digits": "număr de zecimale",
"disable_inclusion": "script-urile cu această etichetă nu vor fi incluse în execuția scriptului părinte.",

View File

@ -0,0 +1 @@
{}

View File

@ -66,7 +66,8 @@ const ALLOWED_OPTIONS = new Set([
'editedNotesOpenInRibbon',
'locale',
'firstDayOfWeek',
'textNoteEditorType'
'textNoteEditorType',
'layoutOrientation'
]);
function getOptions() {

View File

@ -34,7 +34,7 @@ interface Item {
baseSize?: string;
growthFactor?: string;
targetNoteId?: "_backendLog" | "_globalNoteMap";
builtinWidget?: "bookmarks" | "spacer" | "backInHistoryButton" | "forwardInHistoryButton" | "syncStatus" | "protectedSession" | "todayInJournal" | "calendar";
builtinWidget?: "bookmarks" | "spacer" | "backInHistoryButton" | "forwardInHistoryButton" | "syncStatus" | "protectedSession" | "todayInJournal" | "calendar" | "quickSearch";
command?: keyof typeof Command;
}
@ -103,7 +103,8 @@ const HIDDEN_SUBTREE_DEFINITION: Item = {
type: 'contentWidget',
icon: 'bx-terminal',
attributes: [
{ type: 'label', name: 'keepCurrentHoisting' }
{ type: 'label', name: 'keepCurrentHoisting' },
{ type: 'label', name: 'fullContentWidth' }
]
},
{
@ -240,6 +241,7 @@ const HIDDEN_SUBTREE_DEFINITION: Item = {
{ id: '_lbBookmarks', title: 'Bookmarks', type: 'launcher', builtinWidget: 'bookmarks', icon: 'bx bx-bookmark' },
{ id: '_lbToday', title: "Open Today's Journal Note", type: 'launcher', builtinWidget: 'todayInJournal', icon: 'bx bx-calendar-star' },
{ id: '_lbSpacer2', title: 'Spacer', type: 'launcher', builtinWidget: 'spacer', baseSize: "0", growthFactor: "1" },
{ id: '_lbQuickSearch', title: "Quick Search", type: "launcher", builtinWidget: "quickSearch", icon: "bx bx-rectangle" },
{ id: '_lbProtectedSession', title: 'Protected Session', type: 'launcher', builtinWidget: 'protectedSession', icon: 'bx bx bx-shield-quarter' },
{ id: '_lbSyncStatus', title: 'Sync Status', type: 'launcher', builtinWidget: 'syncStatus', icon: 'bx bx-wifi' },
{ id: '_lbSettings', title: 'Settings', type: 'launcher', command: 'showOptions', icon: 'bx bx-cog' }

View File

@ -134,7 +134,9 @@ const defaultOptions: DefaultOption[] = [
{ name: "codeBlockWordWrap", value: "false", isSynced: true },
// Text note configuration
{ name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true }
{ name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true },
{ name: "layoutOrientation", value: "vertical", isSynced: false }
];
/**

View File

@ -8,7 +8,7 @@ function parse(value: string): DefinitionObject {
if (token === 'promoted') {
defObj.isPromoted = true;
}
else if (['text', 'number', 'boolean', 'date', 'datetime', 'url'].includes(token)) {
else if (['text', 'number', 'boolean', 'date', 'datetime', 'time', 'url'].includes(token)) {
defObj.labelType = token;
}
else if (['single', 'multi'].includes(token)) {

View File

@ -89,7 +89,8 @@
"copy-without-formatting": "不带格式复制选定文本",
"force-save-revision": "强制创建/保存当前笔记的历史版本",
"show-help": "显示内置帮助/备忘单",
"toggle-book-properties": "切换书籍属性"
"toggle-book-properties": "切换书籍属性",
"toggle-classic-editor-toolbar": "切换具有固定工具栏的编辑器格式化选项卡"
},
"login": {
"title": "登录",
@ -157,5 +158,40 @@
"clipped-from": "此笔记最初剪切自 {{- url}}",
"child-notes": "子笔记:",
"no-content": "此笔记没有内容。"
},
"weekdays": {
"monday": "周一",
"tuesday": "周二",
"wednesday": "周三",
"thursday": "周四",
"friday": "周五",
"saturday": "周六",
"sunday": "周日"
},
"months": {
"january": "一月",
"february": "二月",
"march": "三月",
"april": "四月",
"may": "五月",
"june": "六月",
"july": "七月",
"august": "八月",
"september": "九月",
"october": "十月",
"november": "十一月",
"december": "十二月"
},
"special_notes": {
"search_prefix": "搜索:"
},
"code_block": {
"theme_none": "无语法高亮",
"theme_group_light": "浅色主题",
"theme_group_dark": "深色主题"
},
"test_sync": {
"not-configured": "同步服务器主机未配置。请先配置同步。",
"successful": "同步服务器握手成功,同步已开始。"
}
}