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)
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 "|--------|---------------|---------------|"
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 {
if (!this.parent) {
throw new Error(`Component "${this.componentId}" does not have a parent attached to propagate a command.`);
}
else {
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,24 +93,31 @@ 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)
.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 FlexContainer("column")
.id("launcher-pane")
.css("width", "53px")
.child(new GlobalMenuWidget())
.child(new LauncherContainer())
.child(new LeftPaneToggleWidget())
)
.optChild(launcherPaneIsHorizontal, launcherPane)
.child(new FlexContainer('row')
.css("flex-grow", "1")
.optChild(!launcherPaneIsHorizontal, launcherPane)
.child(new LeftPaneContainer()
.child(new QuickSearchWidget())
.optChild(!launcherPaneIsHorizontal, new QuickSearchWidget())
.child(appContext.noteTreeWidget)
.child(...this.customWidgets.get('left-pane'))
)
.child(new FlexContainer('column')
.id('rest-pane')
.css("flex-grow", "1")
.child(new FlexContainer('row')
.optChild(!launcherPaneIsHorizontal, new FlexContainer('row')
.child(new TabRowWidget())
.child(new TitleBarButtonsWidget())
.css('height', '40px')
@ -201,6 +209,7 @@ export default class DesktopLayout {
)
)
)
)
.child(new BulkActionsDialog())
.child(new AboutDialog())
.child(new HelpDialog())
@ -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,12 +19,12 @@ 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"
},

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");
}
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"));
@ -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();
if (this.tooltip) {
this.tooltip.hide();
this.tooltip.disable();
}
});
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,28 +91,30 @@ export default class LauncherWidget extends BasicWidget {
initBuiltinWidget(note) {
const builtinWidget = note.getLabelValue("builtinWidget");
if (builtinWidget === 'calendar') {
switch (builtinWidget) {
case "calendar":
return new CalendarWidget(note.title, note.getIcon());
} else if (builtinWidget === 'spacer') {
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);
} else if (builtinWidget === 'bookmarks') {
return new BookmarkButtons();
} else if (builtinWidget === 'protectedSession') {
case "bookmarks":
return new BookmarkButtons(this.isHorizontalLayout);
case "protectedSession":
return new ProtectedSessionStatusWidget();
} else if (builtinWidget === 'syncStatus') {
case "syncStatus":
return new SyncStatusWidget();
} else if (builtinWidget === 'backInHistoryButton') {
case "backInHistoryButton":
return new HistoryNavigationButton(note, "backInNoteHistory");
} else if (builtinWidget === 'forwardInHistoryButton') {
case "forwardInHistoryButton":
return new HistoryNavigationButton(note, "forwardInNoteHistory");
} else if (builtinWidget === 'todayInJournal') {
case "todayInJournal":
return new TodayLauncher(note);
} else {
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>
@ -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

@ -56,6 +56,10 @@ const TAB_ROW_TPL = `
overflow: hidden;
}
.tab-row-widget.full-width {
background: var(--launcher-pane-background-color);
}
.tab-row-widget * {
box-sizing: inherit;
font: 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">
@ -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;
@ -1124,8 +1140,20 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
border: none;
color: var(--launcher-pane-text-color);
background-color: var(--launcher-pane-background-color);
height: 53px;
}
#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,7 +1062,12 @@
"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": "缩放系数(仅桌面客户端有效)",
@ -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": "同步服务器握手成功,同步已开始。"
}
}