Merge pull request #264 from TriliumNext/feature/i18n

i18n support again (part 1)
This commit is contained in:
Elian Doran 2024-08-12 10:21:44 +03:00 committed by GitHub
commit f378cf9ff9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
125 changed files with 3266 additions and 1014 deletions

View File

@ -10,7 +10,7 @@ await i18next
debug: true,
backend: {
loadPath: `/${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
}
}
});
export const t = i18next.t;
export const t = i18next.t;

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import froca from "../../services/froca.js";
import linkService from "../../services/link.js";
@ -68,25 +69,25 @@ const TPL = `
</style>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<h5 class="attr-detail-title"></h5>
<h5 class="attr-detail-title">${t('attribute_detail.attr_detail_title')}</h5>
<span class="bx bx-x close-attr-detail-button" title="Cancel changes and close"></span>
<span class="bx bx-x close-attr-detail-button" title="${t('attribute_detail.close_button_title')}"></span>
</div>
<div class="attr-is-owned-by"></div>
<div class="attr-is-owned-by">${t('attribute_detail.attr_is_owned_by')}</div>
<table class="attr-edit-table">
<tr title="Attribute name can be composed of alphanumeric characters, colon and underscore only">
<th>Name:</th>
<tr title="${t('attribute_detail.attr_name_title')}">
<th>${t('attribute_detail.name')}</th>
<td><input type="text" class="attr-input-name form-control" /></td>
</tr>
<tr class="attr-help"></tr>
<tr class="attr-row-value">
<th>Value:</th>
<th>${t('attribute_detail.value')}</th>
<td><input type="text" class="attr-input-value form-control" /></td>
</tr>
<tr class="attr-row-target-note">
<th title="Relation is a named connection between source note and target note.">Target note:</th>
<th title="${t('attribute_detail.target_note_title')}">${t('attribute_detail.target_note')}</th>
<td>
<div class="input-group">
<input type="text" class="attr-input-target-note form-control" />
@ -94,12 +95,12 @@ const TPL = `
</td>
</tr>
<tr class="attr-row-promoted"
title="Promoted attribute is displayed prominently on the note.">
<th>Promoted:</th>
title="${t('attribute_detail.promoted_title')}">
<th>${t('attribute_detail.promoted')}</th>
<td><input type="checkbox" class="attr-input-promoted form-control form-control-sm" /></td>
</tr>
<tr class="attr-row-promoted-alias">
<th title="The name to be displayed in the promoted attributes UI.">Alias:</th>
<th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th>
<td>
<div class="input-group">
<input type="text" class="attr-input-promoted-alias form-control" />
@ -107,48 +108,48 @@ const TPL = `
</td>
</tr>
<tr class="attr-row-multiplicity">
<th title="Multiplicity defines how many attributes of the same name can be created - at max 1 or more than 1.">Multiplicity:</th>
<th title="${t('attribute_detail.multiplicity_title')}">${t('attribute_detail.multiplicity')}</th>
<td>
<select class="attr-input-multiplicity form-control">
<option value="single">Single value</option>
<option value="multi">Multi value</option>
<option value="single">${t('attribute_detail.single_value')}</option>
<option value="multi">${t('attribute_detail.multi_value')}</option>
</select>
</td>
</tr>
<tr class="attr-row-label-type">
<th title="Type of the label will help Trilium to choose suitable interface to enter the label value.">Type:</th>
<th title="${t('attribute_detail.label_type_title')}">${t('attribute_detail.label_type')}</th>
<td>
<select class="attr-input-label-type form-control">
<option value="text">Text</option>
<option value="number">Number</option>
<option value="boolean">Boolean</option>
<option value="date">Date</option>
<option value="datetime">Date & Time</option>
<option value="url">URL</option>
<option value="text">${t('attribute_detail.text')}</option>
<option value="number">${t('attribute_detail.number')}</option>
<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="url">${t('attribute_detail.url')}</option>
</select>
</td>
</tr>
<tr class="attr-row-number-precision">
<th title="What number of digits after floating point should be available in the value setting interface.">Precision:</th>
<th title="${t('attribute_detail.precision_title')}">${t('attribute_detail.precision')}</th>
<td>
<div class="input-group">
<input type="number" class="form-control attr-input-number-precision" style="text-align: right">
<div class="input-group-append">
<span class="input-group-text">digits</span>
<span class="input-group-text">${t('attribute_detail.digits')}</span>
</div>
</div>
</td>
</tr>
<tr class="attr-row-inverse-relation">
<th title="Optional setting to define to which relation is this one opposite. Example: Father - Son are inverse relations to each other.">Inverse relation:</th>
<th title="${t('attribute_detail.inverse_relation_title')}">${t('attribute_detail.inverse_relation')}</th>
<td>
<div class="input-group">
<input type="text" class="attr-input-inverse-relation form-control" />
</div>
</td>
</tr>
<tr title="Inheritable attribute will be inherited to all descendants under this tree.">
<th>Inheritable:</th>
<tr title="${t('attribute_detail.inheritable_title')}">
<th>${t('attribute_detail.inheritable')}</th>
<td><input type="checkbox" class="attr-input-inheritable form-control form-control-sm" /></td>
</tr>
</table>
@ -156,127 +157,112 @@ const TPL = `
<div class="attr-save-delete-button-container">
<button class="btn btn-primary btn-sm attr-save-changes-and-close-button"
style="flex-grow: 1; margin-right: 20px">
Save & close <kbd>Ctrl+Enter</kbd></button>
${t('attribute_detail.save_and_close')}</button>
<button class="btn btn-secondary btn-sm attr-delete-button">
Delete</button>
${t('attribute_detail.delete')}</button>
</div>
<div class="related-notes-container">
<br/>
<h5 class="related-notes-tile">Other notes with this label</h5>
<h5 class="related-notes-tile">${t('attribute_detail.related_notes_title')}</h5>
<ul class="related-notes-list"></ul>
<div class="related-notes-more-notes"></div>
<div class="related-notes-more-notes">${t('attribute_detail.more_notes')}</div>
</div>
</div>`;
const DISPLAYED_NOTES = 10;
const ATTR_TITLES = {
"label": "Label detail",
"label-definition": "Label definition detail",
"relation": "Relation detail",
"relation-definition": "Relation definition detail"
"label": t('attribute_detail.label'),
"label-definition": t('attribute_detail.label_definition'),
"relation": t('attribute_detail.relation'),
"relation-definition": t('attribute_detail.relation_definition')
};
const ATTR_HELP = {
"label": {
"disableVersioning": "disables auto-versioning. Useful for e.g. large, but unimportant notes - e.g. large JS libraries used for scripting",
"calendarRoot": "marks note which should be used as root for day notes. Only one should be marked as such.",
"archived": "notes with this label won't be visible by default in search results (also in Jump To, Add Link dialogs etc).",
"excludeFromExport": "notes (with their sub-tree) won't be included in any note export",
"run": `defines on which events script should run. Possible values are:
<ul>
<li>frontendStartup - when Trilium frontend starts up (or is refreshed), but not on mobile.</li>
<li>mobileStartup - when Trilium frontend starts up (or is refreshed), on mobile.</li>
<li>backendStartup - when Trilium backend starts up.</li>
<li>hourly - run once an hour. You can use additional label <code>runAtHour</code> to specify at which hour.</li>
<li>daily - run once a day</li>
</ul>`,
"runOnInstance": "Define which trilium instance should run this on. Default to all instances.",
"runAtHour": "On which hour should this run. Should be used together with <code>#run=hourly</code>. Can be defined multiple times for more runs during the day.",
"disableInclusion": "scripts with this label won't be included into parent script execution.",
"sorted": "keeps child notes sorted by title alphabetically",
"sortDirection": "ASC (the default) or DESC",
"sortFoldersFirst": "Folders (notes with children) should be sorted on top",
"top": "keep given note on top in its parent (applies only on sorted parents)",
"hidePromotedAttributes": "Hide promoted attributes on this note",
"readOnly": "editor is in read only mode. Works only for text and code notes.",
"autoReadOnlyDisabled": "text/code notes can be set automatically into read mode when they are too large. You can disable this behavior on per-note basis by adding this label to the note",
"appCss": "marks CSS notes which are loaded into the Trilium application and can thus be used to modify Trilium's looks.",
"appTheme": "marks CSS notes which are full Trilium themes and are thus available in Trilium options.",
"cssClass": "value of this label is then added as CSS class to the node representing given note in the tree. This can be useful for advanced theming. Can be used in template notes.",
"iconClass": "value of this label is added as a CSS class to the icon on the tree which can help visually distinguish the notes in the tree. Example might be bx bx-home - icons are taken from boxicons. Can be used in template notes.",
"pageSize": "number of items per page in note listing",
"customRequestHandler": 'see <a href="javascript:" data-help-page="custom-request-handler.html">Custom request handler</a>',
"customResourceProvider": 'see <a href="javascript:" data-help-page="custom-request-handler.html">Custom request handler</a>',
"widget": "marks this note as a custom widget which will be added to the Trilium component tree",
"workspace": "marks this note as a workspace which allows easy hoisting",
"workspaceIconClass": "defines box icon CSS class which will be used in tab when hoisted to this note",
"workspaceTabBackgroundColor": "CSS color used in the note tab when hoisted to this note",
"workspaceCalendarRoot": "Defines per-workspace calendar root",
"workspaceTemplate": "This note will appear in the selection of available template when creating new note, but only when hoisted into a workspace containing this template",
"searchHome": "new search notes will be created as children of this note",
"workspaceSearchHome": "new search notes will be created as children of this note when hoisted to some ancestor of this workspace note",
"inbox": "default inbox location for new notes - when you create a note using \"new note\" button in the sidebar, notes will be created as child notes in the note marked as with <code>#inbox</code> label.",
"workspaceInbox": "default inbox location for new notes when hoisted to some ancestor of this workspace note",
"sqlConsoleHome": "default location of SQL console notes",
"bookmarkFolder": "note with this label will appear in bookmarks as folder (allowing access to its children)",
"shareHiddenFromTree": "this note is hidden from left navigation tree, but still accessible with its URL",
"shareExternalLink": "note will act as a link to an external website in the share tree",
"shareAlias": "define an alias using which the note will be available under https://your_trilium_host/share/[your_alias]",
"shareOmitDefaultCss": "default share page CSS will be omitted. Use when you make extensive styling changes.",
"shareRoot": "marks note which is served on /share root.",
"shareDescription": "define text to be added to the HTML meta tag for description",
"shareRaw": "note will be served in its raw format, without HTML wrapper",
"shareDisallowRobotIndexing": `will forbid robot indexing of this note via <code>X-Robots-Tag: noindex</code> header`,
"shareCredentials": "require credentials to access this shared note. Value is expected to be in format 'username:password'. Don't forget to make this inheritable to apply to child-notes/images.",
"shareIndex": "note with this this label will list all roots of shared notes",
"displayRelations": "comma delimited names of relations which should be displayed. All other ones will be hidden.",
"hideRelations": "comma delimited names of relations which should be hidden. All other ones will be displayed.",
"titleTemplate": `default title of notes created as children of this note. The value is evaluated as JavaScript string
and thus can be enriched with dynamic content via the injected <code>now</code> and <code>parentNote</code> variables. Examples:
<ul>
<li><code>\${parentNote.getLabelValue('authorName')}'s literary works</code></li>
<li><code>Log for \${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>
</ul>
See <a href="https://triliumnext.github.io/Docs/Wiki/default-note-title.html">wiki with details</a>, API docs for <a href="https://zadam.github.io/trilium/backend_api/Note.html">parentNote</a> and <a href="https://day.js.org/docs/en/display/format">now</a> for details.`,
"template": "This note will appear in the selection of available template when creating new note",
"toc": "<code>#toc</code> or <code>#toc=show</code> will force the Table of Contents to be shown, <code>#toc=hide</code> will force hiding it. If the label doesn't exist, the global setting is observed",
"color": "defines color of the note in note tree, links etc. Use any valid CSS color value like 'red' or #a13d5f",
"keyboardShortcut": "Defines a keyboard shortcut which will immediately jump to this note. Example: 'ctrl+alt+e'. Requires frontend reload for the change to take effect.",
"keepCurrentHoisting": "Opening this link won't change hoisting even if the note is not displayable in the current hoisted subtree.",
"executeButton": "Title of the button which will execute the current code note",
"executeDescription": "Longer description of the current code note displayed together with the execute button",
"excludeFromNoteMap": "Notes with this label will be hidden from the Note Map",
"newNotesOnTop": "New notes will be created at the top of the parent note, not on the bottom.",
"hideHighlightWidget": "Hide Hightlight List widget"
"disableVersioning": t('attribute_detail.disable_versioning'),
"calendarRoot": t('attribute_detail.calendar_root'),
"archived": t('attribute_detail.archived'),
"excludeFromExport": t('attribute_detail.exclude_from_export'),
"run": t('attribute_detail.run'),
"runOnInstance": t('attribute_detail.run_on_instance'),
"runAtHour": t('attribute_detail.run_at_hour'),
"disableInclusion": t('attribute_detail.disable_inclusion'),
"sorted": t('attribute_detail.sorted'),
"sortDirection": t('attribute_detail.sort_direction'),
"sortFoldersFirst": t('attribute_detail.sort_folders_first'),
"top": t('attribute_detail.top'),
"hidePromotedAttributes": t('attribute_detail.hide_promoted_attributes'),
"readOnly": t('attribute_detail.read_only'),
"autoReadOnlyDisabled": t('attribute_detail.auto_read_only_disabled'),
"appCss": t('attribute_detail.app_css'),
"appTheme": t('attribute_detail.app_theme'),
"cssClass": t('attribute_detail.css_class'),
"iconClass": t('attribute_detail.icon_class'),
"pageSize": t('attribute_detail.page_size'),
"customRequestHandler": t('attribute_detail.custom_request_handler'),
"customResourceProvider": t('attribute_detail.custom_resource_provider'),
"widget": t('attribute_detail.widget'),
"workspace": t('attribute_detail.workspace'),
"workspaceIconClass": t('attribute_detail.workspace_icon_class'),
"workspaceTabBackgroundColor": t('attribute_detail.workspace_tab_background_color'),
"workspaceCalendarRoot": t('attribute_detail.workspace_calendar_root'),
"workspaceTemplate": t('attribute_detail.workspace_template'),
"searchHome": t('attribute_detail.search_home'),
"workspaceSearchHome": t('attribute_detail.workspace_search_home'),
"inbox": t('attribute_detail.inbox'),
"workspaceInbox": t('attribute_detail.workspace_inbox'),
"sqlConsoleHome": t('attribute_detail.sql_console_home'),
"bookmarkFolder": t('attribute_detail.bookmark_folder'),
"shareHiddenFromTree": t('attribute_detail.share_hidden_from_tree'),
"shareExternalLink": t('attribute_detail.share_external_link'),
"shareAlias": t('attribute_detail.share_alias'),
"shareOmitDefaultCss": t('attribute_detail.share_omit_default_css'),
"shareRoot": t('attribute_detail.share_root'),
"shareDescription": t('attribute_detail.share_description'),
"shareRaw": t('attribute_detail.share_raw'),
"shareDisallowRobotIndexing": t('attribute_detail.share_disallow_robot_indexing'),
"shareCredentials": t('attribute_detail.share_credentials'),
"shareIndex": t('attribute_detail.share_index'),
"displayRelations": t('attribute_detail.display_relations'),
"hideRelations": t('attribute_detail.hide_relations'),
"titleTemplate": t('attribute_detail.title_template'),
"template": t('attribute_detail.template'),
"toc": t('attribute_detail.toc'),
"color": t('attribute_detail.color'),
"keyboardShortcut": t('attribute_detail.keyboard_shortcut'),
"keepCurrentHoisting": t('attribute_detail.keep_current_hoisting'),
"executeButton": t('attribute_detail.execute_button'),
"executeDescription": t('attribute_detail.execute_description'),
"excludeFromNoteMap": t('attribute_detail.exclude_from_note_map'),
"newNotesOnTop": t('attribute_detail.new_notes_on_top'),
"hideHighlightWidget": t('attribute_detail.hide_highlight_widget')
},
"relation": {
"runOnNoteCreation": "executes when note is created on backend. Use this relation if you want to run the script for all notes created under a specific subtree. In that case, create it on the subtree root note and make it inheritable. A new note created within the subtree (any depth) will trigger the script.",
"runOnChildNoteCreation": "executes when new note is created under the note where this relation is defined",
"runOnNoteTitleChange": "executes when note title is changed (includes note creation as well)",
"runOnNoteContentChange": "executes when note content is changed (includes note creation as well).",
"runOnNoteChange": "executes when note is changed (includes note creation as well). Does not include content changes",
"runOnNoteDeletion": "executes when note is being deleted",
"runOnBranchCreation": "executes when a branch is created. Branch is a link between parent note and child note and is created e.g. when cloning or moving note.",
"runOnBranchChange": "executes when a branch is updated.",
"runOnBranchDeletion": "executes when a branch is deleted. Branch is a link between parent note and child note and is deleted e.g. when moving note (old branch/link is deleted).",
"runOnAttributeCreation": "executes when new attribute is created for the note which defines this relation",
"runOnAttributeChange": " executes when the attribute is changed of a note which defines this relation. This is triggered also when the attribute is deleted",
"template": "note's attributes will be inherited even without a parent-child relationship, note's content and subtree will be added to instance notes if empty. See documentation for details.",
"inherit": "note's attributes will be inherited even without a parent-child relationship. See template relation for a similar concept. See attribute inheritance in the documentation.",
"renderNote": 'notes of type "render HTML note" will be rendered using a code note (HTML or script) and it is necessary to point using this relation to which note should be rendered',
"widget": "target of this relation will be executed and rendered as a widget in the sidebar",
"shareCss": "CSS note which will be injected into the share page. CSS note must be in the shared sub-tree as well. Consider using 'shareHiddenFromTree' and 'shareOmitDefaultCss' as well.",
"shareJs": "JavaScript note which will be injected into the share page. JS note must be in the shared sub-tree as well. Consider using 'shareHiddenFromTree'.",
"shareTemplate": "Embedded JavaScript note that will be used as the template for displaying the shared note. Falls back to the default template. Consider using 'shareHiddenFromTree'.",
"shareFavicon": "Favicon note to be set in the shared page. Typically you want to set it to share root and make it inheritable. Favicon note must be in the shared sub-tree as well. Consider using 'shareHiddenFromTree'.",
"runOnNoteCreation": t('attribute_detail.run_on_note_creation'),
"runOnChildNoteCreation": t('attribute_detail.run_on_child_note_creation'),
"runOnNoteTitleChange": t('attribute_detail.run_on_note_title_change'),
"runOnNoteContentChange": t('attribute_detail.run_on_note_content_change'),
"runOnNoteChange": t('attribute_detail.run_on_note_change'),
"runOnNoteDeletion": t('attribute_detail.run_on_note_deletion'),
"runOnBranchCreation": t('attribute_detail.run_on_branch_creation'),
"runOnBranchChange": t('attribute_detail.run_on_branch_change'),
"runOnBranchDeletion": t('attribute_detail.run_on_branch_deletion'),
"runOnAttributeCreation": t('attribute_detail.run_on_attribute_creation'),
"runOnAttributeChange": t('attribute_detail.run_on_attribute_change'),
"template": t('attribute_detail.relation_template'),
"inherit": t('attribute_detail.inherit'),
"renderNote": t('attribute_detail.render_note'),
"widget": t('attribute_detail.widget_relation'),
"shareCss": t('attribute_detail.share_css'),
"shareJs": t('attribute_detail.share_js'),
"shareTemplate": t('attribute_detail.share_template'),
"shareFavicon": t('attribute_detail.share_favicon')
}
};
@ -454,7 +440,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
.show()
.empty()
.append(attribute.type === 'label' ? 'Label' : 'Relation')
.append(' is owned by note ')
.append(` ${t("attribute_detail.is_owned_by_note")} `)
.append(await linkService.createLink(attribute.noteId))
}
@ -615,7 +601,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$relatedNotesContainer.hide();
} else {
this.$relatedNotesContainer.show();
this.$relatedNotesTitle.text(`Other notes with ${this.attribute.type} name "${this.attribute.name}"`);
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", {attributeType: this.attribute.type, attributeName: this.attribute.name}));
this.$relatedNotesList.empty();
@ -633,7 +619,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
if (results.length > DISPLAYED_NOTES) {
this.$relatedNotesMoreNotes.show().text(`... and ${count - DISPLAYED_NOTES} more.`);
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", {count: count - DISPLAYED_NOTES}));
} else {
this.$relatedNotesMoreNotes.hide();
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import server from "../../services/server.js";
@ -11,11 +12,11 @@ import attributeService from "../../services/attributes.js";
import linkService from "../../services/link.js";
const HELP_TEXT = `
<p>To add label, just type e.g. <code>#rock</code> or if you want to add also value then e.g. <code>#year = 2020</code></p>
<p>${t("attribute_editor.help_text_body1")}</p>
<p>For relation, type <code>~author = @</code> which should bring up an autocomplete where you can look up the desired note.</p>
<p>${t("attribute_editor.help_text_body2")}</p>
<p>Alternatively you can add label and relation using the <code>+</code> button on the right side.</p>`;
<p>${t("attribute_editor.help_text_body3")}</p>`;
const TPL = `
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
@ -71,8 +72,8 @@ const TPL = `
<div class="attribute-list-editor" tabindex="200"></div>
<div class="bx bx-save save-attributes-button" title="Save attributes <enter>"></div>
<div class="bx bx-plus add-new-attribute-button" title="Add a new attribute"></div>
<div class="bx bx-save save-attributes-button" title="${t("attribute_editor.save_attributes")}"></div>
<div class="bx bx-plus add-new-attribute-button" title="${t("attribute_editor.add_a_new_attribute")}"></div>
<div class="attribute-errors" style="display: none;"></div>
</div>
@ -217,13 +218,13 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
y: e.pageY,
orientation: 'left',
items: [
{title: `Add new label <kbd data-command="addNewLabel"></kbd>`, command: "addNewLabel", uiIcon: "bx bx-hash"},
{title: `Add new relation <kbd data-command="addNewRelation"></kbd>`, command: "addNewRelation", uiIcon: "bx bx-transfer"},
{title: "----"},
{title: "Add new label definition", command: "addNewLabelDefinition", uiIcon: "bx bx-empty"},
{title: "Add new relation definition", command: "addNewRelationDefinition", uiIcon: "bx bx-empty"},
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ title: "----" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" },
],
selectMenuItemHandler: ({command}) => this.handleAddNewAttributeCommand(command)
selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command)
});
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import ws from "../../services/ws.js";
import utils from "../../services/utils.js";
@ -14,7 +15,7 @@ export default class AbstractBulkAction {
$rendered.find('.action-conf-del')
.on('click', () => this.deleteAction())
.attr('title', 'Remove this search action');
.attr('title', t('abstract_bulk_action.remove_this_search_action'));
utils.initHelpDropdown($rendered);

View File

@ -1,10 +1,11 @@
import { t } from "../../services/i18n.js";
import SpacedUpdate from "../../services/spaced_update.js";
import AbstractBulkAction from "./abstract_bulk_action.js";
const TPL = `
<tr>
<td>
Execute script:
${t('execute_script.execute_script')}
</td>
<td>
<input type="text"
@ -16,13 +17,13 @@ const TPL = `
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
You can execute simple scripts on the matched notes.
${t('execute_script.help_text')}
For example to append a string to a note's title, use this small script:
${t('execute_script.example_1')}
<pre>note.title = note.title + ' - suffix';</pre>
More complex example would be deleting all matched note's attributes:
${t('execute_script.example_2')}
<pre>for (const attr of note.getOwnedAttributes) { attr.markAsDeleted(); }</pre>
</div>
@ -35,7 +36,7 @@ const TPL = `
export default class ExecuteScriptBulkAction extends AbstractBulkAction {
static get actionName() { return "executeScript"; }
static get actionTitle() { return "Execute script"; }
static get actionTitle() { return t("execute_script.execute_script"); }
doRender() {
const $action = $(TPL);

View File

@ -1,3 +1,4 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
@ -5,31 +6,31 @@ const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">Add label</div>
<div style="margin-right: 10px;" class="text-nowrap">${t("add_label.add_label")}</div>
<input type="text"
class="form-control label-name"
placeholder="label name"
placeholder="${t("add_label.label_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t("add_label.label_name_title")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">to value</div>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("add_label.to_value")}</div>
<input type="text" class="form-control label-value" placeholder="new value"/>
<input type="text" class="form-control label-value" placeholder="${t("add_label.new_value_placeholder")}"/>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>On all matched notes:</p>
<p>${t("add_label.help_text")}</p>
<ul>
<li>create given label if note doesn't have one yet</li>
<li>or change value of the existing label</li>
<li>${t("add_label.help_text_item1")}</li>
<li>${t("add_label.help_text_item2")}</li>
</ul>
<p>You can also call this method without value, in such case label will be assigned to the note without value.</p>
<p>${t("add_label.help_text_note")}</p>
</div>
</div>
@ -39,7 +40,7 @@ const TPL = `
export default class AddLabelBulkAction extends AbstractBulkAction {
static get actionName() { return "addLabel"; }
static get actionTitle() { return "Add label"; }
static get actionTitle() { return t("add_label.add_label"); }
doRender() {
const $action = $(TPL);

View File

@ -1,17 +1,18 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = `
<tr>
<td>
Delete label:
${t("delete_label.delete_label")}
</td>
<td>
<input type="text"
class="form-control label-name"
pattern="[\\p{L}\\p{N}_:]+"
title="Alphanumeric characters, underscore and colon are allowed characters."
placeholder="label name"/>
title="${t("delete_label.label_name_title")}"
placeholder="${t("delete_label.label_name_placeholder")}"/>
</td>
<td class="button-column">
<span class="bx bx-x icon-action action-conf-del"></span>
@ -20,7 +21,7 @@ const TPL = `
export default class DeleteLabelBulkAction extends AbstractBulkAction {
static get actionName() { return "deleteLabel"; }
static get actionTitle() { return "Delete label"; }
static get actionTitle() { return t("delete_label.delete_label"); }
doRender() {
const $action = $(TPL);

View File

@ -1,3 +1,4 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
@ -5,21 +6,21 @@ const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px; flex-shrink: 0;">Rename label from:</div>
<div style="margin-right: 10px; flex-shrink: 0;">${t("rename_label.rename_label_from")}</div>
<input type="text"
class="form-control old-label-name"
placeholder="old name"
placeholder="${t("rename_label.old_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t("rename_label.name_title")}"/>
<div style="margin-right: 10px; margin-left: 10px;">To:</div>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("rename_label.to")}</div>
<input type="text"
class="form-control new-label-name"
placeholder="new name"
placeholder="${t("rename_label.new_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t("rename_label.name_title")}"/>
</div>
</td>
<td class="button-column">
@ -29,7 +30,7 @@ const TPL = `
export default class RenameLabelBulkAction extends AbstractBulkAction {
static get actionName() { return "renameLabel"; }
static get actionTitle() { return "Rename label"; }
static get actionTitle() { return t("rename_label.rename_label"); }
doRender() {
const $action = $(TPL);

View File

@ -1,3 +1,4 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
@ -5,26 +6,26 @@ const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">Update label value</div>
<div style="margin-right: 10px;" class="text-nowrap">${t("update_label_value.update_label_value")}</div>
<input type="text"
class="form-control label-name"
placeholder="label name"
placeholder="${t("update_label_value.label_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t("update_label_value.label_name_title")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">to value</div>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("update_label_value.to_value")}</div>
<input type="text" class="form-control label-value" placeholder="new value"/>
<input type="text" class="form-control label-value" placeholder="${t("update_label_value.new_value_placeholder")}"/>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>On all matched notes, change value of the existing label.</p>
<p>${t("update_label_value.help_text")}</p>
<p>You can also call this method without value, in such case label will be assigned to the note without value.</p>
<p>${t("update_label_value.help_text_note")}</p>
</div>
</div>
@ -34,7 +35,7 @@ const TPL = `
export default class UpdateLabelValueBulkAction extends AbstractBulkAction {
static get actionName() { return "updateLabelValue"; }
static get actionTitle() { return "Update label value"; }
static get actionTitle() { return t("update_label_value.update_label_value"); }
doRender() {
const $action = $(TPL);

View File

@ -1,3 +1,4 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = `
@ -5,17 +6,17 @@ const TPL = `
<td colspan="2">
<span class="bx bx-trash"></span>
Delete matched notes
${t("delete_note.delete_matched_notes")}
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>This will delete matched notes.</p>
<p>${t("delete_note.delete_matched_notes_description")}</p>
<p>After the deletion, it's possible to undelete them from <span class="bx bx-history"></span> Recent Notes dialog.</p>
<p>${t("delete_note.undelete_notes_instruction")}</p>
<p>To erase notes permanently, you can go after the deletion to the Option -> Other and click the "Erase deleted notes now" button.</p>
<p>${t("delete_note.erase_notes_instruction")}</p>
</div>
</div>
@ -25,7 +26,7 @@ const TPL = `
export default class DeleteNoteBulkAction extends AbstractBulkAction {
static get actionName() { return "deleteNote"; }
static get actionTitle() { return "Delete note"; }
static get actionTitle() { return t("delete_note.delete_note"); }
doRender() {
return $(TPL);

View File

@ -1,27 +1,26 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = `
<tr>
<td colspan="2">
<span class="bx bx-trash"></span>
Delete note revisions
${t('delete_revisions.delete_note_revisions')}
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
All past note revisions of matched notes will be deleted. Note itself will be fully preserved. In other terms, note's history will be removed.
${t('delete_revisions.all_past_note_revisions')}
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class DeleteRevisionsBulkAction extends AbstractBulkAction {
static get actionName() { return "deleteRevisions"; }
static get actionTitle() { return "Delete note revisions"; }
static get actionTitle() { return t('delete_revisions.delete_note_revisions'); }
doRender() {
return $(TPL);

View File

@ -1,3 +1,4 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
@ -6,12 +7,12 @@ const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">Move note</div>
<div style="margin-right: 10px;" class="text-nowrap">${t('move_note.move_note')}</div>
<div style="margin-right: 10px;" class="text-nowrap">to</div>
<div style="margin-right: 10px;" class="text-nowrap">${t('move_note.to')}</div>
<div class="input-group">
<input type="text" class="form-control target-parent-note" placeholder="target parent note"/>
<input type="text" class="form-control target-parent-note" placeholder="${t('move_note.target_parent_note')}"/>
</div>
</div>
</td>
@ -19,12 +20,12 @@ const TPL = `
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>On all matched notes:</p>
<p>${t('move_note.on_all_matched_notes')}:</p>
<ul>
<li>move note to the new parent if note has only one parent (i.e. the old placement is removed and new placement into the new parent is created)</li>
<li>clone note to the new parent if note has multiple clones/placements (it's not clear which placement should be removed)</li>
<li>nothing will happen if note cannot be moved to the target note (i.e. this would create a tree cycle)</li>
<li>${t('move_note.move_note_new_parent')}</li>
<li>${t('move_note.clone_note_new_parent')}</li>
<li>${t('move_note.nothing_will_happen')}</li>
</ul>
</div>
</div>
@ -35,7 +36,7 @@ const TPL = `
export default class MoveNoteBulkAction extends AbstractBulkAction {
static get actionName() { return "moveNote"; }
static get actionTitle() { return "Move note"; }
static get actionTitle() { return t('move_note.move_note'); }
doRender() {
const $action = $(TPL);

View File

@ -1,31 +1,32 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px; flex-shrink: 0;">Rename note title to:</div>
<div style="margin-right: 10px; flex-shrink: 0;">${t('rename_note.rename_note_title_to')}</div>
<input type="text"
class="form-control new-title"
placeholder="new note title"
title="Click help icon on the right to see all the options"/>
placeholder="${t('rename_note.new_note_title')}"
title="${t('rename_note.click_help_icon')}"/>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>The given value is evaluated as JavaScript string and thus can be enriched with dynamic content via the injected <code>note</code> variable (note being renamed). Examples:</p>
<p>${t('rename_note.evaluated_as_js_string')}</p>
<ul>
<li><code>Note</code> - all matched notes are renamed to "Note"</li>
<li><code>NEW: \${note.title}</code> - matched notes titles are prefixed with "NEW: "</li>
<li><code>\${note.dateCreatedObj.format('MM-DD:')}: \${note.title}</code> - matched notes are prefixed with note's creation month-date</li>
<li>${t('rename_note.example_note')}</li>
<li>${t('rename_note.example_new_title')}</li>
<li>${t('rename_note.example_date_prefix')}</li>
</ul>
See API docs for <a href="https://zadam.github.io/trilium/backend_api/Note.html">note</a> and its <a href="https://day.js.org/docs/en/display/format">dateCreatedObj / utcDateCreatedObj properties</a> for details.
${t('rename_note.api_docs')}
</div>
</div>
@ -35,7 +36,7 @@ const TPL = `
export default class RenameNoteBulkAction extends AbstractBulkAction {
static get actionName() { return "renameNote"; }
static get actionTitle() { return "Rename note"; }
static get actionTitle() { return t('rename_note.rename_note'); }
doRender() {
const $action = $(TPL);

View File

@ -1,24 +1,25 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
import { t } from "../../../services/i18n.js";
const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">Add relation</div>
<div style="margin-right: 10px;" class="text-nowrap">${t('add_relation.add_relation')}</div>
<input type="text"
class="form-control relation-name"
placeholder="relation name"
placeholder="${t('add_relation.relation_name')}"
pattern="[\\p{L}\\p{N}_:]+"
style="flex-shrink: 3"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t('add_relation.allowed_characters')}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">to</div>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t('add_relation.to')}</div>
<div class="input-group" style="flex-shrink: 2">
<input type="text" class="form-control target-note" placeholder="target note"/>
<input type="text" class="form-control target-note" placeholder="${t('add_relation.target_note')}"/>
</div>
</div>
</td>
@ -26,7 +27,7 @@ const TPL = `
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>On all matched notes create given relation.</p>
<p>${t('add_relation.create_relation_on_all_matched_notes')}</p>
</div>
</div>
@ -36,7 +37,7 @@ const TPL = `
export default class AddRelationBulkAction extends AbstractBulkAction {
static get actionName() { return "addRelation"; }
static get actionTitle() { return "Add relation"; }
static get actionTitle() { return t('add_relation.add_relation'); }
doRender() {
const $action = $(TPL);
@ -55,7 +56,7 @@ export default class AddRelationBulkAction extends AbstractBulkAction {
relationName: $relationName.val(),
targetNoteId: $targetNote.getSelectedNoteId()
});
}, 1000)
}, 1000);
$relationName.on('input', () => spacedUpdate.scheduleUpdate());
$targetNote.on('input', () => spacedUpdate.scheduleUpdate());

View File

@ -1,18 +1,19 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
const TPL = `
<tr>
<td>
Delete relation:
${t('delete_relation.delete_relation')}
</td>
<td>
<div style="display: flex; align-items: center">
<input type="text"
class="form-control relation-name"
pattern="[\\p{L}\\p{N}_:]+"
placeholder="relation name"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
placeholder="${t('delete_relation.relation_name')}"
title="${t('delete_relation.allowed_characters')}"/>
</div>
</td>
<td class="button-column">
@ -22,7 +23,7 @@ const TPL = `
export default class DeleteRelationBulkAction extends AbstractBulkAction {
static get actionName() { return "deleteRelation"; }
static get actionTitle() { return "Delete relation"; }
static get actionTitle() { return t('delete_relation.delete_relation'); }
doRender() {
const $action = $(TPL);
@ -31,7 +32,7 @@ export default class DeleteRelationBulkAction extends AbstractBulkAction {
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({ relationName: $relationName.val() });
}, 1000)
}, 1000);
$relationName.on('input', () => spacedUpdate.scheduleUpdate());

View File

@ -1,25 +1,26 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px; flex-shrink: 0;">Rename relation from:</div>
<div style="margin-right: 10px; flex-shrink: 0;">${t('rename_relation.rename_relation_from')}</div>
<input type="text"
class="form-control old-relation-name"
placeholder="old name"
placeholder="${t('rename_relation.old_name')}"
pattern="[\\p{L}\\p{N}_:]+"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t('rename_relation.allowed_characters')}"/>
<div style="margin-right: 10px; margin-left: 10px;">To:</div>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t('rename_relation.to')}</div>
<input type="text"
class="form-control new-relation-name"
placeholder="new name"
placeholder="${t('rename_relation.new_name')}"
pattern="[\\p{L}\\p{N}_:]+"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t('rename_relation.allowed_characters')}"/>
</div>
</td>
<td class="button-column">
@ -29,7 +30,7 @@ const TPL = `
export default class RenameRelationBulkAction extends AbstractBulkAction {
static get actionName() { return "renameRelation"; }
static get actionTitle() { return "Rename relation"; }
static get actionTitle() { return t('rename_relation.rename_relation'); }
doRender() {
const $action = $(TPL);
@ -45,7 +46,7 @@ export default class RenameRelationBulkAction extends AbstractBulkAction {
oldRelationName: $oldRelationName.val(),
newRelationName: $newRelationName.val()
});
}, 1000)
}, 1000);
$oldRelationName.on('input', () => spacedUpdate.scheduleUpdate());
$newRelationName.on('input', () => spacedUpdate.scheduleUpdate());

View File

@ -1,24 +1,25 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
import { t } from "../../../services/i18n.js";
const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">Update relation</div>
<div style="margin-right: 10px;" class="text-nowrap">${t('update_relation_target.update_relation')}</div>
<input type="text"
class="form-control relation-name"
placeholder="relation name"
placeholder="${t('update_relation_target.relation_name')}"
pattern="[\\p{L}\\p{N}_:]+"
style="flex-shrink: 3"
title="Alphanumeric characters, underscore and colon are allowed characters."/>
title="${t('update_relation_target.allowed_characters')}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">to</div>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t('update_relation_target.to')}</div>
<div class="input-group" style="flex-shrink: 2">
<input type="text" class="form-control target-note" placeholder="target note"/>
<input type="text" class="form-control target-note" placeholder="${t('update_relation_target.target_note')}"/>
</div>
</div>
</td>
@ -26,11 +27,11 @@ const TPL = `
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>On all matched notes:</p>
<p>${t('update_relation_target.on_all_matched_notes')}:</p>
<ul>
<li>create given relation if note doesn't have one yet</li>
<li>or change target note of the existing relation</li>
<li>${t('update_relation_target.create_given_relation')}</li>
<li>${t('update_relation_target.change_target_note')}</li>
</ul>
</div>
</div>
@ -41,7 +42,7 @@ const TPL = `
export default class UpdateRelationTargetBulkAction extends AbstractBulkAction {
static get actionName() { return "updateRelationTarget"; }
static get actionTitle() { return "Update relation target"; }
static get actionTitle() { return t('update_relation_target.update_relation_target'); }
doRender() {
const $action = $(TPL);
@ -60,7 +61,7 @@ export default class UpdateRelationTargetBulkAction extends AbstractBulkAction {
relationName: $relationName.val(),
targetNoteId: $targetNote.getSelectedNoteId()
});
}, 1000)
}, 1000);
$relationName.on('input', () => spacedUpdate.scheduleUpdate());
$targetNote.on('input', () => spacedUpdate.scheduleUpdate());

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import server from "../../services/server.js";
import dialogService from "../../services/dialog.js";
@ -32,15 +33,15 @@ const TPL = `
<div class="dropdown-menu dropdown-menu-right">
<a data-trigger-command="openAttachment" class="dropdown-item"
title="File will be open in an external application and watched for changes. You'll then be able to upload the modified version back to Trilium.">Open externally</a>
title="${t('attachments_actions.open_externally_title')}">${t('attachments_actions.open_externally')}</a>
<a data-trigger-command="openAttachmentCustom" class="dropdown-item"
title="File will be open in an external application and watched for changes. You'll then be able to upload the modified version back to Trilium.">Open custom</a>
<a data-trigger-command="downloadAttachment" class="dropdown-item">Download</a>
<a data-trigger-command="renameAttachment" class="dropdown-item">Rename attachment</a>
<a data-trigger-command="uploadNewAttachmentRevision" class="dropdown-item">Upload new revision</a>
<a data-trigger-command="copyAttachmentLinkToClipboard" class="dropdown-item">Copy link to clipboard</a>
<a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item">Convert attachment into note</a>
<a data-trigger-command="deleteAttachment" class="dropdown-item">Delete attachment</a>
title="${t('attachments_actions.open_custom_title')}">${t('attachments_actions.open_custom')}</a>
<a data-trigger-command="downloadAttachment" class="dropdown-item">${t('attachments_actions.download')}</a>
<a data-trigger-command="renameAttachment" class="dropdown-item">${t('attachments_actions.rename_attachment')}</a>
<a data-trigger-command="uploadNewAttachmentRevision" class="dropdown-item">${t('attachments_actions.upload_new_revision')}</a>
<a data-trigger-command="copyAttachmentLinkToClipboard" class="dropdown-item">${t('attachments_actions.copy_link_to_clipboard')}</a>
<a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item">${t('attachments_actions.convert_attachment_into_note')}</a>
<a data-trigger-command="deleteAttachment" class="dropdown-item">${t('attachments_actions.delete_attachment')}</a>
</div>
<input type="file" class="attachment-upload-new-revision-input" style="display: none">
@ -70,26 +71,24 @@ export default class AttachmentActionsWidget extends BasicWidget {
const result = await server.upload(`attachments/${this.attachmentId}/file`, fileToUpload);
if (result.uploaded) {
toastService.showMessage("New attachment revision has been uploaded.");
toastService.showMessage(t('attachments_actions.upload_success'));
} else {
toastService.showError("Upload of a new attachment revision failed.");
toastService.showError(t('attachments_actions.upload_failed'));
}
});
if (!this.isFullDetail) {
// we deactivate this button because the WatchedFileUpdateStatusWidget assumes only one visible attachment
// in a note context, so it doesn't work in a list
const $openAttachmentButton = this.$widget.find("[data-trigger-command='openAttachment']");
$openAttachmentButton
.addClass("disabled")
.append($('<span class="disabled-tooltip"> (?)</span>')
.attr("title", "Opening attachment externally is available only from the detail page, please first click on the attachment detail first and repeat the action.")
.attr("title", t('attachments_actions.open_externally_detail_page'))
);
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton
.addClass("disabled")
.append($('<span class="disabled-tooltip"> (?)</span>')
.attr("title", "Opening attachment externally is available only from the detail page, please first click on the attachment detail first and repeat the action.")
.attr("title", t('attachments_actions.open_externally_detail_page'))
);
}
if (!utils.isElectron()){
@ -97,7 +96,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
$openAttachmentCustomButton
.addClass("disabled")
.append($('<span class="disabled-tooltip"> (?)</span>')
.attr("title", "Custom opening of attachments can only be done from the client.")
.attr("title", t('attachments_actions.open_custom_client_only'))
);
}
}
@ -123,29 +122,29 @@ export default class AttachmentActionsWidget extends BasicWidget {
}
async deleteAttachmentCommand() {
if (!await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) {
if (!await dialogService.confirm(t('attachments_actions.delete_confirm', { title: this.attachment.title }))) {
return;
}
await server.remove(`attachments/${this.attachmentId}`);
toastService.showMessage(`Attachment '${this.attachment.title}' has been deleted.`);
toastService.showMessage(t('attachments_actions.delete_success', { title: this.attachment.title }));
}
async convertAttachmentIntoNoteCommand() {
if (!await dialogService.confirm(`Are you sure you want to convert attachment '${this.attachment.title}' into a separate note?`)) {
if (!await dialogService.confirm(t('attachments_actions.convert_confirm', { title: this.attachment.title }))) {
return;
}
const {note: newNote} = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
toastService.showMessage(`Attachment '${this.attachment.title}' has been converted to note.`);
toastService.showMessage(t('attachments_actions.convert_success', { title: this.attachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
}
async renameAttachmentCommand() {
const attachmentTitle = await dialogService.prompt({
title: "Rename attachment",
message: "Please enter new attachment's name",
title: t('attachments_actions.rename_attachment'),
message: t('attachments_actions.enter_new_name'),
defaultValue: this.attachment.title
});

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import libraryLoader from "../../services/library_loader.js";
import utils from "../../services/utils.js";
import dateNoteService from "../../services/date_notes.js";
@ -23,7 +24,7 @@ const DROPDOWN_TPL = `
</div>
<div class="calendar-week">
<span>Mon</span> <span>Tue</span><span>Wed</span> <span>Thu</span> <span>Fri</span> <span>Sat</span> <span>Sun</span>
<span>${t("calendar.mon")}</span> <span>${t("calendar.tue")}</span><span>${t("calendar.wed")}</span> <span>${t("calendar.thu")}</span> <span>${t("calendar.fri")}</span> <span>${t("calendar.sat")}</span> <span>${t("calendar.sun")}</span>
</div>
<div class="calendar-body" data-calendar-area="month"></div>
</div>`;
@ -63,7 +64,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.hideDropdown();
}
else {
toastService.showError("Cannot find day note");
toastService.showError(t("calendar.cannot_find_day_note"));
}
});
}
@ -153,23 +154,23 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.date.setDate(1);
this.date.setMonth(this.date.getMonth() - 1);
this.$label.html(`${this.monthsAsString(this.date.getMonth())} ${this.date.getFullYear()}`);
this.$label.html(`${t(this.monthsAsString(this.date.getMonth()).toLowerCase())} ${this.date.getFullYear()}`);
}
monthsAsString(monthIndex) {
return [
'January',
'Febuary',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
t("calendar.january"),
t("calendar.febuary"),
t("calendar.march"),
t("calendar.april"),
t("calendar.may"),
t("calendar.june"),
t("calendar.july"),
t("calendar.august"),
t("calendar.september"),
t("calendar.october"),
t("calendar.november"),
t("calendar.december")
][monthIndex];
}
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import OnClickButtonWidget from "./onclick_button.js";
export default class ClosePaneButton extends OnClickButtonWidget {
@ -15,7 +16,7 @@ export default class ClosePaneButton extends OnClickButtonWidget {
super();
this.icon("bx-x")
.title("Close this pane")
.title(t("close_pane_button.close_this_pane"))
.titlePlacement("bottom")
.onClick((widget, e) => {
// to avoid split pane container detecting click within the pane which would try to activate this

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import OnClickButtonWidget from "./onclick_button.js";
export default class CreatePaneButton extends OnClickButtonWidget {
@ -5,7 +6,7 @@ export default class CreatePaneButton extends OnClickButtonWidget {
super();
this.icon("bx-dock-right")
.title("Create new split")
.title(t("create_pane_button.create_new_split"))
.titlePlacement("bottom")
.onClick(widget => widget.triggerCommand("openNewNoteSplit", { ntxId: widget.getClosestNtxId() }))
.class("icon-action");

View File

@ -2,6 +2,7 @@ import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import protectedSessionHolder from "../../services/protected_session_holder.js";
import { t } from "../../services/i18n.js";
export default class EditButton extends OnClickButtonWidget {
isEnabled() {
@ -14,7 +15,7 @@ export default class EditButton extends OnClickButtonWidget {
super();
this.icon("bx-edit-alt")
.title("Edit this note")
.title(t("edit_button.edit_this_note"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.readOnlyTemporarilyDisabled = true;

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import utils from "../../services/utils.js";
import UpdateAvailableWidget from "./update_available.js";
@ -103,7 +104,7 @@ const TPL = `
<button type="button" data-toggle="dropdown" data-placement="right"
aria-haspopup="true" aria-expanded="false"
class="icon-action global-menu-button" title="Menu">
class="icon-action global-menu-button" title="${t('global_menu.menu')}">
<svg viewBox="0 0 256 256">
<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"/>
@ -126,112 +127,112 @@ const TPL = `
<ul class="dropdown-menu dropdown-menu-right">
<li class="dropdown-item" data-trigger-command="showOptions">
<span class="bx bx-slider"></span>
Options
${t('global_menu.options')}
</li>
<li class="dropdown-item" data-trigger-command="openNewWindow">
<span class="bx bx-window-open"></span>
Open New Window
${t('global_menu.open_new_window')}
<kbd data-command="openNewWindow"></kbd>
</li>
<li class="dropdown-item switch-to-mobile-version-button" data-trigger-command="switchToMobileVersion">
<span class="bx bx-mobile"></span>
Switch to Mobile Version
${t('global_menu.switch_to_mobile_version')}
</li>
<li class="dropdown-item switch-to-desktop-version-button" data-trigger-command="switchToDesktopVersion">
<span class="bx bx-desktop"></span>
Switch to Desktop Version
${t('global_menu.switch_to_desktop_version')}
</li>
<span class="zoom-container dropdown-item">
<div>
<span class="bx bx-empty"></span>
Zoom
${t('global_menu.zoom')}
</div>
<div class="zoom-buttons">
<a data-trigger-command="toggleFullscreen" title="Toggle Fullscreen" class="bx bx-expand-alt"></a>
<a data-trigger-command="toggleFullscreen" title="${t('global_menu.toggle_fullscreen')}" class="bx bx-expand-alt"></a>
&nbsp;
<a data-trigger-command="zoomOut" title="Zoom Out" class="bx bx-minus"></a>
<a data-trigger-command="zoomOut" title="${t('global_menu.zoom_out')}" class="bx bx-minus"></a>
<span data-trigger-command="zoomReset" title="Reset Zoom Level" class="zoom-state"></span>
<span data-trigger-command="zoomReset" title="${t('global_menu.reset_zoom_level')}" class="zoom-state"></span>
<a data-trigger-command="zoomIn" title="Zoom In" class="bx bx-plus"></a>
<a data-trigger-command="zoomIn" title="${t('global_menu.zoom_in')}" class="bx bx-plus"></a>
</div>
</span>
<li class="dropdown-item" data-trigger-command="showLaunchBarSubtree">
<span class="bx bx-sidebar"></span>
Configure Launchbar
${t('global_menu.configure_launchbar')}
</li>
<li class="dropdown-item" data-trigger-command="showShareSubtree">
<span class="bx bx-share-alt"></span>
Show Shared Notes Subtree
${t('global_menu.show_shared_notes_subtree')}
</li>
<li class="dropdown-item dropdown-submenu">
<span class="dropdown-toggle">
<span class="bx bx-empty"></span>
Advanced
${t('global_menu.advanced')}
</span>
<ul class="dropdown-menu">
<li class="dropdown-item open-dev-tools-button" data-trigger-command="openDevTools">
<span class="bx bx-bug-alt"></span>
Open Dev Tools
${t('global_menu.open_dev_tools')}
<kbd data-command="openDevTools"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showSQLConsole">
<span class="bx bx-data"></span>
Open SQL Console
${t('global_menu.open_sql_console')}
<kbd data-command="showSQLConsole"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
<span class="bx bx-empty"></span>
Open SQL Console History
${t('global_menu.open_sql_console_history')}
</li>
<li class="dropdown-item" data-trigger-command="showSearchHistory">
<span class="bx bx-empty"></span>
Open Search History
${t('global_menu.open_search_history')}
</li>
<li class="dropdown-item" data-trigger-command="showBackendLog">
<span class="bx bx-empty"></span>
Show Backend Log
${t('global_menu.show_backend_log')}
<kbd data-command="showBackendLog"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
title="Reload can help with some visual glitches without restarting the whole app.">
title="${t('global_menu.reload_hint')}">
<span class="bx bx-empty"></span>
Reload Frontend
${t('global_menu.reload_frontend')}
<kbd data-command="reloadFrontendApp"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showHiddenSubtree">
<span class="bx bx-empty"></span>
Show Hidden Subtree
${t('global_menu.show_hidden_subtree')}
</li>
</ul>
</li>
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
<span class="bx bx-info-circle"></span>
Show Help
${t('global_menu.show_help')}
<kbd data-command="showHelp"></kbd>
</li>
<li class="dropdown-item show-about-dialog-button">
<span class="bx bx-empty"></span>
About TriliumNext Notes
${t('global_menu.about')}
</li>
<li class="dropdown-item update-to-latest-version-button" data-trigger-command="downloadLatestVersion">
@ -242,7 +243,7 @@ const TPL = `
<li class="dropdown-item logout-button" data-trigger-command="logout">
<span class="bx bx-log-out"></span>
Logout
${t('global_menu.logout')}
</li>
</ul>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "../../../services/i18n.js";
import AbstractLauncher from "./abstract_launcher.js";
import dialogService from "../../../services/dialog.js";
import appContext from "../../../components/app_context.js";
@ -58,7 +59,7 @@ export default class NoteLauncher extends AbstractLauncher {
const targetNoteId = this.launcherNote.getRelationValue('target');
if (!targetNoteId) {
dialogService.info("This launcher doesn't define target note.");
dialogService.info(t("note_launcher.this_launcher_doesnt_define_target_note"));
return;
}

View File

@ -1,6 +1,7 @@
import options from "../../services/options.js";
import splitService from "../../services/resizer.js";
import CommandButtonWidget from "./command_button.js";
import { t } from "../../services/i18n.js";
export default class LeftPaneToggleWidget extends CommandButtonWidget {
constructor() {
@ -13,8 +14,8 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget {
: "bx-chevrons-right";
this.settings.title = () => options.is('leftPaneVisible')
? "Hide panel"
: "Open panel";
? t("left_pane_toggle.hide_panel")
: t("left_pane_toggle.show_panel");
this.settings.command = () => options.is('leftPaneVisible')
? "hideLeftPane"

View File

@ -1,5 +1,6 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
export default class MovePaneButton extends OnClickButtonWidget {
constructor(isMovingLeft) {
@ -8,7 +9,7 @@ export default class MovePaneButton extends OnClickButtonWidget {
this.isMovingLeft = isMovingLeft;
this.icon(isMovingLeft ? "bx-chevron-left" : "bx-chevron-right")
.title(isMovingLeft ? "Move left" : "Move right")
.title(isMovingLeft ? t("move_pane_button.move_left") : t("move_pane_button.move_right"))
.titlePlacement("bottom")
.onClick(async (widget, e) => {
e.stopPropagation();

View File

@ -6,6 +6,7 @@ import server from "../../services/server.js";
import toastService from "../../services/toast.js";
import ws from "../../services/ws.js";
import appContext from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="dropdown note-actions">
@ -30,22 +31,22 @@ const TPL = `
aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
<div class="dropdown-menu dropdown-menu-right">
<a data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">Convert into attachment</a>
<a data-trigger-command="renderActiveNote" class="dropdown-item render-note-button"><kbd data-command="renderActiveNote"></kbd> Re-render note</a>
<a data-trigger-command="findInText" class="dropdown-item find-in-text-button">Search in note <kbd data-command="findInText"></a>
<a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a>
<a data-trigger-command="showAttachments" class="dropdown-item"><kbd data-command="showAttachments"></kbd> Note attachments</a>
<a data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">${t('note_actions.convert_into_attachment')}</a>
<a data-trigger-command="renderActiveNote" class="dropdown-item render-note-button"><kbd data-command="renderActiveNote"></kbd> ${t('note_actions.re_render_note')}</a>
<a data-trigger-command="findInText" class="dropdown-item find-in-text-button">${t('note_actions.search_in_note')} <kbd data-command="findInText"></kbd></a>
<a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> ${t('note_actions.note_source')}</a>
<a data-trigger-command="showAttachments" class="dropdown-item"><kbd data-command="showAttachments"></kbd> ${t('note_actions.note_attachments')}</a>
<a data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button"
title="File will be open in an external application and watched for changes. You'll then be able to upload the modified version back to Trilium.">
title="${t('note_actions.open_note_externally_title')}">
<kbd data-command="openNoteExternally"></kbd>
Open note externally
${t('note_actions.open_note_externally')}
</a>
<a data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button"><kbd data-command="openNoteCustom"></kbd> Open note custom</a>
<a class="dropdown-item import-files-button">Import files</a>
<a class="dropdown-item export-note-button">Export note</a>
<a class="dropdown-item delete-note-button">Delete note</a>
<a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
<a data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button"><kbd data-command="forceSaveRevision"></kbd> Save revision</a>
<a data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button"><kbd data-command="openNoteCustom"></kbd> ${t('note_actions.open_note_custom')}</a>
<a class="dropdown-item import-files-button">${t('note_actions.import_files')}</a>
<a class="dropdown-item export-note-button">${t('note_actions.export_note')}</a>
<a class="dropdown-item delete-note-button">${t('note_actions.delete_note')}</a>
<a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> ${t('note_actions.print_note')}</a>
<a data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button"><kbd data-command="forceSaveRevision"></kbd> ${t('note_actions.save_revision')}</a>
</div>
</div>`;

View File

@ -1,4 +1,5 @@
import AbstractButtonWidget from "./abstract_button.js";
import { t } from "../../services/i18n.js";
export default class OnClickButtonWidget extends AbstractButtonWidget {
doRender() {
@ -12,7 +13,7 @@ export default class OnClickButtonWidget extends AbstractButtonWidget {
this.settings.onClick(this, e);
});
} else {
console.warn(`Button widget '${this.componentId}' has no defined click handler`, this.settings);
console.warn(t("onclick_button.no_click_handler", { componentId: this.componentId }), this.settings);
}
if (this.settings.onAuxClick) {

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import protectedSessionHolder from "../../services/protected_session_holder.js";
import CommandButtonWidget from "./command_button.js";
@ -12,8 +13,8 @@ export default class ProtectedSessionStatusWidget extends CommandButtonWidget {
: "bx-shield-quarter";
this.settings.title = () => protectedSessionHolder.isProtectedSessionAvailable()
? "Protected session is active. Click to leave protected session."
: "Click to enter protected session";
? t("protected_session_status.active")
: t("protected_session_status.inactive");
this.settings.command = () => protectedSessionHolder.isProtectedSessionAvailable()
? "leaveProtectedSession"

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import CommandButtonWidget from "./command_button.js";
export default class RevisionsButton extends CommandButtonWidget {
@ -5,7 +6,7 @@ export default class RevisionsButton extends CommandButtonWidget {
super();
this.icon('bx-history')
.title("Note Revisions")
.title(t("revisions_button.note_revisions"))
.command("showRevisions")
.titlePlacement("bottom")
.class("icon-action");

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
const TPL = `
@ -23,7 +24,7 @@ const TPL = `
}
</style>
<span class="bx bx-sync global-menu-button-update-available-button" title="Update available"></span>
<span class="bx bx-sync global-menu-button-update-available-button" title="${t('update_available.update_available')}"></span>
</div>
`;

View File

@ -15,7 +15,7 @@ const TPL = `
</button>
</div>
<div class="modal-body">
<table class="table table-borderless">
<table class="table table-borderless text-nowrap">
<tr>
<th>${t("about.homepage")}</th>
<td><a href="https://github.com/TriliumNext/Notes" class="external">https://github.com/TriliumNext/Notes</a></td>

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import treeService from '../../services/tree.js';
import noteAutocompleteService from "../../services/note_autocomplete.js";
import utils from "../../services/utils.js";
@ -8,21 +9,21 @@ const TPL = `
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Add link</h5>
<h5 class="modal-title mr-auto">${t('add_link.add_link')}</h5>
<button type="button" class="help-button" title="Help on links" data-help-page="links.html">?</button>
<button type="button" class="help-button" title="${t('add_link.help_on_links')}" data-help-page="links.html">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<button type="button" class="close" data-dismiss="modal" aria-label="${t('add_link.close')}" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form class="add-link-form">
<div class="modal-body">
<div class="form-group">
<label for="add-link-note-autocomplete">Note</label>
<label for="add-link-note-autocomplete">${t('add_link.note')}</label>
<div class="input-group">
<input class="add-link-note-autocomplete form-control" placeholder="search for note by its name">
<input class="add-link-note-autocomplete form-control" placeholder="${t('add_link.search_note')}">
</div>
</div>
@ -30,20 +31,20 @@ const TPL = `
<div class="add-link-title-radios form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="link-type" value="reference-link" checked>
link title mirrors the note's current title
${t('add_link.link_title_mirrors')}
</label>
</div>
<div class="add-link-title-radios form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="link-type" value="hyper-link">
link title can be changed arbitrarily
${t('add_link.link_title_arbitrary')}
</label>
</div>
<div class="add-link-title-form-group form-group">
<br/>
<label>
Link title
${t('add_link.link_title')}
<input class="link-title form-control" style="width: 100%;">
</label>
@ -51,7 +52,7 @@ const TPL = `
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Add link <kbd>enter</kbd></button>
<button type="submit" class="btn btn-primary">${t('add_link.add_link')} <kbd>enter</kbd></button>
</div>
</form>
</div>

View File

@ -5,6 +5,7 @@ import toastService from "../../services/toast.js";
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
import appContext from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
let branchId;
@ -13,17 +14,17 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
<form class="branch-prefix-form">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Edit branch prefix</h5>
<h5 class="modal-title mr-auto">${t('branch_prefix.edit_branch_prefix')}</h5>
<button class="help-button" type="button" data-help-page="tree-concepts.html#prefix" title="Help on Tree prefix">?</button>
<button class="help-button" type="button" data-help-page="tree-concepts.html#prefix" title="${t('branch_prefix.help_on_tree_prefix')}">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<button type="button" class="close" data-dismiss="modal" aria-label="${t('branch_prefix.close')}" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="branch-prefix-input">Prefix: </label> &nbsp;
<label for="branch-prefix-input">${t('branch_prefix.prefix')}</label> &nbsp;
<div class="input-group">
<input class="branch-prefix-input form-control">
@ -35,7 +36,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm">Save</button>
<button class="btn btn-primary btn-sm">${t('branch_prefix.save')}</button>
</div>
</div>
</form>
@ -100,6 +101,6 @@ export default class BranchPrefixDialog extends BasicWidget {
this.$widget.modal('hide');
toastService.showMessage("Branch prefix has been saved.");
toastService.showMessage(t('branch_prefix.branch_prefix_saved'));
}
}

View File

@ -4,6 +4,7 @@ import bulkActionService from "../../services/bulk_action.js";
import utils from "../../services/utils.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="bulk-actions-dialog modal mx-auto" tabindex="-1" role="dialog">
@ -37,32 +38,32 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Bulk actions</h5>
<h5 class="modal-title mr-auto">${t('bulk_actions.bulk_actions')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<button type="button" class="close" data-dismiss="modal" aria-label="${t('bulk_actions.close')}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h4>Affected notes: <span class="affected-note-count">0</span></h4>
<h4>${t('bulk_actions.affected_notes')}: <span class="affected-note-count">0</span></h4>
<div class="form-check">
<label class="form-check-label">
<input class="include-descendants form-check-input" type="checkbox" value="">
Include descendants of the selected notes
${t('bulk_actions.include_descendants')}
</label>
</div>
<h4>Available actions</h4>
<h4>${t('bulk_actions.available_actions')}</h4>
<table class="bulk-available-action-list"></table>
<h4>Chosen actions</h4>
<h4>${t('bulk_actions.chosen_actions')}</h4>
<table class="bulk-existing-action-list"></table>
</div>
<div class="modal-footer">
<button type="submit" class="execute-bulk-actions btn btn-primary">Execute bulk actions</button>
<button type="submit" class="execute-bulk-actions btn btn-primary">${t('bulk_actions.execute_bulk_actions')}</button>
</div>
</div>
</div>
@ -95,7 +96,7 @@ export default class BulkActionsDialog extends BasicWidget {
includeDescendants: this.$includeDescendants.is(":checked")
});
toastService.showMessage("Bulk actions have been executed successfully.", 3000);
toastService.showMessage(t('bulk_actions.bulk_actions_executed'), 3000);
utils.closeActiveDialog();
});
@ -120,7 +121,7 @@ export default class BulkActionsDialog extends BasicWidget {
if (actions.length > 0) {
this.$existingActionList.append(...actions.map(action => action.render()));
} else {
this.$existingActionList.append($("<p>None yet ... add an action by clicking one of the available ones above.</p>"))
this.$existingActionList.append($("<p>").text(t('bulk_actions.none_yet')))
}
}

View File

@ -6,15 +6,16 @@ import froca from "../../services/froca.js";
import branchService from "../../services/branches.js";
import appContext from "../../components/app_context.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="clone-to-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Clone notes to ...</h5>
<h5 class="modal-title mr-auto">${t('clone_to.clone_notes_to')}</h5>
<button type="button" class="help-button" title="Help on links" data-help-page="cloning-notes.html">?</button>
<button type="button" class="help-button" title="${t('clone_to.help_on_links')}" data-help-page="cloning-notes.html">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
@ -22,28 +23,28 @@ const TPL = `
</div>
<form class="clone-to-form">
<div class="modal-body">
<h5>Notes to clone</h5>
<h5>${t('clone_to.notes_to_clone')}</h5>
<ul class="clone-to-note-list" style="max-height: 200px; overflow: auto;"></ul>
<div class="form-group">
<label style="width: 100%">
Target parent note
${t('clone_to.target_parent_note')}
<div class="input-group">
<input class="clone-to-note-autocomplete form-control" placeholder="search for note by its name">
<input class="clone-to-note-autocomplete form-control" placeholder="${t('clone_to.search_for_note_by_its_name')}">
</div>
</label>
</div>
<div class="form-group" title="Cloned note will be shown in note tree with given prefix">
<div class="form-group" title="${t('clone_to.cloned_note_prefix_title')}">
<label style="width: 100%">
Prefix (optional)
${t('clone_to.prefix_optional')}
<input class="clone-prefix form-control" style="width: 100%;">
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Clone to selected note <kbd>enter</kbd></button>
<button type="submit" class="btn btn-primary">${t('clone_to.clone_to_selected_note')}</button>
</div>
</form>
</div>
@ -73,7 +74,7 @@ export default class CloneToDialog extends BasicWidget {
this.cloneNotesTo(notePath);
}
else {
logError("No path to clone to.");
logError(t('clone_to.no_path_to_clone_to'));
}
return false;
@ -119,7 +120,7 @@ export default class CloneToDialog extends BasicWidget {
const clonedNote = await froca.getNote(cloneNoteId);
const targetNote = await froca.getBranch(targetBranchId).getNote();
toastService.showMessage(`Note "${clonedNote.title}" has been cloned into ${targetNote.title}`);
toastService.showMessage(t('clone_to.note_cloned', {clonedTitle: clonedNote.title, targetTitle: targetNote.title}));
}
}
}

View File

@ -1,4 +1,5 @@
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
const DELETE_NOTE_BUTTON_CLASS = "confirm-dialog-delete-note";
@ -7,7 +8,7 @@ const TPL = `
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Confirmation</h5>
<h5 class="modal-title mr-auto">${t('confirm.confirmation')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
@ -19,11 +20,11 @@ const TPL = `
<div class="confirm-dialog-custom"></div>
</div>
<div class="modal-footer">
<button class="confirm-dialog-cancel-button btn btn-sm">Cancel</button>
<button class="confirm-dialog-cancel-button btn btn-sm">${t('confirm.cancel')}</button>
&nbsp;
<button class="confirm-dialog-ok-button btn btn-primary btn-sm">OK</button>
<button class="confirm-dialog-ok-button btn btn-primary btn-sm">${t('confirm.ok')}</button>
</div>
</div>
</div>
@ -82,7 +83,7 @@ export default class ConfirmDialog extends BasicWidget {
showConfirmDeleteNoteBoxWithNoteDialogEvent({title, callback}) {
glob.activeDialog = this.$widget;
this.$confirmContent.text(`Are you sure you want to remove the note "${title}" from relation map?`);
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', {title: title})}`);
this.$custom.empty()
.append("<br/>")
@ -92,13 +93,13 @@ export default class ConfirmDialog extends BasicWidget {
$("<label>")
.addClass("form-check-label")
.attr("style", "text-decoration: underline dotted var(--main-text-color)")
.attr("title", "If you don't check this, the note will be only removed from the relation map.")
.attr("title", `${t('confirm.if_you_dont_check')}`)
.append(
$("<input>")
.attr("type", "checkbox")
.addClass(`form-check-input ${DELETE_NOTE_BUTTON_CLASS}`)
)
.append("Also delete the note")
.append(`${t('confirm.also_delete_note')}`)
));
this.$custom.show();

View File

@ -3,13 +3,14 @@ import froca from "../../services/froca.js";
import linkService from "../../services/link.js";
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="delete-notes-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mr-auto">Delete notes preview</h4>
<h4 class="modal-title mr-auto">${t('delete_notes.delete_notes_preview')}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
@ -20,42 +21,42 @@ const TPL = `
<label>
<input class="delete-all-clones" value="1" type="checkbox">
delete also all clones (can be undone in recent changes)
${t('delete_notes.delete_all_clones_description')}
</label>
</div>
<div class="checkbox">
<label title="Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediately and it won't be possible to undelete the notes.">
<label title="${t('delete_notes.erase_notes_description')}">
<input class="erase-notes" value="1" type="checkbox">
erase notes permanently (can't be undone), including all clones. This will force application reload.
${t('delete_notes.erase_notes_warning')}
</label>
</div>
<div class="delete-notes-list-wrapper">
<h4>Following notes will be deleted (<span class="deleted-notes-count"></span>)</h4>
<h4>${t('delete_notes.notes_to_be_deleted')} (<span class="deleted-notes-count"></span>)</h4>
<ul class="delete-notes-list" style="max-height: 200px; overflow: auto;"></ul>
</div>
<div class="no-note-to-delete-wrapper alert alert-info">
No note will be deleted (only clones).
${t('delete_notes.no_note_to_delete')}
</div>
<div class="broken-relations-wrapper">
<div class="alert alert-danger">
<h4>Following relations will be broken and deleted (<span class="broke-relations-count"></span>)</h4>
<h4>${t('delete_notes.broken_relations_to_be_deleted')} (<span class="broke-relations-count"></span>)</h4>
<ul class="broken-relations-list" style="max-height: 200px; overflow: auto;"></ul>
</div>
</div>
</div>
<div class="modal-footer">
<button class="delete-notes-dialog-cancel-button btn btn-sm">Cancel</button>
<button class="delete-notes-dialog-cancel-button btn btn-sm">${t('delete_notes.cancel')}</button>
&nbsp;
<button class="delete-notes-dialog-ok-button btn btn-primary btn-sm">OK</button>
<button class="delete-notes-dialog-ok-button btn btn-primary btn-sm">${t('delete_notes.ok')}</button>
</div>
</div>
</div>
@ -135,9 +136,9 @@ export default class DeleteNotesDialog extends BasicWidget {
for (const attr of response.brokenRelations) {
this.$brokenRelationsList.append(
$("<li>")
.append(`Note `)
.append(`${t('delete_notes.note')} `)
.append(await linkService.createLink(attr.value))
.append(` (to be deleted) is referenced by relation <code>${attr.name}</code> originating from `)
.append(` ${t('delete_notes.to_be_deleted', {attrName: attr.name})} `)
.append(await linkService.createLink(attr.noteId))
);
}

View File

@ -5,6 +5,7 @@ import toastService from "../../services/toast.js";
import froca from "../../services/froca.js";
import openService from "../../services/open.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="export-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@ -32,8 +33,8 @@ const TPL = `
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Export note "<span class="export-note-title"></span>"</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<h5 class="modal-title">${t('export.export_note_title')} <span class="export-note-title"></span></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('export.close')}">
<span aria-hidden="true">&times;</span>
</button>
</div>
@ -42,7 +43,7 @@ const TPL = `
<div class="form-check">
<label class="form-check-label">
<input class="export-type-subtree form-check-input" type="radio" name="export-type" value="subtree">
this note and all of its descendants
${t('export.export_type_subtree')}
</label>
</div>
@ -50,21 +51,21 @@ const TPL = `
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="export-subtree-format" value="html">
HTML in ZIP archive - this is recommended since this preserves all the formatting.
${t('export.format_html')}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="export-subtree-format" value="markdown">
Markdown - this preserves most of the formatting.
${t('export.format_markdown')}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="export-subtree-format" value="opml">
OPML - outliner interchange format for text only. Formatting, images and files are not included.
${t('export.format_opml')}
</label>
</div>
@ -72,14 +73,14 @@ const TPL = `
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="opml-version" value="1.0">
OPML v1.0 - plain text only
${t('export.opml_version_1')}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="opml-version" value="2.0">
OMPL v2.0 - allows also HTML
${t('export.opml_version_2')}
</label>
</div>
</div>
@ -88,7 +89,7 @@ const TPL = `
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="export-type" value="single">
only this note without its descendants
${t('export.export_type_single')}
</label>
</div>
@ -96,20 +97,20 @@ const TPL = `
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="export-single-format" value="html">
HTML - this is recommended since this preserves all the formatting.
${t('export.format_html')}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="export-single-format" value="markdown">
Markdown - this preserves most of the formatting.
<input class="form-check-input" type="radio" name="export-single-format" value="markdown">
${t('export.format_markdown')}
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="export-button btn btn-primary">Export</button>
<button class="export-button btn btn-primary">${t('export.export')}</button>
</div>
</form>
</div>
@ -141,8 +142,7 @@ export default class ExportDialog extends BasicWidget {
const exportType = this.$widget.find("input[name='export-type']:checked").val();
if (!exportType) {
// this shouldn't happen as we always choose a default export type
toastService.showError("Choose export type first please");
toastService.showError(t('export.choose_export_type'));
return;
}
@ -189,14 +189,12 @@ export default class ExportDialog extends BasicWidget {
}
async showExportDialogEvent({notePath, defaultType}) {
// each opening of the dialog resets the taskId, so we don't associate it with previous exports anymore
this.taskId = '';
this.$exportButton.removeAttr("disabled");
if (defaultType === 'subtree') {
this.$subtreeType.prop("checked", true).trigger('change');
// to show/hide OPML versions
this.$widget.find("input[name=export-subtree-format]:checked").trigger('change');
}
else if (defaultType === 'single') {
@ -228,7 +226,7 @@ export default class ExportDialog extends BasicWidget {
ws.subscribeToMessages(async message => {
const makeToast = (id, message) => ({
id: id,
title: "Export status",
title: t('export.export_status'),
message: message,
icon: "arrow-square-up-right"
});
@ -242,10 +240,10 @@ ws.subscribeToMessages(async message => {
toastService.showError(message.message);
}
else if (message.type === 'taskProgressCount') {
toastService.showPersistent(makeToast(message.taskId, `Export in progress: ${message.progressCount}`));
toastService.showPersistent(makeToast(message.taskId, t('export.export_in_progress', { progressCount: message.progressCount })));
}
else if (message.type === 'taskSucceeded') {
const toast = makeToast(message.taskId, "Export finished successfully.");
const toast = makeToast(message.taskId, t('export.export_finished_successfully'));
toast.closeAfter = 5000;
toastService.showPersistent(toast);

View File

@ -1,14 +1,15 @@
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="help-dialog modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document" style="min-width: 100%; height: 100%; margin: 0;">
<div class="modal-content" style="height: auto;">
<div class="modal-header">
<h5 class="modal-title mr-auto">Help (full documentation is available <a class="external" href="https://triliumnext.github.io/Docs/">online</a>)</h5>
<h5 class="modal-title mr-auto">${t('help.fullDocumentation')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<button type="button" class="close" data-dismiss="modal" aria-label="${t('help.close')}">
<span aria-hidden="true">&times;</span>
</button>
</div>
@ -16,18 +17,18 @@ const TPL = `
<div class="card-columns help-cards">
<div class="card">
<div class="card-body">
<h5 class="card-title">Note navigation</h5>
<h5 class="card-title">${t('help.noteNavigation')}</h5>
<p class="card-text">
<ul>
<li><kbd>UP</kbd>, <kbd>DOWN</kbd> - go up/down in the list of notes</li>
<li><kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - collapse/expand node</li>
<li><kbd data-command="backInNoteHistory">not set</kbd>, <kbd data-command="forwardInNoteHistory">not set</kbd> - go back / forwards in the history</li>
<li><kbd data-command="jumpToNote">not set</kbd> - show <a class="external" href="https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note">"Jump to" dialog</a></li>
<li><kbd data-command="scrollToActiveNote">not set</kbd> - scroll to active note</li>
<li><kbd>Backspace</kbd> - jump to parent note</li>
<li><kbd data-command="collapseTree">not set</kbd> - collapse whole note tree</li>
<li><kbd data-command="collapseSubtree">not set</kbd> - collapse sub-tree</li>
<li><kbd>UP</kbd>, <kbd>DOWN</kbd> - ${t('help.goUpDown')}</li>
<li><kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - ${t('help.collapseExpand')}</li>
<li><kbd data-command="backInNoteHistory">${t('help.notSet')}</kbd>, <kbd data-command="forwardInNoteHistory">${t('help.notSet')}</kbd> - ${t('help.goBackForwards')}</li>
<li><kbd data-command="jumpToNote">${t('help.notSet')}</kbd> - ${t('help.showJumpToNoteDialog')}</li>
<li><kbd data-command="scrollToActiveNote">${t('help.notSet')}</kbd> - ${t('help.scrollToActiveNote')}</li>
<li><kbd>Backspace</kbd> - ${t('help.jumpToParentNote')}</li>
<li><kbd data-command="collapseTree">${t('help.notSet')}</kbd> - ${t('help.collapseWholeTree')}</li>
<li><kbd data-command="collapseSubtree">${t('help.notSet')}</kbd> - ${t('help.collapseSubTree')}</li>
</ul>
</p>
</div>
@ -35,19 +36,19 @@ const TPL = `
<div class="card">
<div class="card-body">
<h5 class="card-title">Tab shortcuts</h5>
<h5 class="card-title">${t('help.tabShortcuts')}</h5>
<p class="card-text">
<ul>
<li><kbd>CTRL+click</kbd> (or middle mouse click) on note link opens note in a new tab</li>
<li><kbd>CTRL+click</kbd> ${t('help.newTabNoteLink')}</li>
</ul>
Only in desktop (electron build):
${t('help.onlyInDesktop')}:
<ul>
<li><kbd data-command="openNewTab">not set</kbd> open empty tab</li>
<li><kbd data-command="closeActiveTab">not set</kbd> close active tab</li>
<li><kbd data-command="activateNextTab">not set</kbd> activate next tab</li>
<li><kbd data-command="activatePreviousTab">not set</kbd> activate previous tab</li>
<li><kbd data-command="openNewTab">${t('help.notSet')}</kbd> ${t('help.openEmptyTab')}</li>
<li><kbd data-command="closeActiveTab">${t('help.notSet')}</kbd> ${t('help.closeActiveTab')}</li>
<li><kbd data-command="activateNextTab">${t('help.notSet')}</kbd> ${t('help.activateNextTab')}</li>
<li><kbd data-command="activatePreviousTab">${t('help.notSet')}</kbd> ${t('help.activatePreviousTab')}</li>
</ul>
</p>
</div>
@ -55,13 +56,13 @@ const TPL = `
<div class="card">
<div class="card-body">
<h5 class="card-title">Creating notes</h5>
<h5 class="card-title">${t('help.creatingNotes')}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="createNoteAfter">not set</kbd> - create new note after the active note</li>
<li><kbd data-command="createNoteInto">not set</kbd> - create new sub-note into active note</li>
<li><kbd data-command="editBranchPrefix">not set</kbd> - edit <a class="external" href="https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix">prefix</a> of active note clone</li>
<li><kbd data-command="createNoteAfter">${t('help.notSet')}</kbd> - ${t('help.createNoteAfter')}</li>
<li><kbd data-command="createNoteInto">${t('help.notSet')}</kbd> - ${t('help.createNoteInto')}</li>
<li><kbd data-command="editBranchPrefix">${t('help.notSet')}</kbd> - ${t('help.editBranchPrefix')}</li>
</ul>
</p>
</div>
@ -69,19 +70,19 @@ const TPL = `
<div class="card">
<div class="card-body">
<h5 class="card-title">Moving / cloning notes</h5>
<h5 class="card-title">${t('help.movingCloningNotes')}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="moveNoteUp">not set</kbd>, <kbd data-command="moveNoteDown">not set</kbd> - move note up/down in the note list</li>
<li><kbd data-command="moveNoteUpInHierarchy">not set</kbd>, <kbd data-command="moveNoteDownInHierarchy">not set</kbd> - move note up in the hierarchy</li>
<li><kbd data-command="addNoteAboveToSelection">not set</kbd>, <kbd data-command="addNoteBelowToSelection">not set</kbd> - multi-select note above/below</li>
<li><kbd data-command="selectAllNotesInParent">not set</kbd> - select all notes in the current level</li>
<li><kbd>Shift+click</kbd> - select note</li>
<li><kbd data-command="copyNotesToClipboard">not set</kbd> - copy active note (or current selection) into clipboard (used for <a class="external" href="https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes">cloning</a>)</li>
<li><kbd data-command="cutNotesToClipboard">not set</kbd> - cut current (or current selection) note into clipboard (used for moving notes)</li>
<li><kbd data-command="pasteNotesFromClipboard">not set</kbd> - paste note(s) as sub-note into active note (which is either move or clone depending on whether it was copied or cut into clipboard)</li>
<li><kbd data-command="deleteNotes">not set</kbd> - delete note / sub-tree</li>
<li><kbd data-command="moveNoteUp">${t('help.notSet')}</kbd>, <kbd data-command="moveNoteDown">${t('help.notSet')}</kbd> - ${t('help.moveNoteUpDown')}</li>
<li><kbd data-command="moveNoteUpInHierarchy">${t('help.notSet')}</kbd>, <kbd data-command="moveNoteDownInHierarchy">${t('help.notSet')}</kbd> - ${t('help.moveNoteUpHierarchy')}</li>
<li><kbd data-command="addNoteAboveToSelection">${t('help.notSet')}</kbd>, <kbd data-command="addNoteBelowToSelection">${t('help.notSet')}</kbd> - ${t('help.multiSelectNote')}</li>
<li><kbd data-command="selectAllNotesInParent">${t('help.notSet')}</kbd> - ${t('help.selectAllNotes')}</li>
<li><kbd>Shift+click</kbd> - ${t('help.selectNote')}</li>
<li><kbd data-command="copyNotesToClipboard">${t('help.notSet')}</kbd> - ${t('help.copyNotes')}</li>
<li><kbd data-command="cutNotesToClipboard">${t('help.notSet')}</kbd> - ${t('help.cutNotes')}</li>
<li><kbd data-command="pasteNotesFromClipboard">${t('help.notSet')}</kbd> - ${t('help.pasteNotes')}</li>
<li><kbd data-command="deleteNotes">${t('help.notSet')}</kbd> - ${t('help.deleteNotes')}</li>
</ul>
</p>
</div>
@ -89,17 +90,16 @@ const TPL = `
<div class="card">
<div class="card-body">
<h5 class="card-title">Editing notes</h5>
<h5 class="card-title">${t('help.editingNotes')}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="editNoteTitle">not set</kbd> in tree pane will switch from tree pane into note title. Enter from note title will switch focus to text editor.
<kbd data-command="scrollToActiveNote">not set</kbd> will switch back from editor to tree pane.</li>
<li><kbd>Ctrl+K</kbd> - create / edit external link</li>
<li><kbd data-command="addLinkToText">not set</kbd> - create internal link</li>
<li><kbd data-command="followLinkUnderCursor">not set</kbd> - follow link under cursor</li>
<li><kbd data-command="insertDateTimeToText">not set</kbd> - insert current date and time at caret position</li>
<li><kbd data-command="scrollToActiveNote">not set</kbd> - jump away to the tree pane and scroll to active note</li>
<li><kbd data-command="editNoteTitle">${t('help.notSet')}</kbd> ${t('help.editNoteTitle')}</li>
<li><kbd>Ctrl+K</kbd> - ${t('help.createEditLink')}</li>
<li><kbd data-command="addLinkToText">${t('help.notSet')}</kbd> - ${t('help.createInternalLink')}</li>
<li><kbd data-command="followLinkUnderCursor">${t('help.notSet')}</kbd> - ${t('help.followLink')}</li>
<li><kbd data-command="insertDateTimeToText">${t('help.notSet')}</kbd> - ${t('help.insertDateTime')}</li>
<li><kbd data-command="scrollToActiveNote">${t('help.notSet')}</kbd> - ${t('help.jumpToTreePane')}</li>
</ul>
</p>
</div>
@ -107,14 +107,14 @@ const TPL = `
<div class="card">
<div class="card-body">
<h5 class="card-title"><a class="external" href="https://triliumnext.github.io/Docs/Wiki/text-notes.html#markdown--autoformat">Markdown-like autoformatting</a></h5>
<h5 class="card-title"><a class="external" href="https://triliumnext.github.io/Docs/Wiki/text-notes.html#markdown--autoformat">${t('help.markdownAutoformat')}</a></h5>
<p class="card-text">
<ul>
<li><kbd>##</kbd>, <kbd>###</kbd>, <kbd>####</kbd> etc. followed by space for headings</li>
<li><kbd>*</kbd> or <kbd>-</kbd> followed by space for bullet list</li>
<li><kbd>1.</kbd> or <kbd>1)</kbd> followed by space for numbered list</li>
<li>start a line with <kbd>&gt;</kbd> followed by space for block quote</li>
<li><kbd>##</kbd>, <kbd>###</kbd>, <kbd>####</kbd> ${t('help.headings')}</li>
<li>${t('help.bulletList')}</li>
<li>${t('help.numberedList')}</li>
<li>${t('help.blockQuote')}</li>
</ul>
</p>
</div>
@ -122,13 +122,13 @@ const TPL = `
<div class="card">
<div class="card-body">
<h5 class="card-title">Troubleshooting</h5>
<h5 class="card-title">${t('help.troubleshooting')}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="reloadFrontendApp">not set</kbd> - reload Trilium frontend</li>
<li><kbd data-command="openDevTools">not set</kbd> - show developer tools</li>
<li><kbd data-command="showSQLConsole">not set</kbd> - show SQL console</li>
<li><kbd data-command="reloadFrontendApp">${t('help.notSet')}</kbd> - ${t('help.reloadFrontend')}</li>
<li><kbd data-command="openDevTools">${t('help.notSet')}</kbd> - ${t('help.showDevTools')}</li>
<li><kbd data-command="showSQLConsole">${t('help.notSet')}</kbd> - ${t('help.showSQLConsole')}</li>
</ul>
</p>
</div>
@ -136,12 +136,12 @@ const TPL = `
<div class="card">
<div class="card-body">
<h5 class="card-title">Other</h5>
<h5 class="card-title">${t('help.other')}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="quickSearch">not set</kbd> - focus on quick search input</li>
<li><kbd data-command="findInText">not set</kbd> - in page search</li>
<li><kbd data-command="quickSearch">${t('help.notSet')}</kbd> - ${t('help.quickSearch')}</li>
<li><kbd data-command="findInText">${t('help.notSet')}</kbd> - ${t('help.inPageSearch')}</li>
</ul>
</p>
</div>

View File

@ -3,75 +3,74 @@ import treeService from "../../services/tree.js";
import importService from "../../services/import.js";
import options from "../../services/options.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Import into note</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<h5 class="modal-title">${t('import.importIntoNote')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('import.close')}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form class="import-form">
<div class="modal-body">
<div class="form-group">
<label for="import-file-upload-input"><strong>Choose import file</strong></label>
<label for="import-file-upload-input"><strong>${t('import.chooseImportFile')}</strong></label>
<input type="file" class="import-file-upload-input form-control-file" multiple />
<p>Content of the selected file(s) will be imported as child note(s) into <strong class="import-note-title"></strong>.
<p>${t('import.importDescription')} <strong class="import-note-title"></strong>.
</div>
<div class="form-group">
<strong>Options:</strong>
<strong>${t('import.options')}:</strong>
<div class="checkbox">
<label data-toggle="tooltip" title="Trilium <code>.zip</code> export files can contain executable scripts which may contain harmful behavior. Safe import will deactivate automatic execution of all imported scripts. Uncheck &quot;Safe import&quot; only if the imported tar archive is supposed to contain executable scripts and you completely trust the contents of the import file.">
<label data-toggle="tooltip" title="${t('import.safeImportTooltip')}">
<input class="safe-import-checkbox" value="1" type="checkbox" checked>
<span>Safe import</span>
<span>${t('import.safeImport')}</span>
</label>
</div>
<div class="checkbox">
<label data-toggle="tooltip" title="If this is checked then Trilium will read <code>.zip</code>, <code>.enex</code> and <code>.opml</code> files and create notes from files insides those archives. If unchecked, then Trilium will attach the archives themselves to the note.">
<label data-toggle="tooltip" title="${t('import.explodeArchivesTooltip')}">
<input class="explode-archives-checkbox" value="1" type="checkbox" checked>
<span>Read contents of <code>.zip</code>, <code>.enex</code> and <code>.opml</code> archives.</span>
<span>${t('import.explodeArchives')}</span>
</label>
</div>
<div class="checkbox">
<label data-toggle="tooltip" title="<p>If you check this option, Trilium will attempt to shrink the imported images by scaling and optimization which may affect the perceived image quality. If unchecked, images will be imported without changes.</p><p>This doesn't apply to <code>.zip</code> imports with metadata since it is assumed these files are already optimized.</p>">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>Shrink images</span>
<label data-toggle="tooltip" title="${t('import.shrinkImagesTooltip')}">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t('import.shrinkImages')}</span>
</label>
</div>
<div class="checkbox">
<label>
<input class="text-imported-as-text-checkbox" value="1" type="checkbox" checked>
Import HTML, Markdown and TXT as text notes if it's unclear from metadata
${t('import.textImportedAsText')}
</label>
</div>
<div class="checkbox">
<label>
<input class="code-imported-as-code-checkbox" value="1" type="checkbox" checked> Import recognized code files (e.g. <code>.json</code>) as code notes if it's unclear from metadata
<input class="code-imported-as-code-checkbox" value="1" type="checkbox" checked> ${t('import.codeImportedAsCode')}
</label>
</div>
<div class="checkbox">
<label>
<input class="replace-underscores-with-spaces-checkbox" value="1" type="checkbox" checked>
Replace underscores with spaces in imported note names
${t('import.replaceUnderscoresWithSpaces')}
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="import-button btn btn-primary">Import</button>
<button class="import-button btn btn-primary">${t('import.import')}</button>
</div>
</form>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import treeService from '../../services/tree.js';
import noteAutocompleteService from '../../services/note_autocomplete.js';
import utils from "../../services/utils.js";
@ -9,7 +10,7 @@ const TPL = `
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Include note</h5>
<h5 class="modal-title">${t('include_note.dialog_title')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@ -17,35 +18,35 @@ const TPL = `
<form class="include-note-form">
<div class="modal-body">
<div class="form-group">
<label for="include-note-autocomplete">Note</label>
<label for="include-note-autocomplete">${t('include_note.label_note')}</label>
<div class="input-group">
<input class="include-note-autocomplete form-control" placeholder="search for note by its name">
<input class="include-note-autocomplete form-control" placeholder="${t('include_note.placeholder_search')}">
</div>
</div>
Box size of the included note:
${t('include_note.box_size_prompt')}
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
small (~ 10 lines)
${t('include_note.box_size_small')}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
medium (~ 30 lines)
${t('include_note.box_size_medium')}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
full (box shows complete text)
${t('include_note.box_size_full')}
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Include note <kbd>enter</kbd></button>
<button type="submit" class="btn btn-primary">${t('include_note.button_include')} <kbd>enter</kbd></button>
</div>
</form>
</div>
@ -62,10 +63,8 @@ export default class IncludeNoteDialog extends BasicWidget {
if (notePath) {
this.$widget.modal('hide');
this.includeNote(notePath);
}
else {
} else {
logError("No noteId to include.");
}
@ -75,15 +74,12 @@ export default class IncludeNoteDialog extends BasicWidget {
async showIncludeNoteDialogEvent({textTypeWidget}) {
this.textTypeWidget = textTypeWidget;
await this.refresh();
utils.openDialog(this.$widget);
}
async refresh(widget) {
this.$autoComplete.val('');
noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, {
hideGoToSelectedNoteButton: true,
allowCreatingNotes: true
@ -94,15 +90,13 @@ export default class IncludeNoteDialog extends BasicWidget {
async includeNote(notePath) {
const noteId = treeService.getNoteIdFromUrl(notePath);
const note = await froca.getNote(noteId);
const boxSize = $("input[name='include-note-box-size']:checked").val();
if (['image', 'canvas', 'mermaid'].includes(note.type)) {
// there's no benefit to use insert note functionlity for images,
// so we'll just add an IMG tag
this.textTypeWidget.addImage(noteId);
}
else {
} else {
this.textTypeWidget.addIncludeNote(noteId, boxSize);
}
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
@ -6,9 +7,9 @@ const TPL = `
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Info message</h5>
<h5 class="modal-title mr-auto">${t("info.modalTitle")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<button type="button" class="close" data-dismiss="modal" aria-label="${t("info.closeButton")}">
<span aria-hidden="true">&times;</span>
</button>
</div>
@ -16,7 +17,7 @@ const TPL = `
<div class="info-dialog-content"></div>
</div>
<div class="modal-footer">
<button class="info-dialog-ok-button btn btn-primary btn-sm">OK</button>
<button class="info-dialog-ok-button btn btn-primary btn-sm">${t("info.okButton")}</button>
</div>
</div>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import noteAutocompleteService from '../../services/note_autocomplete.js';
import utils from "../../services/utils.js";
import appContext from "../../components/app_context.js";
@ -9,7 +10,7 @@ const TPL = `<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="
<div class="modal-content">
<div class="modal-header">
<div class="input-group">
<input class="jump-to-note-autocomplete form-control" placeholder="search for note by its name">
<input class="jump-to-note-autocomplete form-control" placeholder="${t('jump_to_note.search_placeholder')}">
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
@ -20,7 +21,7 @@ const TPL = `<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="
<div class="algolia-autocomplete-container jump-to-note-results"></div>
</div>
<div class="modal-footer">
<button class="show-in-full-text-button btn btn-sm">Search in full text <kbd>Ctrl+Enter</kbd></button>
<button class="show-in-full-text-button btn btn-sm">${t('jump_to_note.search_button')}</button>
</div>
</div>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import toastService from "../../services/toast.js";
import utils from "../../services/utils.js";
import appContext from "../../components/app_context.js";
@ -10,18 +11,18 @@ const TPL = `
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Markdown import</h5>
<h5 class="modal-title">${t("markdown_import.dialog_title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Because of browser sandbox it's not possible to directly read clipboard from JavaScript. Please paste the Markdown to import to textarea below and click on Import button</p>
<p>${t("markdown_import.modal_body_text")}</p>
<textarea class="markdown-import-textarea" style="height: 340px; width: 100%"></textarea>
</div>
<div class="modal-footer">
<button class="markdown-import-button btn btn-primary">Import <kbd>Ctrl+Enter</kbd></button>
<button class="markdown-import-button btn btn-primary">${t("markdown_import.import_button")}</button>
</div>
</div>
</div>
@ -56,7 +57,7 @@ export default class MarkdownImportDialog extends BasicWidget {
textEditor.model.insertContent(modelFragment, textEditor.model.document.selection);
toastService.showMessage("Markdown content has been imported into the document.");
toastService.showMessage(t("markdown_import.import_success"));
}
async pasteMarkdownIntoTextEvent() {

View File

@ -5,13 +5,14 @@ import froca from "../../services/froca.js";
import branchService from "../../services/branches.js";
import treeService from "../../services/tree.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js"; // Added import
const TPL = `
<div class="move-to-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Move notes to ...</h5>
<h5 class="modal-title mr-auto">${t("move_to.dialog_title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
@ -19,21 +20,21 @@ const TPL = `
</div>
<form class="move-to-form">
<div class="modal-body">
<h5>Notes to move</h5>
<h5>${t("move_to.notes_to_move")}</h5>
<ul class="move-to-note-list" style="max-height: 200px; overflow: auto;"></ul>
<div class="form-group">
<label style="width: 100%">
Target parent note
${t("move_to.target_parent_note")}
<div class="input-group">
<input class="move-to-note-autocomplete form-control" placeholder="search for note by its name">
<input class="move-to-note-autocomplete form-control" placeholder="${t("move_to.search_placeholder")}">
</div>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Move to selected note <kbd>enter</kbd></button>
<button type="submit" class="btn btn-primary">${t("move_to.move_button")}</button>
</div>
</form>
</div>
@ -63,7 +64,7 @@ export default class MoveToDialog extends BasicWidget {
froca.getBranchId(parentNoteId, noteId).then(branchId => this.moveNotesTo(branchId));
}
else {
logError("No path to move to.");
logError(t("move_to.error_no_path"));
}
return false;
@ -96,6 +97,6 @@ export default class MoveToDialog extends BasicWidget {
const parentBranch = froca.getBranch(parentBranchId);
const parentNote = await parentBranch.getNote();
toastService.showMessage(`Selected notes have been moved into ${parentNote.title}`);
toastService.showMessage(`${t("move_to.move_success_message")} ${parentNote.title}`);
}
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import noteTypesService from "../../services/note_types.js";
import BasicWidget from "../basic_widget.js";
@ -22,17 +23,17 @@ const TPL = `
<div class="modal-dialog" style="max-width: 500px;" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Choose note type</h5>
<h5 class="modal-title mr-auto">${t("note_type_chooser.modal_title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Choose note type / template of the new note:
${t("note_type_chooser.modal_body")}
<div class="dropdown">
<button class="note-type-dropdown-trigger" type="button" style="display: none;" data-toggle="dropdown">Dropdown trigger</button>
<button class="note-type-dropdown-trigger" type="button" style="display: none;" data-toggle="dropdown">${t("note_type_chooser.dropdown_trigger")}</button>
<div class="note-type-dropdown dropdown-menu"></div>
</div>
@ -102,7 +103,7 @@ export default class NoteTypeChooserDialog extends BasicWidget {
for (const noteType of noteTypes) {
if (noteType.title === '----') {
this.$noteTypeDropdown.append($('<h6 class="dropdown-header">').append("Templates:"));
this.$noteTypeDropdown.append($('<h6 class="dropdown-header">').append(t("note_type_chooser.templates")));
}
else {
this.$noteTypeDropdown.append(

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
@ -6,17 +7,16 @@ const TPL = `
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Password is not set</h5>
<h5 class="modal-title mr-auto">${t("password_not_set.title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Protected notes are encrypted using a user password, but password has not been set yet.
To be able to protect notes, <a class="open-password-options-button" href="javascript:">
click here to open the Options dialog</a> and set your password.
${t("password_not_set.body1")}
${t("password_not_set.body2")}
</div>
</div>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
@ -7,8 +8,7 @@ const TPL = `
<div class="modal-content">
<form class="prompt-dialog-form">
<div class="modal-header">
<h5 class="prompt-title modal-title mr-auto">Prompt</h5>
<h5 class="prompt-title modal-title mr-auto">${t("prompt.title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@ -16,7 +16,7 @@ const TPL = `
<div class="modal-body">
</div>
<div class="modal-footer">
<button class="prompt-dialog-ok-button btn btn-primary btn-sm">OK <kbd>enter</kbd></button>
<button class="prompt-dialog-ok-button btn btn-primary btn-sm">${t("prompt.ok")}</button>
</div>
</form>
</div>
@ -69,7 +69,7 @@ export default class PromptDialog extends BasicWidget {
this.shownCb = shown;
this.resolve = callback;
this.$widget.find(".prompt-title").text(title || "Prompt");
this.$widget.find(".prompt-title").text(title || t("prompt.defaultTitle"));
this.$question = $("<label>")
.prop("for", "prompt-dialog-answer")

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import protectedSessionService from "../../services/protected_session.js";
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
@ -7,11 +8,11 @@ const TPL = `
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Protected session</h5>
<h5 class="modal-title mr-auto">${t("protected_session_password.modal_title")}</h5>
<button class="help-button" type="button" data-help-page="protected-notes.html" title="Help on Protected notes">?</button>
<button class="help-button" type="button" data-help-page="protected-notes.html" title="${t("protected_session_password.help_title")}">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<button type="button" class="close" data-dismiss="modal" aria-label="${t("protected_session_password.close_label")}" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
</div>
@ -19,13 +20,13 @@ const TPL = `
<div class="modal-body">
<div class="form-group">
<label>
To proceed with requested action you need to start protected session by entering password:
${t("protected_session_password.form_label")}
<input class="form-control protected-session-password" type="password">
</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary">Start protected session <kbd>enter</kbd></button>
<button class="btn btn-primary">${t("protected_session_password.start_button")}</button>
</div>
</form>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import linkService from '../../services/link.js';
import utils from '../../services/utils.js';
import server from '../../services/server.js';
@ -14,10 +15,10 @@ const TPL = `
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Recent changes</h5>
<h5 class="modal-title mr-auto">${t('recent_changes.title')}</h5>
<button class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px">
Erase deleted notes now</button>
${t('recent_changes.erase_notes_button')}</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
@ -39,7 +40,7 @@ export default class RecentChangesDialog extends BasicWidget {
server.post('notes/erase-deleted-notes-now').then(() => {
this.refresh();
toastService.showMessage("Deleted notes have been erased.");
toastService.showMessage(t('recent_changes.deleted_notes_message'));
});
});
}
@ -65,7 +66,7 @@ export default class RecentChangesDialog extends BasicWidget {
this.$content.empty();
if (recentChangesRows.length === 0) {
this.$content.append("No changes yet ...");
this.$content.append(t('recent_changes.no_changes_message'));
}
const groupedByDate = this.groupByDate(recentChangesRows);
@ -85,9 +86,9 @@ export default class RecentChangesDialog extends BasicWidget {
if (change.canBeUndeleted) {
const $undeleteLink = $(`<a href="javascript:">`)
.text("undelete")
.text(t('recent_changes.undelete_link'))
.on('click', async () => {
const text = 'Do you want to undelete this note and its sub-notes?';
const text = t('recent_changes.confirm_undelete');
if (await dialogService.confirm(text)) {
await server.put(`notes/${change.noteId}/undelete`);

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import utils from '../../services/utils.js';
import server from '../../services/server.js';
import toastService from "../../services/toast.js";
@ -39,13 +40,13 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Note revisions</h5>
<h5 class="modal-title mr-auto">${t("revisions.note_revisions")}</h5>
<button class="revisions-erase-all-revisions-button btn btn-sm"
title="Delete all revisions of this note"
style="padding: 0 10px 0 10px;" type="button">Delete all revisions</button>
title="${t("revisions.delete_all_revisions")}"
style="padding: 0 10px 0 10px;" type="button">${t("revisions.delete_all_button")}</button>
<button class="help-button" type="button" data-help-page="note-revisions.html" title="Help on Note revisions">?</button>
<button class="help-button" type="button" data-help-page="note-revisions.html" title="${t("revisions.help_title")}">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
@ -53,7 +54,7 @@ const TPL = `
</div>
<div class="modal-body" style="display: flex; height: 80vh;">
<div class="dropdown">
<button class="revision-list-dropdown" type="button" style="display: none;" data-toggle="dropdown">Dropdown trigger</button>
<button class="revision-list-dropdown" type="button" style="display: none;" data-toggle="dropdown">${t("revisions.dropdown_trigger")}</button>
<div class="revision-list dropdown-menu" style="position: static; height: 100%; overflow: auto;"></div>
</div>
@ -105,14 +106,14 @@ export default class RevisionsDialog extends BasicWidget {
});
this.$eraseAllRevisionsButton.on('click', async () => {
const text = 'Do you want to delete all revisions of this note? This action will erase revision title and content, but still preserve revision metadata.';
const text = t("revisions.confirm_delete_all");
if (await dialogService.confirm(text)) {
await server.remove(`notes/${this.note.noteId}/revisions`);
this.$widget.modal('hide');
toastService.showMessage('Note revisions has been deleted.');
toastService.showMessage(t("revisions.revisions_deleted"));
}
});
@ -149,7 +150,7 @@ export default class RevisionsDialog extends BasicWidget {
$('<a class="dropdown-item" tabindex="0">')
.text(`${item.dateLastEdited.substr(0, 16)} (${utils.formatSize(item.contentLength)})`)
.attr('data-revision-id', item.revisionId)
.attr('title', `This revision was last edited on ${item.dateLastEdited}`)
.attr('title', t("revisions.revision_last_edited", { date: item.dateLastEdited }))
);
}
@ -160,7 +161,7 @@ export default class RevisionsDialog extends BasicWidget {
this.revisionId = this.revisionItems[0].revisionId;
}
} else {
this.$title.text("No revisions for this note yet...");
this.$title.text(t("revisions.no_revisions"));
this.revisionId = null;
}
@ -182,31 +183,31 @@ export default class RevisionsDialog extends BasicWidget {
renderContentButtons(revisionItem) {
this.$titleButtons.empty();
const $restoreRevisionButton = $('<button class="btn btn-sm" type="button">Restore this revision</button>');
const $restoreRevisionButton = $(`<button class="btn btn-sm" type="button">${t("revisions.restore_button")}</button>`);
$restoreRevisionButton.on('click', async () => {
const text = 'Do you want to restore this revision? This will overwrite current title/content of the note with this revision.';
const text = t("revisions.confirm_restore");
if (await dialogService.confirm(text)) {
await server.post(`revisions/${revisionItem.revisionId}/restore`);
this.$widget.modal('hide');
toastService.showMessage('Note revision has been restored.');
toastService.showMessage(t("revisions.revision_restored"));
}
});
const $eraseRevisionButton = $('<button class="btn btn-sm" type="button">Delete this revision</button>');
const $eraseRevisionButton = $(`<button class="btn btn-sm" type="button">${t("revisions.delete_button")}</button>`);
$eraseRevisionButton.on('click', async () => {
const text = 'Do you want to delete this revision? This action will delete revision title and content, but still preserve revision metadata.';
const text = t("revisions.confirm_delete");
if (await dialogService.confirm(text)) {
await server.remove(`revisions/${revisionItem.revisionId}`);
this.loadRevisions(revisionItem.noteId);
toastService.showMessage('Note revision has been deleted.');
toastService.showMessage(t("revisions.revision_deleted"));
}
});
@ -220,7 +221,7 @@ export default class RevisionsDialog extends BasicWidget {
.append($eraseRevisionButton)
.append(' &nbsp; ');
const $downloadButton = $('<button class="btn btn-sm btn-primary" type="button">Download</button>');
const $downloadButton = $(`<button class="btn btn-sm btn-primary" type="button">${t("revisions.download_button")}</button>`);
$downloadButton.on('click', () => openService.downloadRevision(revisionItem.noteId, revisionItem.revisionId));
@ -254,18 +255,18 @@ export default class RevisionsDialog extends BasicWidget {
} else if (revisionItem.type === 'file') {
const $table = $("<table cellpadding='10'>")
.append($("<tr>").append(
$("<th>").text("MIME: "),
$("<th>").text(t("revisions.mime")),
$("<td>").text(revisionItem.mime)
))
.append($("<tr>").append(
$("<th>").text("File size:"),
$("<th>").text(t("revisions.file_size")),
$("<td>").text(utils.formatSize(revisionItem.contentLength))
));
if (fullRevision.content) {
$table.append($("<tr>").append(
$('<td colspan="2">').append(
$('<div style="font-weight: bold;">').text("Preview:"),
$('<div style="font-weight: bold;">').text(t("revisions.preview")),
$('<pre class="file-preview-content"></pre>')
.text(fullRevision.content)
)
@ -288,7 +289,7 @@ export default class RevisionsDialog extends BasicWidget {
this.$content.append($("<pre>").text(fullRevision.content));
} else {
this.$content.text("Preview isn't available for this note type.");
this.$content.text(t("revisions.preview_not_available"));
}
}
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
@ -6,88 +7,73 @@ const TPL = `<div class="sort-child-notes-dialog modal mx-auto" tabindex="-1" ro
<div class="modal-dialog modal-lg" style="max-width: 500px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Sort children by ...</h5>
<h5 class="modal-title mr-auto">${t("sort_child_notes.sort_children_by")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form class="sort-child-notes-form">
<div class="modal-body">
<h5>Sorting criteria</h5>
<h5>${t("sort_child_notes.sorting_criteria")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="title" checked>
title
<input class="form-check-input" type="radio" name="sort-by" value="title" checked>
${t("sort_child_notes.title")}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="dateCreated">
date created
${t("sort_child_notes.date_created")}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="dateModified">
date modified
${t("sort_child_notes.date_modified")}
</label>
</div>
<br/>
<h5>Sorting direction</h5>
<h5>${t("sort_child_notes.sorting_direction")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-direction" value="asc" checked>
ascending
${t("sort_child_notes.ascending")}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-direction" value="desc">
descending
${t("sort_child_notes.descending")}
</label>
</div>
<br />
<h5>Folders</h5>
<h5>${t("sort_child_notes.folders")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="sort-folders-first" value="1">
sort folders at the top
${t("sort_child_notes.sort_folders_at_top")}
</label>
</div>
<br />
<h5>Natural Sort</h5>
<h5>${t("sort_child_notes.natural_sort")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="sort-natural" value="1">
sort with respect to different character sorting and collation rules in different languages or regions.
${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
</label>
</div>
<br />
<div class="form-check">
<label>
Natural sort language
${t("sort_child_notes.natural_sort_language")}
<input class="form-control" name="sort-locale">
The language code for natural sort, e.g. "zh-CN" for Chinese.
${t("sort_child_notes.the_language_code_for_natural_sort")}
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Sort <kbd>enter</kbd></button>
<button type="submit" class="btn btn-primary">${t("sort_child_notes.sort")} <kbd>enter</kbd></button>
</div>
</form>
</div>
@ -106,13 +92,13 @@ export default class SortChildNotesDialog extends BasicWidget {
const sortNatural = this.$form.find("input[name='sort-natural']").is(":checked");
const sortLocale = this.$form.find("input[name='sort-locale']").val();
await server.put(`notes/${this.parentNoteId}/sort-children`, {sortBy, sortDirection, foldersFirst, sortNatural, sortLocale});
await server.put(`notes/${this.parentNoteId}/sort-children`, { sortBy, sortDirection, foldersFirst, sortNatural, sortLocale });
utils.closeActiveDialog();
});
}
async sortChildNotesEvent({node}) {
async sortChildNotesEvent({ node }) {
this.parentNoteId = node.data.noteId;
utils.openDialog(this.$widget);

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import utils from '../../services/utils.js';
import treeService from "../../services/tree.js";
import importService from "../../services/import.js";
@ -9,7 +10,7 @@ const TPL = `
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Upload attachments to note</h5>
<h5 class="modal-title">${t("upload_attachments.upload_attachments_to_note")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@ -17,25 +18,22 @@ const TPL = `
<form class="upload-attachment-form">
<div class="modal-body">
<div class="form-group">
<label for="upload-attachment-file-upload-input"><strong>Choose files</strong></label>
<label for="upload-attachment-file-upload-input"><strong>${t("upload_attachments.choose_files")}</strong></label>
<input type="file" class="upload-attachment-file-upload-input form-control-file" multiple />
<p>Files will be uploaded as attachments into <strong class="upload-attachment-note-title"></strong>.
<p>${t("upload_attachments.files_will_be_uploaded")} <strong class="upload-attachment-note-title"></strong>.</p>
</div>
<div class="form-group">
<strong>Options:</strong>
<strong>${t("upload_attachments.options")}:</strong>
<div class="checkbox">
<label data-toggle="tooltip" title="<p>If you check this option, Trilium will attempt to shrink the uploaded images by scaling and optimization which may affect the perceived image quality. If unchecked, images will be uploaded without changes.</p>">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>Shrink images</span>
<label data-toggle="tooltip" title="${t("upload_attachments.tooltip")}">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t("upload_attachments.shrink_images")}</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="upload-attachment-button btn btn-primary">Upload</button>
<button class="upload-attachment-button btn btn-primary">${t("upload_attachments.upload")}</button>
</div>
</form>
</div>
@ -60,9 +58,7 @@ export default class UploadAttachmentsDialog extends BasicWidget {
this.$form.on('submit', () => {
// disabling so that import is not triggered again.
this.$uploadButton.attr("disabled", "disabled");
this.uploadAttachments(this.parentNoteId);
return false;
});

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import ws from "../../services/ws.js";
import appContext from "../../components/app_context.js";
@ -15,15 +16,15 @@ const TPL = `
}
</style>
<button data-trigger-command="runActiveNote" class="execute-button floating-button btn" title="Execute script">
<button data-trigger-command="runActiveNote" class="execute-button floating-button btn" title="${t('code_buttons.execute_button_title')}">
<span class="bx bx-run"></span>
</button>
<button class="trilium-api-docs-button floating-button btn" title="Open Trilium API docs">
<button class="trilium-api-docs-button floating-button btn" title="${t('code_buttons.trilium_api_docs_button_title')}">
<span class="bx bx-help-circle"></span>
</button>
<button class="save-to-note-button floating-button btn">
<button class="save-to-note-button floating-button btn" title="${t('code_buttons.save_to_note_button_title')}">
<span class="bx bx-save"></span>
</button>
</div>`;
@ -39,7 +40,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget {
this.$widget = $(TPL);
this.$openTriliumApiDocsButton = this.$widget.find(".trilium-api-docs-button");
this.$openTriliumApiDocsButton.on("click", () => {
toastService.showMessage("Opening API docs...");
toastService.showMessage(t('code_buttons.opening_api_docs_message'));
if (this.note.mime.endsWith("frontend")) {
window.open("https://zadam.github.io/trilium/frontend_api/FrontendScriptApi.html", "_blank");
@ -58,7 +59,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget {
await appContext.tabManager.getActiveContext().setNote(notePath);
toastService.showMessage(`SQL Console note has been saved into ${await treeService.getNotePathTitle(notePath)}`);
toastService.showMessage(t('code_buttons.sql_console_saved_message', { notePath: await treeService.getNotePathTitle(notePath) }));
});
keyboardActionService.updateDisplayedShortcuts(this.$widget);

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import utils from "../../services/utils.js";
import imageService from "../../services/image.js";
@ -5,7 +6,7 @@ import imageService from "../../services/image.js";
const TPL = `
<button type="button"
class="copy-image-reference-button"
title="Copy image reference to the clipboard, can be pasted into a text note.">
title="${t('copy_image_reference_button.button_title')}">
<span class="bx bx-copy"></span>
<div class="hidden-image-copy"></div>

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `
@ -27,7 +28,7 @@ const TPL = `
<button type="button"
class="close-floating-buttons-button btn bx bx-x"
title="Hide buttons"></button>
title="${t('hide_floating_buttons_button.button_title')}"></button>
</div>
`;

View File

@ -1,9 +1,10 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `
<button type="button"
class="export-mermaid-button"
title="Export Mermaid diagram as SVG">
title="${t('mermaid_export_button.button_title')}">
<span class="bx bx-export"></span>
</button>
`;

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `
@ -11,20 +12,20 @@ const TPL = `
<button type="button"
class="relation-map-create-child-note floating-button btn bx bx-folder-plus"
title="Create new child note and add it into this relation map"></button>
title="${t('relation_map_buttons.create_child_note_title')}"></button>
<button type="button"
class="relation-map-reset-pan-zoom floating-button btn bx bx-crop"
title="Reset pan & zoom to initial coordinates and magnification"></button>
title="${t('relation_map_buttons.reset_pan_zoom_title')}"></button>
<div class="btn-group">
<button type="button"
class="relation-map-zoom-in floating-button btn bx bx-zoom-in"
title="Zoom In"></button>
title="${t('relation_map_buttons.zoom_in_title')}"></button>
<button type="button"
class="relation-map-zoom-out floating-button btn bx bx-zoom-out"
title="Zoom Out"></button>
title="${t('relation_map_buttons.zoom_out_title')}"></button>
</div>
</div>`;

View File

@ -2,6 +2,7 @@
* !!! Filename is intentionally mangled, because some adblockers don't like the word "backlinks".
*/
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import linkService from "../../services/link.js";
import server from "../../services/server.js";
@ -100,8 +101,8 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
this.toggle(true);
this.$count.text(
`${resp.count} backlink`
+ (resp.count === 1 ? '' : 's')
// i18next plural
`${t('zpetne_odkazy.backlink', {count: resp.count})}`
);
}
@ -138,7 +139,7 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
}));
if (backlink.relationName) {
$item.append($("<p>").text(`relation: ${backlink.relationName}`));
$item.append($("<p>").text(`${t('zpetne_odkazy.relation')}: ${backlink.relationName}`));
}
else {
$item.append(...backlink.excerpts);

View File

@ -4,6 +4,7 @@ import contextMenu from "../../menus/context_menu.js";
import noteCreateService from "../../services/note_create.js";
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>`;
@ -18,9 +19,9 @@ class MobileDetailMenuWidget extends BasicWidget {
x: e.pageX,
y: e.pageY,
items: [
{ title: "Insert child note", command: "insertChildNote", uiIcon: "bx bx-plus",
{ title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus",
enabled: note.type !== 'search' },
{ title: "Delete this note", command: "delete", uiIcon: "bx bx-trash",
{ title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash",
enabled: note.noteId !== 'root' }
],
selectMenuItemHandler: async ({command}) => {
@ -32,7 +33,7 @@ class MobileDetailMenuWidget extends BasicWidget {
const branchId = await treeService.getBranchIdFromUrl(notePath);
if (!branchId) {
throw new Error(`Cannot get branchId for notePath '${notePath}'`);
throw new Error(t("mobile_detail_menu.error_cannot_get_branch_id", { notePath }));
}
if (await branchService.deleteNotes([branchId])) {
@ -40,7 +41,7 @@ class MobileDetailMenuWidget extends BasicWidget {
}
}
else {
throw new Error(`Unrecognized command ${command}`);
throw new Error(t("mobile_detail_menu.error_unrecognized_command", { command }));
}
}
});

View File

@ -4,6 +4,7 @@ import ProtectedNoteSwitchWidget from "../protected_note_switch.js";
import EditabilitySelectWidget from "../editability_select.js";
import BookmarkSwitchWidget from "../bookmark_switch.js";
import SharedSwitchWidget from "../shared_switch.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="basic-properties-widget">
@ -28,13 +29,13 @@ const TPL = `
</style>
<div class="note-type-container">
<span>Note type:</span> &nbsp;
<span>${t("basic_properties.note_type")}:</span> &nbsp;
</div>
<div class="protected-note-switch-container"></div>
<div class="editability-select-container">
<span>Editable:</span> &nbsp;
<span>${t("basic_properties.editable")}:</span> &nbsp;
</div>
<div class="bookmark-switch-container"></div>
@ -72,7 +73,7 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: !this.note.isLaunchBarConfig(),
title: 'Basic Properties',
title: t("basic_properties.basic_properties"),
icon: 'bx bx-slider'
};
}

View File

@ -1,5 +1,6 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="book-properties-widget">
@ -15,32 +16,33 @@ const TPL = `
</style>
<div style="display: flex; align-items: baseline">
<span style="white-space: nowrap">View type:&nbsp; &nbsp;</span>
<span style="white-space: nowrap">${t("book_properties.view_type")}:&nbsp; &nbsp;</span>
<select class="view-type-select form-control form-control-sm">
<option value="grid">Grid</option>
<option value="list">List</option>
<option value="grid">${t("book_properties.grid")}</option>
<option value="list">${t("book_properties.list")}</option>
</select>
</div>
<button type="button"
class="collapse-all-button btn btn-sm"
title="Collapse all notes">
title="${t("book_properties.collapse_all_notes")}">
<span class="bx bx-layer-minus"></span>
Collapse
${t("book_properties.collapse")}
</button>
<button type="button"
class="expand-children-button btn btn-sm"
title="Expand all children">
title="${t("book_properties.expand_all_children")}">
<span class="bx bx-move-vertical"></span>
Expand
${t("book_properties.expand")}
</button>
</div>
`;
export default class BookPropertiesWidget extends NoteContextAwareWidget {
get name() {
return "bookProperties";
@ -58,7 +60,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
activate: true,
title: 'Book Properties',
title: t("book_properties.book_properties"),
icon: 'bx bx-book'
};
}
@ -101,7 +103,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
async toggleViewType(type) {
if (type !== 'list' && type !== 'grid') {
throw new Error(`Invalid view type '${type}'`);
throw new Error(t("book_properties.invalid_view_type", { type }));
}
await attributeService.setLabel(this.noteId, 'viewType', type);

View File

@ -3,6 +3,7 @@ import server from "../../services/server.js";
import froca from "../../services/froca.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import options from "../../services/options.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="edited-notes-widget">
@ -15,7 +16,7 @@ const TPL = `
}
</style>
<div class="no-edited-notes-found">No edited notes on this day yet ...</div>
<div class="no-edited-notes-found">${t("edited_notes.no_edited_notes_found")}</div>
<div class="edited-notes-list"></div>
</div>
@ -38,7 +39,7 @@ export default class EditedNotesWidget extends NoteContextAwareWidget {
activate:
(this.note.getPromotedDefinitionAttributes().length === 0 || !options.is('promotedAttributesOpenInRibbon'))
&& options.is('editedNotesOpenInRibbon'),
title: 'Edited Notes',
title: t("edited_notes.title"),
icon: 'bx bx-calendar-edit'
};
}
@ -72,7 +73,7 @@ export default class EditedNotesWidget extends NoteContextAwareWidget {
const $item = $('<span class="edited-note-line">');
if (editedNote.isDeleted) {
const title = `${editedNote.title} (deleted)`;
const title = `${editedNote.title} ${t("edited_notes.deleted")}`;
$item.append(
$("<i>")
.text(title)

View File

@ -4,6 +4,7 @@ import toastService from "../../services/toast.js";
import openService from "../../services/open.js";
import utils from "../../services/utils.js";
import protectedSessionHolder from "../../services/protected_session_holder.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="file-properties-widget">
@ -27,26 +28,26 @@ const TPL = `
<table class="file-table">
<tr>
<th>Note ID:</th>
<th class="text-nowrap">${t("file_properties.note_id")}:</th>
<td class="file-note-id"></td>
<th>Original file name:</th>
<th class="text-nowrap">${t("file_properties.original_file_name")}:</th>
<td class="file-filename"></td>
</tr>
<tr>
<th>File type:</th>
<th class="text-nowrap">${t("file_properties.file_type")}:</th>
<td class="file-filetype"></td>
<th>File size:</th>
<th class="text-nowrap">${t("file_properties.file_size")}:</th>
<td class="file-filesize"></td>
</tr>
<tr>
<td colspan="4">
<div class="file-buttons">
<button class="file-download btn btn-sm btn-primary" type="button">Download</button>
<button class="file-download btn btn-sm btn-primary" type="button">${t("file_properties.download")}</button>
&nbsp;
<button class="file-open btn btn-sm btn-primary" type="button">Open</button>
<button class="file-open btn btn-sm btn-primary" type="button">${t("file_properties.open")}</button>
&nbsp;
<button class="file-upload-new-revision btn btn-sm btn-primary">Upload new revision</button>
<button class="file-upload-new-revision btn btn-sm btn-primary">${t("file_properties.upload_new_revision")}</button>
<input type="file" class="file-upload-new-revision-input" style="display: none">
</div>
@ -72,7 +73,7 @@ export default class FilePropertiesWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
activate: true,
title: 'File',
title: t("file_properties.title"),
icon: 'bx bx-file'
};
}
@ -103,12 +104,11 @@ export default class FilePropertiesWidget extends NoteContextAwareWidget {
const result = await server.upload(`notes/${this.noteId}/file`, fileToUpload);
if (result.uploaded) {
toastService.showMessage("New file revision has been uploaded.");
toastService.showMessage(t("file_properties.upload_success"));
this.refresh();
}
else {
toastService.showError("Upload of a new file revision failed.");
} else {
toastService.showError(t("file_properties.upload_failed"));
}
});
}
@ -126,7 +126,7 @@ export default class FilePropertiesWidget extends NoteContextAwareWidget {
// open doesn't work for protected notes since it works through a browser which isn't in protected session
this.$openButton.toggle(!note.isProtected);
this.$downloadButton.toggle(!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable())
this.$uploadNewRevisionButton.toggle(!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable())
this.$downloadButton.toggle(!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable());
this.$uploadNewRevisionButton.toggle(!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable());
}
}

View File

@ -3,34 +3,35 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
import toastService from "../../services/toast.js";
import openService from "../../services/open.js";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="image-properties">
<div style="display: flex; justify-content: space-evenly; margin: 10px;">
<span>
<strong>Original file name:</strong>
<strong>${t("image_properties.original_file_name")}:</strong>
<span class="image-filename"></span>
</span>
<span>
<strong>File type:</strong>
<strong>${t("image_properties.file_type")}:</strong>
<span class="image-filetype"></span>
</span>
<span>
<strong>File size:</strong>
<strong>${t("image_properties.file_size")}:</strong>
<span class="image-filesize"></span>
</span>
</div>
<div class="no-print" style="display: flex; justify-content: space-evenly; margin: 10px;">
<button class="image-download btn btn-sm btn-primary" type="button">Download</button>
<button class="image-download btn btn-sm btn-primary" type="button">${t("image_properties.download")}</button>
<button class="image-open btn btn-sm btn-primary" type="button">Open</button>
<button class="image-open btn btn-sm btn-primary" type="button">${t("image_properties.open")}</button>
<button class="image-copy-reference-to-clipboard btn btn-sm btn-primary" type="button">Copy reference to clipboard</button>
<button class="image-copy-reference-to-clipboard btn btn-sm btn-primary" type="button">${t("image_properties.copy_reference_to_clipboard")}</button>
<button class="image-upload-new-revision btn btn-sm btn-primary" type="button">Upload new revision</button>
<button class="image-upload-new-revision btn btn-sm btn-primary" type="button">${t("image_properties.upload_new_revision")}</button>
</div>
<input type="file" class="image-upload-new-revision-input" style="display: none">
@ -53,7 +54,7 @@ export default class ImagePropertiesWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
activate: true,
title: 'Image',
title: t("image_properties.title"),
icon: 'bx bx-image'
};
}
@ -89,14 +90,13 @@ export default class ImagePropertiesWidget extends NoteContextAwareWidget {
const result = await server.upload(`images/${this.noteId}`, fileToUpload);
if (result.uploaded) {
toastService.showMessage("New image revision has been uploaded.");
toastService.showMessage(t("image_properties.upload_success"));
await utils.clearBrowserCache();
this.refresh();
}
else {
toastService.showError(`Upload of a new image revision failed: ${result.message}`);
} else {
toastService.showError(t("image_properties.upload_failed", { message: result.message }));
}
});
}

View File

@ -2,6 +2,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js";
import attributeRenderer from "../../services/attribute_renderer.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="inherited-attributes-widget">
@ -44,7 +45,7 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: !this.note.isLaunchBarConfig(),
title: "Inherited Attributes",
title: t("inherited_attribute_list.title"),
icon: "bx bx-list-plus"
};
}
@ -63,7 +64,7 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
const inheritedAttributes = this.getInheritedAttributes(note);
if (inheritedAttributes.length === 0) {
this.$container.append("No inherited attributes.");
this.$container.append(t("inherited_attribute_list.no_inherited_attributes"));
return;
}

View File

@ -1,6 +1,7 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import server from "../../services/server.js";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="note-info-widget">
@ -30,31 +31,27 @@ const TPL = `
<table class="note-info-widget-table">
<tr>
<th>Note ID:</th>
<th>${t("note_info_widget.note_id")}:</th>
<td class="note-info-note-id"></td>
<th>Created:</th>
<th>${t("note_info_widget.created")}:</th>
<td class="note-info-date-created"></td>
<th>Modified:</th>
<th>${t("note_info_widget.modified")}:</th>
<td class="note-info-date-modified"></td>
</tr>
<tr>
<th>Type:</th>
<th>${t("note_info_widget.type")}:</th>
<td>
<span class="note-info-type"></span>
<span class="note-info-mime"></span>
</td>
<th title="Note size provides rough estimate of storage requirements for this note. It takes into account note's content and content of its note revisions.">Note size:</th>
<th title="${t("note_info_widget.note_size_info")}">${t("note_info_widget.note_size")}:</th>
<td colspan="3">
<button class="btn btn-sm calculate-button" style="padding: 0px 10px 0px 10px;">
<span class="bx bx-calculator"></span> calculate
<span class="bx bx-calculator"></span> ${t("note_info_widget.calculate")}
</button>
<span class="note-sizes-wrapper">
<span class="note-size"></span>
<span class="subtree-size"></span>
</span>
</td>
@ -62,6 +59,7 @@ const TPL = `
</table>
</div>
`;
export default class NoteInfoWidget extends NoteContextAwareWidget {
get name() {
return "noteInfo";
@ -78,7 +76,7 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: this.isEnabled(),
title: 'Note Info',
title: t("note_info_widget.title"),
icon: 'bx bx-info-circle'
};
}
@ -111,9 +109,8 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
const subTreeResp = await server.get(`stats/subtree-size/${this.noteId}`);
if (subTreeResp.subTreeNoteCount > 1) {
this.$subTreeSize.text(`(subtree size: ${utils.formatSize(subTreeResp.subTreeSize)} in ${subTreeResp.subTreeNoteCount} notes)`);
}
else {
this.$subTreeSize.text(t("note_info_widget.subtree_size", { size: utils.formatSize(subTreeResp.subTreeSize), count: subTreeResp.subTreeNoteCount }));
} else {
this.$subTreeSize.text("");
}
});
@ -143,7 +140,7 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
this.$noteSizesWrapper.hide();
}
entitiesReloadedEvent({loadResults}) {
entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) {
this.refresh();
}

View File

@ -1,5 +1,6 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import NoteMapWidget from "../note_map.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="note-map-ribbon-widget">
@ -25,8 +26,8 @@ const TPL = `
}
</style>
<button class="bx bx-arrow-to-bottom icon-action open-full-button" title="Open full"></button>
<button class="bx bx-arrow-to-top icon-action collapse-button" style="display: none;" title="Collapse to normal size"></button>
<button class="bx bx-arrow-to-bottom icon-action open-full-button" title="${t("note_map.open_full")}"></button>
<button class="bx bx-arrow-to-top icon-action collapse-button" style="display: none;" title="${t("note_map.collapse")}"></button>
<div class="note-map-container"></div>
</div>`;
@ -50,7 +51,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: this.isEnabled(),
title: 'Note Map',
title: t("note_map.title"),
icon: 'bx bx-map-alt'
};
}

View File

@ -1,6 +1,7 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import treeService from "../../services/tree.js";
import linkService from "../../services/link.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="note-paths-widget">
@ -32,7 +33,7 @@ const TPL = `
<ul class="note-path-list"></ul>
<button class="btn btn-sm" data-trigger-command="cloneNoteIdsTo">Clone note to new location...</button>
<button class="btn btn-sm" data-trigger-command="cloneNoteIdsTo">${t("note_paths.clone_button")}</button>
</div>`;
export default class NotePathsWidget extends NoteContextAwareWidget {
@ -47,7 +48,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: true,
title: 'Note Paths',
title: t("note_paths.title"),
icon: 'bx bx-collection'
};
}
@ -76,10 +77,9 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
.filter(notePath => !notePath.isHidden);
if (sortedNotePaths.length > 0) {
this.$notePathIntro.text("This note is placed into the following paths:");
}
else {
this.$notePathIntro.text("This note is not yet placed into the note tree.");
this.$notePathIntro.text(t("note_paths.intro_placed"));
} else {
this.$notePathIntro.text(t("note_paths.intro_not_placed"));
}
const renderedPaths = [];
@ -110,21 +110,20 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
if (!notePathRecord || notePathRecord.isInHoistedSubTree) {
$noteLink.addClass("path-in-hoisted-subtree");
}
else {
icons.push(`<span class="bx bx-trending-up" title="This path is outside of hoisted note and you would have to unhoist."></span>`);
} else {
icons.push(`<span class="bx bx-trending-up" title="${t("note_paths.outside_hoisted")}"></span>`);
}
if (notePathRecord?.isArchived) {
$noteLink.addClass("path-archived");
icons.push(`<span class="bx bx-archive" title="Archived"></span>`);
icons.push(`<span class="bx bx-archive" title="${t("note_paths.archived")}"></span>`);
}
if (notePathRecord?.isSearch) {
$noteLink.addClass("path-search");
icons.push(`<span class="bx bx-search" title="Search"></span>`);
icons.push(`<span class="bx bx-search" title="${t("note_paths.search")}"></span>`);
}
if (icons.length > 0) {

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `
@ -10,7 +11,7 @@ const TPL = `
</style>
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap">
This note was originally taken from: <a class="page-url external"></a>
${t('note_properties.this_note_was_originally_taken_from')} <a class="page-url external"></a>
</div>
</div>`;
@ -26,7 +27,7 @@ export default class NotePropertiesWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
activate: true,
title: 'Info',
title: t('note_properties.info'),
icon: 'bx bx-info-square'
};
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js";
import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js";
@ -48,7 +49,7 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: !this.note.isLaunchBarConfig(),
title: "Owned Attributes",
title: t('owned_attribute_list.owned_attributes'),
icon: "bx bx-list-check"
};
}

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import ws from "../../services/ws.js";
import treeService from "../../services/tree.js";
@ -76,7 +77,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
return {
show: true,
activate: options.is('promotedAttributesOpenInRibbon'),
title: "Promoted Attributes",
title: t('promoted_attributes.promoted_attributes'),
icon: "bx bx-table"
};
}
@ -221,11 +222,11 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
$input.prop('type', 'datetime-local')
}
else if (definition.labelType === 'url') {
$input.prop("placeholder", "http://website...");
$input.prop("placeholder", t("promoted_attributes.url_placeholder"));
const $openButton = $("<span>")
.addClass("input-group-text open-external-link-button bx bx-window-open")
.prop("title", "Open external link")
.prop("title", t("promoted_attributes.open_external_link"))
.on('click', () => window.open($input.val(), '_blank'));
$input.after($("<div>")
@ -233,7 +234,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
.append($openButton));
}
else {
ws.logError(`Unknown labelType '${definitionAttr.labelType}'`);
ws.logError(t("promoted_attributes.unknown_label_type", { type: definitionAttr.labelType }));
}
}
else if (valueAttr.type === 'relation') {
@ -256,14 +257,14 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
}
}
else {
ws.logError(`Unknown attribute type '${valueAttr.type}'`);
ws.logError(t(`promoted_attributes.unknown_attribute_type`, {type: valueAttr.type}));
return;
}
if (definition.multiplicity === "multi") {
const $addButton = $("<span>")
.addClass("bx bx-plus pointer")
.prop("title", "Add new attribute")
.prop("title", t("promoted_attributes.add_new_attribute"))
.on('click', async () => {
const $new = await this.createPromotedAttributeCell(definitionAttr, {
attributeId: "",
@ -279,7 +280,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
const $removeButton = $("<span>")
.addClass("bx bx-trash pointer")
.prop("title", "Remove this attribute")
.prop("title", t("promoted_attributes.remove_this_attribute"))
.on('click', async () => {
const attributeId = $input.attr("data-attribute-id");

View File

@ -1,5 +1,6 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import keyboardActionService from "../../services/keyboard_actions.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="script-runner-widget">
@ -37,7 +38,7 @@ export default class ScriptExecutorWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
activate: true,
title: this.isTriliumSqlite() ? 'Query' : 'Script',
title: this.isTriliumSqlite() ? t('script_executor.query') : t('script_executor.script'),
icon: 'bx bx-run'
};
}
@ -52,7 +53,7 @@ export default class ScriptExecutorWidget extends NoteContextAwareWidget {
async refreshWithNote(note) {
const executeTitle = note.getLabelValue('executeButton')
|| (this.isTriliumSqlite() ? 'Execute Query' : 'Execute Script');
|| (this.isTriliumSqlite() ? t('script_executor.execute_query') : t('script_executor.execute_script'));
this.$executeButton.text(executeTitle);
this.$executeButton.attr('title', executeTitle);

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import froca from "../../services/froca.js";
@ -73,54 +74,54 @@ const TPL = `
<div class="search-settings">
<table class="search-setting-table">
<tr>
<td class="title-column">Add search option:</td>
<td class="title-column">${t('search_definition.add_search_option')}</td>
<td colspan="2" class="add-search-option">
<button type="button" class="btn btn-sm" data-search-option-add="searchString">
<span class="bx bx-text"></span>
search string
${t('search_definition.search_string')}
</button>
<button type="button" class="btn btn-sm" data-search-option-add="searchScript">
<span class="bx bx-code"></span>
search script
${t('search_definition.search_script')}
</button>
<button type="button" class="btn btn-sm" data-search-option-add="ancestor">
<span class="bx bx-filter-alt"></span>
ancestor
${t('search_definition.ancestor')}
</button>
<button type="button" class="btn btn-sm" data-search-option-add="fastSearch"
title="Fast search option disables full text search of note contents which might speed up searching in large databases.">
title="${t('search_definition.fast_search_description')}">
<span class="bx bx-run"></span>
fast search
${t('search_definition.fast_search')}
</button>
<button type="button" class="btn btn-sm" data-search-option-add="includeArchivedNotes"
title="Archived notes are by default excluded from search results, with this option they will be included.">
title="${t('search_definition.include_archived_notes_description')}">
<span class="bx bx-archive"></span>
include archived
${t('search_definition.include_archived')}
</button>
<button type="button" class="btn btn-sm" data-search-option-add="orderBy">
<span class="bx bx-arrow-from-top"></span>
order by
${t('search_definition.order_by')}
</button>
<button type="button" class="btn btn-sm" data-search-option-add="limit" title="Limit number of results">
<button type="button" class="btn btn-sm" data-search-option-add="limit" title="${t('search_definition.limit_description')}">
<span class="bx bx-stop"></span>
limit
${t('search_definition.limit')}
</button>
<button type="button" class="btn btn-sm" data-search-option-add="debug" title="Debug will print extra debugging information into the console to aid in debugging complex queries">
<button type="button" class="btn btn-sm" data-search-option-add="debug" title="${t('search_definition.debug_description')}">
<span class="bx bx-bug"></span>
debug
${t('search_definition.debug')}
</button>
<div class="dropdown" style="display: inline-block;">
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="bx bxs-zap"></span>
action
${t('search_definition.action')}
</button>
<div class="dropdown-menu action-list"></div>
</div>
@ -134,19 +135,19 @@ const TPL = `
<div style="display: flex; justify-content: space-evenly">
<button type="button" class="btn btn-sm search-button">
<span class="bx bx-search"></span>
Search
${t('search_definition.search')}
<kbd>enter</kbd>
<kbd>${t('search_definition.enter')}</kbd>
</button>
<button type="button" class="btn btn-sm search-and-execute-button">
<span class="bx bxs-zap"></span>
Search & Execute actions
${t('search_definition.search_execute')}
</button>
<button type="button" class="btn btn-sm save-to-note-button">
<span class="bx bx-save"></span>
Save to note
${t('search_definition.save_to_note')}
</button>
</div>
</td>
@ -180,7 +181,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
activate: true,
title: 'Search Parameters',
title: t('search_definition.search_parameters'),
icon: 'bx bx-search'
};
}
@ -198,7 +199,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
this.$actionList.append(
$('<a class="dropdown-item" href="#">')
.attr('data-action-add', action.actionName)
.text(action.actionTitle)
.text(t(`${action.actionTitle}`))
);
}
}
@ -211,7 +212,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
await clazz.create(this.noteId);
}
else {
logError(`Unknown search option ${searchOptionName}`);
logError(t('search_definition.unknown_search_option', { searchOptionName }));
}
this.refresh();
@ -243,8 +244,9 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(notePath);
toastService.showMessage(`Search note has been saved into ${await treeService.getNotePathTitle(notePath)}`);
// Note the {{- notePathTitle}} in json file is not typo, it's unescaping
// See https://www.i18next.com/translation-function/interpolation#unescape
toastService.showMessage(t('search_definition.search_note_saved', { notePathTitle: await treeService.getNotePathTitle(notePath) }));
});
}
@ -307,7 +309,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
this.triggerCommand('refreshResults');
toastService.showMessage('Actions have been executed.', 3000);
toastService.showMessage(t('search_definition.actions_executed'), 3000);
}
entitiesReloadedEvent({loadResults}) {

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import linkService from "../../services/link.js";
import server from "../../services/server.js";
import froca from "../../services/froca.js";
@ -48,7 +49,7 @@ export default class SimilarNotesWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: this.isEnabled(),
title: 'Similar Notes',
title: t('similar_notes.title'),
icon: 'bx bx-bar-chart'
};
}
@ -67,7 +68,7 @@ export default class SimilarNotesWidget extends NoteContextAwareWidget {
const similarNotes = await server.get(`similar-notes/${this.noteId}`);
if (similarNotes.length === 0) {
this.$similarNotesWrapper.empty().append("No similar notes found.");
this.$similarNotesWrapper.empty().append(t('similar_notes.no_similar_notes_found'));
return;
}

View File

@ -2,6 +2,7 @@ import server from "../../services/server.js";
import ws from "../../services/ws.js";
import Component from "../../components/component.js";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js"; // 新增的导入
export default class AbstractSearchOption extends Component {
constructor(attribute, note) {
@ -27,14 +28,14 @@ export default class AbstractSearchOption extends Component {
$rendered.find('.search-option-del')
.on('click', () => this.deleteOption())
.attr('title', 'Remove this search option');
.attr('title', t('abstract_search_option.remove_this_search_option')); // 使用 t 函数处理 i18n 字符串
utils.initHelpDropdown($rendered);
return $rendered;
}
catch (e) {
logError(`Failed rendering search option: ${JSON.stringify(this.attribute.dto)} with error: ${e.message} ${e.stack}`);
logError(t('abstract_search_option.failed_rendering', { dto: JSON.stringify(this.attribute.dto), error: e.message, stack: e.stack })); // 使用 t 函数处理 i18n 字符串
return null;
}
}

View File

@ -1,46 +1,47 @@
import AbstractSearchOption from "./abstract_search_option.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr>
<td colspan="2">
<div style="display: flex; align-items: center;">
<div style="margin-right: 10px">Ancestor:</div>
<div style="margin-right: 10px">${t('ancestor.label')}:</div>
<div class="input-group" style="flex-shrink: 2">
<input class="ancestor form-control" placeholder="search for note by its name">
<input class="ancestor form-control" placeholder="${t('ancestor.placeholder')}">
</div>
<div style="margin-left: 10px; margin-right: 10px">depth:</div>
<div style="margin-left: 10px; margin-right: 10px">${t('ancestor.depth_label')}:</div>
<select name="depth" class="form-control d-inline ancestor-depth" style="flex-shrink: 3">
<option value="">doesn't matter</option>
<option value="eq1">is exactly 1 (direct children)</option>
<option value="eq2">is exactly 2</option>
<option value="eq3">is exactly 3</option>
<option value="eq4">is exactly 4</option>
<option value="eq5">is exactly 5</option>
<option value="eq6">is exactly 6</option>
<option value="eq7">is exactly 7</option>
<option value="eq8">is exactly 8</option>
<option value="eq9">is exactly 9</option>
<option value="gt0">is greater than 0</option>
<option value="gt1">is greater than 1</option>
<option value="gt2">is greater than 2</option>
<option value="gt3">is greater than 3</option>
<option value="gt4">is greater than 4</option>
<option value="gt5">is greater than 5</option>
<option value="gt6">is greater than 6</option>
<option value="gt7">is greater than 7</option>
<option value="gt8">is greater than 8</option>
<option value="gt9">is greater than 9</option>
<option value="lt2">is less than 2</option>
<option value="lt3">is less than 3</option>
<option value="lt4">is less than 4</option>
<option value="lt5">is less than 5</option>
<option value="lt6">is less than 6</option>
<option value="lt7">is less than 7</option>
<option value="lt8">is less than 8</option>
<option value="lt9">is less than 9</option>
<option value="">${t('ancestor.depth_doesnt_matter')}</option>
<option value="eq1">${t('ancestor.depth_eq', {count: 1})} (${t('ancestor.direct_children')})</option>
<option value="eq2">${t('ancestor.depth_eq', {count: 2})}</option>
<option value="eq3">${t('ancestor.depth_eq', {count: 3})}</option>
<option value="eq4">${t('ancestor.depth_eq', {count: 4})}</option>
<option value="eq5">${t('ancestor.depth_eq', {count: 5})}</option>
<option value="eq6">${t('ancestor.depth_eq', {count: 6})}</option>
<option value="eq7">${t('ancestor.depth_eq', {count: 7})}</option>
<option value="eq8">${t('ancestor.depth_eq', {count: 8})}</option>
<option value="eq9">${t('ancestor.depth_eq', {count: 9})}</option>
<option value="gt0">${t('ancestor.depth_gt', {count: 0})}</option>
<option value="gt1">${t('ancestor.depth_gt', {count: 1})}</option>
<option value="gt2">${t('ancestor.depth_gt', {count: 2})}</option>
<option value="gt3">${t('ancestor.depth_gt', {count: 3})}</option>
<option value="gt4">${t('ancestor.depth_gt', {count: 4})}</option>
<option value="gt5">${t('ancestor.depth_gt', {count: 5})}</option>
<option value="gt6">${t('ancestor.depth_gt', {count: 6})}</option>
<option value="gt7">${t('ancestor.depth_gt', {count: 7})}</option>
<option value="gt8">${t('ancestor.depth_gt', {count: 8})}</option>
<option value="gt9">${t('ancestor.depth_gt', {count: 9})}</option>
<option value="lt2">${t('ancestor.depth_lt', {count: 2})}</option>
<option value="lt3">${t('ancestor.depth_lt', {count: 3})}</option>
<option value="lt4">${t('ancestor.depth_lt', {count: 4})}</option>
<option value="lt5">${t('ancestor.depth_lt', {count: 5})}</option>
<option value="lt6">${t('ancestor.depth_lt', {count: 6})}</option>
<option value="lt7">${t('ancestor.depth_lt', {count: 7})}</option>
<option value="lt8">${t('ancestor.depth_lt', {count: 8})}</option>
<option value="lt9">${t('ancestor.depth_lt', {count: 9})}</option>
</select>
</div>
</td>

View File

@ -1,22 +1,20 @@
import AbstractSearchOption from "./abstract_search_option.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr data-search-option-conf="debug">
<td colSpan="2">
<span class="bx bx-bug"></span>
Debug
${t("debug.debug")}
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>Debug will print extra debugging information into the console to aid in debugging complex queries.</p>
<p>To access the debug information, execute query and click on "Show backend log" in top left corner.</p>
<p>${t("debug.debug_info")}</p>
<p>${t("debug.access_info")}</p>
</div>
</div>
<span class="bx bx-x icon-action search-option-del"></span>
</td>
</tr>`;

View File

@ -1,20 +1,19 @@
import AbstractSearchOption from "./abstract_search_option.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr data-search-option-conf="fastSearch">
<td colSpan="2">
<span class="bx bx-run"></span>
Fast search
${t('fast_search.fast_search')}
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
Fast search option disables full text search of note contents which might speed up searching in large databases.
${t('fast_search.description')}
</div>
</div>
<span class="bx bx-x icon-action search-option-del"></span>
</td>
</tr>`;

View File

@ -1,11 +1,11 @@
import AbstractSearchOption from "./abstract_search_option.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr data-search-option-conf="includeArchivedNotes">
<td colspan="2">
<span class="bx bx-archive"></span>
Include archived notes
${t('include_archived_notes.include_archived_notes')}
</td>
<td class="button-column">
<span class="bx bx-x icon-action search-option-del"></span>

View File

@ -1,11 +1,11 @@
import AbstractSearchOption from "./abstract_search_option.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr data-search-option-conf="limit">
<td class="title-column">
<span class="bx bx-stop"></span>
Limit
${t('limit.limit')}
</td>
<td>
<input name="limit" class="form-control" type="number" min="1" step="1" />
@ -14,7 +14,7 @@ const TPL = `
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
Take only first X specified results.
${t('limit.take_first_x_results')}
</div>
</div>

View File

@ -1,33 +1,33 @@
import AbstractSearchOption from "./abstract_search_option.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr data-search-option-conf="orderBy">
<td class="title-column">
<span class="bx bx-arrow-from-top"></span>
Order by
${t('order_by.order_by')}
</td>
<td>
<select name="orderBy" class="form-control w-auto d-inline">
<option value="relevancy">Relevancy (default)</option>
<option value="title">Title</option>
<option value="dateCreated">Date created</option>
<option value="dateModified">Date of last modification</option>
<option value="contentSize">Note content size</option>
<option value="contentAndAttachmentsSize">Note content size including attachments</option>
<option value="contentAndAttachmentsAndRevisionsSize">Note content size including attachments and revisions</option>
<option value="revisionCount">Number of revisions</option>
<option value="childrenCount">Number of children notes</option>
<option value="parentCount">Number of clones</option>
<option value="ownedLabelCount">Number of labels</option>
<option value="ownedRelationCount">Number of relations</option>
<option value="targetRelationCount">Number of relations targeting the note</option>
<option value="random">Random order</option>
<option value="relevancy">${t('order_by.relevancy')}</option>
<option value="title">${t('order_by.title')}</option>
<option value="dateCreated">${t('order_by.date_created')}</option>
<option value="dateModified">${t('order_by.date_modified')}</option>
<option value="contentSize">${t('order_by.content_size')}</option>
<option value="contentAndAttachmentsSize">${t('order_by.content_and_attachments_size')}</option>
<option value="contentAndAttachmentsAndRevisionsSize">${t('order_by.content_and_attachments_and_revisions_size')}</option>
<option value="revisionCount">${t('order_by.revision_count')}</option>
<option value="childrenCount">${t('order_by.children_count')}</option>
<option value="parentCount">${t('order_by.parent_count')}</option>
<option value="ownedLabelCount">${t('order_by.owned_label_count')}</option>
<option value="ownedRelationCount">${t('order_by.owned_relation_count')}</option>
<option value="targetRelationCount">${t('order_by.target_relation_count')}</option>
<option value="random">${t('order_by.random')}</option>
</select>
<select name="orderDirection" class="form-control w-auto d-inline">
<option value="asc">Ascending (default)</option>
<option value="desc">Descending</option>
<option value="asc">${t('order_by.asc')}</option>
<option value="desc">${t('order_by.desc')}</option>
</select>
</td>
<td class="button-column">

View File

@ -1,37 +1,30 @@
import AbstractSearchOption from "./abstract_search_option.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr>
<td class="title-column">
Search script:
${t('search_script.title')}
</td>
<td>
<div class="input-group">
<input class="search-script form-control" placeholder="search for note by its name">
<input class="search-script form-control" placeholder="${t('search_script.placeholder')}">
</div>
</td>
<td class="button-column">
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>Search script allows to define search results by running a script. This provides maximal flexibility when standard search doesn't suffice.</p>
<p>${t('search_script.description1')}</p>
<p>Search script must be of type "code" and subtype "JavaScript backend". The script receives needs to return an array of noteIds or notes.</p>
<p>${t('search_script.description2')}</p>
<p>See this example:</p>
<p>${t('search_script.example_title')}</p>
<pre>
// 1. prefiltering using standard search
const candidateNotes = api.searchForNotes("#journal");
<pre>${t('search_script.example_code')}</pre>
// 2. applying custom search criteria
const matchedNotes = candidateNotes
.filter(note => note.title.match(/[0-9]{1,2}\. ?[0-9]{1,2}\. ?[0-9]{4}\/));
return matchedNotes;</pre>
<p>Note that search script and search string can't be combined with each other.</p>
<p>${t('search_script.note')}</p>
</div>
</div>

View File

@ -3,27 +3,28 @@ import SpacedUpdate from "../../services/spaced_update.js";
import server from "../../services/server.js";
import shortcutService from "../../services/shortcuts.js";
import appContext from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
const TPL = `
<tr>
<td class="title-column">Search string:</td>
<td class="title-column">${t('search_string.title_column')}</td>
<td>
<textarea class="form-control search-string" placeholder="fulltext keywords, #tag = value ..."></textarea>
<textarea class="form-control search-string" placeholder="${t('search_string.placeholder')}"></textarea>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<strong>Search syntax</strong> - also see <button class="btn btn-sm" type="button" data-help-page="search.html">complete help on search syntax</button>
<strong>${t('search_string.search_syntax')}</strong> - ${t('search_string.also_see')} <button class="btn btn-sm" type="button" data-help-page="search.html">${t('search_string.complete_help')}</button>
<p>
<ul>
<li>Just enter any text for full text search</li>
<li><code>#abc</code> - returns notes with label abc</li>
<li><code>#year = 2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li>
<li><code>#rock #pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li>
<li><code>#rock or #pop</code> - only one of the labels must be present</li>
<li><code>#year &lt;= 2000</code> - numerical comparison (also &gt;, &gt;=, &lt;).</li>
<li><code>note.dateCreated >= MONTH-1</code> - notes created in the last month</li>
<li>${t('search_string.full_text_search')}</li>
<li><code>#abc</code> - ${t('search_string.label_abc')}</li>
<li><code>#year = 2019</code> - ${t('search_string.label_year')}</li>
<li><code>#rock #pop</code> - ${t('search_string.label_rock_pop')}</li>
<li><code>#rock or #pop</code> - ${t('search_string.label_rock_or_pop')}</li>
<li><code>#year &lt;= 2000</code> - ${t('search_string.label_year_comparison')}</li>
<li><code>note.dateCreated >= MONTH-1</code> - ${t('search_string.label_date_created')}</li>
</ul>
</p>
</div>
@ -76,7 +77,7 @@ export default class SearchString extends AbstractSearchOption {
showSearchErrorEvent({error}) {
this.$searchString.tooltip({
trigger: 'manual',
title: `Search error: ${error}`,
title: `${t('search_string.error', {error})}`,
placement: 'bottom'
});

View File

@ -3,6 +3,7 @@ import AttachmentDetailWidget from "../attachment_detail.js";
import linkService from "../../services/link.js";
import froca from "../../services/froca.js";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="attachment-detail note-detail-printable">
@ -47,15 +48,15 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
this.$wrapper.empty();
this.children = [];
const $helpButton = $('<button class="attachment-help-button" type="button" data-help-page="attachments.html" title="Open help page on attachments"><span class="bx bx-help-circle"></span></button>');
const $helpButton = $('<button class="attachment-help-button" type="button" data-help-page="attachments.html" title="' + t('attachment_detail.open_help_page') + '"><span class="bx bx-help-circle"></span></button>');
utils.initHelpButtons($helpButton);
this.$linksWrapper.empty().append(
"Owning note: ",
t('attachment_detail.owning_note'),
await linkService.createLink(this.noteId),
", you can also open the ",
t('attachment_detail.you_can_also_open'),
await linkService.createLink(this.noteId, {
title: 'List of all attachments',
title: t('attachment_detail.list_of_all_attachments'),
viewScope: {
viewMode: 'attachments'
}
@ -66,8 +67,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
const attachment = await froca.getAttachment(this.attachmentId, true);
if (!attachment) {
this.$wrapper.html("<strong>This attachment has been deleted.</strong>");
this.$wrapper.html("<strong>" + t('attachment_detail.attachment_deleted') + "</strong>");
return;
}

View File

@ -2,6 +2,7 @@ import TypeWidget from "./type_widget.js";
import AttachmentDetailWidget from "../attachment_detail.js";
import linkService from "../../services/link.js";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="attachment-list note-detail-printable">
@ -39,19 +40,19 @@ export default class AttachmentListTypeWidget extends TypeWidget {
}
async doRefresh(note) {
const $helpButton = $('<button class="attachment-help-button" type="button" data-help-page="attachments.html" title="Open help page on attachments"><span class="bx bx-help-circle"></span></button>');
const $helpButton = $('<button class="attachment-help-button" type="button" data-help-page="attachments.html" title="' + t('attachment_list.open_help_page') + '"><span class="bx bx-help-circle"></span></button>');
utils.initHelpButtons($helpButton);
const noteLink = await linkService.createLink(this.noteId); // do separately to avoid race condition between empty() and .append()
this.$linksWrapper.empty().append(
$('<div>').append(
"Owning note: ",
t('attachment_list.owning_note'),
noteLink,
),
$('<div>').append(
$('<button class="btn btn-sm">')
.text("Upload attachments")
.text(t('attachment_list.upload_attachments'))
.on('click', () => this.triggerCommand("showUploadAttachmentsDialog", {noteId: this.noteId})),
$helpButton
)
@ -64,8 +65,7 @@ export default class AttachmentListTypeWidget extends TypeWidget {
const attachments = await note.getAttachments();
if (attachments.length === 0) {
this.$list.html('<div class="alert alert-info">This note has no attachments.</div>');
this.$list.html('<div class="alert alert-info">' + t('attachment_list.no_attachments') + '</div>');
return;
}

View File

@ -1,4 +1,5 @@
import TypeWidget from "./type_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="note-detail-book note-detail-printable">
@ -13,7 +14,7 @@ const TPL = `
</style>
<div class="note-detail-book-empty-help alert alert-warning" style="margin: 50px; padding: 20px;">
This note of type Book doesn't have any child notes so there's nothing to display. See <a href="https://triliumnext.github.io/Docs/Wiki/book-note.html">wiki</a> for details.
${t('book.no_children_help')}
</div>
</div>`;

View File

@ -1,5 +1,6 @@
import NoteContextAwareWidget from "../../note_context_aware_widget.js";
import server from "../../../services/server.js";
import { t } from "../../../services/i18n.js";
const TPL = `<div style="height: 100%; display: flex; flex-direction: column;">
<style>
@ -13,7 +14,7 @@ const TPL = `<div style="height: 100%; display: flex; flex-direction: column;">
<textarea class="backend-log-textarea" readonly="readonly"></textarea>
<div style="display: flex; justify-content: space-around; margin-top: 10px;">
<button class="refresh-backend-log-button btn btn-primary">Refresh</button>
<button class="refresh-backend-log-button btn btn-primary">${t("backend_log.refresh")}</button>
</div>
</div>`;

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import libraryLoader from "../../services/library_loader.js";
import TypeWidget from "./type_widget.js";
import keyboardActionService from "../../services/keyboard_actions.js";
@ -62,7 +63,7 @@ export default class EditableCodeTypeWidget extends TypeWidget {
// all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem
lineWrapping: options.is('codeLineWrapEnabled'),
dragDrop: false, // with true the editor inlines dropped files which is not what we expect
placeholder: "Type the content of your code note here...",
placeholder: t('editable_code.placeholder'),
});
this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate());

View File

@ -1,3 +1,4 @@
import { t } from "../../services/i18n.js";
import libraryLoader from "../../services/library_loader.js";
import noteAutocompleteService from '../../services/note_autocomplete.js';
import mimeTypesService from '../../services/mime_types.js';
@ -167,7 +168,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
});
await this.watchdog.create(this.$editor[0], {
placeholder: "Type the content of your note here ...",
placeholder: t('editable_text.placeholder'),
mention: mentionSetup,
codeBlock: {
languages: codeBlockLanguages

View File

@ -2,6 +2,7 @@ import noteAutocompleteService from '../../services/note_autocomplete.js';
import TypeWidget from "./type_widget.js";
import appContext from "../../components/app_context.js";
import searchService from "../../services/search.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="note-detail-empty note-detail-printable">
@ -33,9 +34,9 @@ const TPL = `
</style>
<div class="form-group">
<label>Open a note by typing the note's title into the input below or choose a note in the tree.</label>
<label>${t('empty.open_note_instruction')}</label>
<div class="input-group">
<input class="form-control note-autocomplete" placeholder="search for a note by its name">
<input class="form-control note-autocomplete" placeholder="${t('empty.search_placeholder')}">
</div>
</div>
@ -78,7 +79,7 @@ export default class EmptyTypeWidget extends TypeWidget {
$('<div class="workspace-note">')
.append($("<div>").addClass(`${workspaceNote.getIcon()} workspace-icon`))
.append($("<div>").text(workspaceNote.title))
.attr("title", `Enter workspace ${workspaceNote.title}`)
.attr("title", t('empty.enter_workspace', { title: workspaceNote.title }))
.on('click', () => this.triggerCommand('hoistNote', {noteId: workspaceNote.noteId}))
);
}

View File

@ -1,5 +1,6 @@
import openService from "../../services/open.js";
import TypeWidget from "./type_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="note-detail-file note-detail-printable">
@ -25,7 +26,7 @@ const TPL = `
<pre class="file-preview-content"></pre>
<div class="file-preview-not-available alert alert-info">
File preview is not available for this file format.
${t('file.file_preview_not_available')}
</div>
<iframe class="pdf-preview" style="width: 100%; height: 100%; flex-grow: 100;"></iframe>

View File

@ -1,12 +1,13 @@
import OptionsWidget from "../options_widget.js";
import toastService from "../../../../services/toast.js";
import server from "../../../../services/server.js";
import { t } from "../../../../services/i18n.js";
const TPL = `
<div class="options-section">
<h4>Consistency Checks</h4>
<h4>${t("consistency_checks.title")}</h4>
<button class="find-and-fix-consistency-issues-button btn">Find and fix consistency issues</button>
<button class="find-and-fix-consistency-issues-button btn">${t("consistency_checks.find_and_fix_button")}</button>
</div>`;
export default class ConsistencyChecksOptions extends OptionsWidget {
@ -14,11 +15,11 @@ export default class ConsistencyChecksOptions extends OptionsWidget {
this.$widget = $(TPL);
this.$findAndFixConsistencyIssuesButton = this.$widget.find(".find-and-fix-consistency-issues-button");
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
toastService.showMessage("Finding and fixing consistency issues...");
toastService.showMessage(t("consistency_checks.finding_and_fixing_message"));
await server.post('database/find-and-fix-consistency-issues');
toastService.showMessage("Consistency issues should be fixed.");
toastService.showMessage(t("consistency_checks.issues_fixed_message"));
});
}
}

View File

@ -1,27 +1,27 @@
import OptionsWidget from "../options_widget.js";
import toastService from "../../../../services/toast.js";
import server from "../../../../services/server.js";
import { t } from "../../../../services/i18n.js";
const TPL = `
<div class="options-section">
<h4>Database Anonymization</h4>
<h4>${t("database_anonymization.title")}</h4>
<h5>Full Anonymization</h5>
<h5>${t("database_anonymization.full_anonymization")}</h5>
<p>This action will create a new copy of the database and anonymize it (remove all note content and leave only structure and some non-sensitive metadata)
for sharing online for debugging purposes without fear of leaking your personal data.</p>
<p>${t("database_anonymization.full_anonymization_description")}</p>
<button class="anonymize-full-button btn">Save fully anonymized database</button>
<button class="anonymize-full-button btn">${t("database_anonymization.save_fully_anonymized_database")}</button>
<h5>Light Anonymization</h5>
<h5>${t("database_anonymization.light_anonymization")}</h5>
<p>This action will create a new copy of the database and do a light anonymization on it specifically only content of all notes will be removed, but titles and attributes will remain. Additionally, custom JS frontend/backend script notes and custom widgets will remain. This provides more context to debug the issues.</p>
<p>${t("database_anonymization.light_anonymization_description")}</p>
<p>You can decide yourself if you want to provide a fully or lightly anonymized database. Even fully anonymized DB is very useful, however in some cases lightly anonymized database can speed up the process of bug identification and fixing.</p>
<p>${t("database_anonymization.choose_anonymization")}</p>
<button class="anonymize-light-button btn">Save lightly anonymized database</button>
<button class="anonymize-light-button btn">${t("database_anonymization.save_lightly_anonymized_database")}</button>
<h5>Existing anonymized databases</h5>
<h5>${t("database_anonymization.existing_anonymized_databases")}</h5>
<ul class="existing-anonymized-databases"></ul>
</div>`;
@ -32,30 +32,30 @@ export default class DatabaseAnonymizationOptions extends OptionsWidget {
this.$anonymizeFullButton = this.$widget.find(".anonymize-full-button");
this.$anonymizeLightButton = this.$widget.find(".anonymize-light-button");
this.$anonymizeFullButton.on('click', async () => {
toastService.showMessage("Creating fully anonymized database...");
toastService.showMessage(t("database_anonymization.creating_fully_anonymized_database"));
const resp = await server.post('database/anonymize/full');
if (!resp.success) {
toastService.showError("Could not create anonymized database, check backend logs for details");
toastService.showError(t("database_anonymization.error_creating_anonymized_database"));
}
else {
toastService.showMessage(`Created fully anonymized database in ${resp.anonymizedFilePath}`, 10000);
toastService.showMessage(t("database_anonymization.successfully_created_fully_anonymized_database", { anonymizedFilePath: resp.anonymizedFilePath }), 10000);
}
this.refresh();
});
this.$anonymizeLightButton.on('click', async () => {
toastService.showMessage("Creating lightly anonymized database...");
toastService.showMessage(t("database_anonymization.creating_lightly_anonymized_database"));
const resp = await server.post('database/anonymize/light');
if (!resp.success) {
toastService.showError("Could not create anonymized database, check backend logs for details");
toastService.showError(t("database_anonymization.error_creating_anonymized_database"));
}
else {
toastService.showMessage(`Created lightly anonymized database in ${resp.anonymizedFilePath}`, 10000);
toastService.showMessage(t("database_anonymization.successfully_created_lightly_anonymized_database", { anonymizedFilePath: resp.anonymizedFilePath }), 10000);
}
this.refresh();
@ -69,7 +69,7 @@ export default class DatabaseAnonymizationOptions extends OptionsWidget {
this.$existingAnonymizedDatabases.empty();
if (!anonymizedDatabases.length) {
anonymizedDatabases = [{filePath: "no anonymized database yet"}];
anonymizedDatabases = [{filePath: t("database_anonymization.no_anonymized_database_yet")}];
}
for (const {filePath} of anonymizedDatabases) {

View File

@ -1,14 +1,15 @@
import OptionsWidget from "../options_widget.js";
import toastService from "../../../../services/toast.js";
import server from "../../../../services/server.js";
import { t } from "../../../../services/i18n.js";
const TPL = `
<div class="options-section">
<h4>Database Integrity Check</h4>
<h4>${t("database_integrity_check.title")}</h4>
<p>This will check that the database is not corrupted on the SQLite level. It might take some time, depending on the DB size.</p>
<p>${t("database_integrity_check.description")}</p>
<button class="check-integrity-button btn">Check database integrity</button>
<button class="check-integrity-button btn">${t("database_integrity_check.check_button")}</button>
</div>`;
export default class DatabaseIntegrityCheckOptions extends OptionsWidget {
@ -16,15 +17,15 @@ export default class DatabaseIntegrityCheckOptions extends OptionsWidget {
this.$widget = $(TPL);
this.$checkIntegrityButton = this.$widget.find(".check-integrity-button");
this.$checkIntegrityButton.on('click', async () => {
toastService.showMessage("Checking database integrity...");
toastService.showMessage(t("database_integrity_check.checking_integrity"));
const {results} = await server.get('database/check-integrity');
if (results.length === 1 && results[0].integrity_check === "ok") {
toastService.showMessage("Integrity check succeeded - no problems found.");
toastService.showMessage(t("database_integrity_check.integrity_check_succeeded"));
}
else {
toastService.showMessage(`Integrity check failed: ${JSON.stringify(results, null, 2)}`, 15000);
toastService.showMessage(t("database_integrity_check.integrity_check_failed", { results: JSON.stringify(results, null, 2) }), 15000);
}
});
}

View File

@ -1,13 +1,14 @@
import OptionsWidget from "../options_widget.js";
import server from "../../../../services/server.js";
import toastService from "../../../../services/toast.js";
import { t } from "../../../../services/i18n.js";
const TPL = `
<div class="options-section">
<h4>Sync</h4>
<button class="force-full-sync-button btn">Force full sync</button>
<h4>${t("sync.title")}</h4>
<button class="force-full-sync-button btn">${t("sync.force_full_sync_button")}</button>
<button class="fill-entity-changes-button btn">Fill entity changes records</button>
<button class="fill-entity-changes-button btn">${t("sync.fill_entity_changes_button")}</button>
</div>`;
export default class AdvancedSyncOptions extends OptionsWidget {
@ -18,15 +19,15 @@ export default class AdvancedSyncOptions extends OptionsWidget {
this.$forceFullSyncButton.on('click', async () => {
await server.post('sync/force-full-sync');
toastService.showMessage("Full sync triggered");
toastService.showMessage(t("sync.full_sync_triggered"));
});
this.$fillEntityChangesButton.on('click', async () => {
toastService.showMessage("Filling entity changes rows...");
toastService.showMessage(t("sync.filling_entity_changes"));
await server.post('sync/fill-entity-changes');
toastService.showMessage("Sync rows filled successfully");
toastService.showMessage(t("sync.sync_rows_filled_successfully"));
});
}

View File

@ -1,14 +1,15 @@
import OptionsWidget from "../options_widget.js";
import toastService from "../../../../services/toast.js";
import server from "../../../../services/server.js";
import { t } from "../../../../services/i18n.js";
const TPL = `
<div class="options-section">
<h4>Vacuum Database</h4>
<h4>${t("vacuum_database.title")}</h4>
<p>This will rebuild the database which will typically result in a smaller database file. No data will be actually changed.</p>
<p>${t("vacuum_database.description")}</p>
<button class="vacuum-database-button btn">Vacuum database</button>
<button class="vacuum-database-button btn">${t("vacuum_database.button_text")}</button>
</div>`;
export default class VacuumDatabaseOptions extends OptionsWidget {
@ -16,11 +17,11 @@ export default class VacuumDatabaseOptions extends OptionsWidget {
this.$widget = $(TPL);
this.$vacuumDatabaseButton = this.$widget.find(".vacuum-database-button");
this.$vacuumDatabaseButton.on('click', async () => {
toastService.showMessage("Vacuuming database...");
toastService.showMessage(t("vacuum_database.vacuuming_database"));
await server.post('database/vacuum-database');
toastService.showMessage("Database has been vacuumed");
toastService.showMessage(t("vacuum_database.database_vacuumed"));
});
}
}

View File

@ -1,8 +1,9 @@
import OptionsWidget from "../options_widget.js";
import utils from "../../../../services/utils.js";
import { t } from "../../../../services/i18n.js";
const FONT_FAMILIES = [
{ value: "theme", label: "Theme defined" },
{ value: "theme", label: t("fonts.theme_defined") },
{ value: "serif", label: "Serif" },
{ value: "sans-serif", label: "Sans Serif" },
{ value: "monospace", label: "Monospace" },
@ -29,18 +30,18 @@ const FONT_FAMILIES = [
const TPL = `
<div class="options-section">
<h4>Fonts</h4>
<h4>${t("fonts.fonts")}</h4>
<h5>Main Font</h5>
<h5>${t("fonts.main_font")}</h5>
<div class="form-group row">
<div class="col-6">
<label>Font family</label>
<label>${t("fonts.font_family")}</label>
<select class="main-font-family form-control"></select>
</div>
<div class="col-6">
<label>Size</label>
<label>${t("fonts.size")}</label>
<div class="input-group">
<input type="number" class="main-font-size form-control options-number-input" min="50" max="200" step="10"/>
@ -51,16 +52,16 @@ const TPL = `
</div>
</div>
<h5>Note Tree Font</h5>
<h5>${t("fonts.note_tree_font")}</h5>
<div class="form-group row">
<div class="col-4">
<label>Font family</label>
<label>${t("fonts.font_family")}</label>
<select class="tree-font-family form-control"></select>
</div>
<div class="col-4">
<label>Size</label>
<div class="col-6">
<label>${t("fonts.size")}</label>
<div class="input-group">
<input type="number" class="tree-font-size form-control options-number-input" min="50" max="200" step="10"/>
@ -71,16 +72,16 @@ const TPL = `
</div>
</div>
<h5>Note Detail Font</h5>
<h5>${t("fonts.note_detail_font")}</h5>
<div class="form-group row">
<div class="col-4">
<label>Font family</label>
<label>${t("fonts.font_family")}</label>
<select class="detail-font-family form-control"></select>
</div>
<div class="col-4">
<label>Size</label>
<div class="col-6">
<label>${t("fonts.size")}</label>
<div class="input-group">
<input type="number" class="detail-font-size form-control options-number-input" min="50" max="200" step="10"/>
@ -91,16 +92,16 @@ const TPL = `
</div>
</div>
<h5>Monospace (code) Font</h5>
<h5>${t("fonts.monospace_font")}</h5>
<div class="form-group row">
<div class="col-4">
<label>Font family</label>
<label>${t("fonts.font_family")}</label>
<select class="monospace-font-family form-control"></select>
</div>
<div class="col-4">
<label>Size</label>
<div class="col-6">
<label>${t("fonts.size")}</label>
<div class="input-group">
<input type="number" class="monospace-font-size form-control options-number-input" min="50" max="200" step="10"/>
@ -111,13 +112,13 @@ const TPL = `
</div>
</div>
<p>Note that tree and detail font sizing is relative to the main font size setting.</p>
<p>${t("fonts.note_tree_and_detail_font_sizing")}</p>
<p>Not all listed fonts may be available on your system.</p>
<p>${t("fonts.not_all_fonts_available")}</p>
<p>
To apply font changes, click on
<button class="btn btn-micro reload-frontend-button">reload frontend</button>
${t("fonts.apply_font_changes")}
<button class="btn btn-micro reload-frontend-button">${t("fonts.reload_frontend")}</button>
</p>
</div>`;

View File

@ -1,22 +1,23 @@
import OptionsWidget from "../options_widget.js";
import utils from "../../../../services/utils.js";
import { t } from "../../../../services/i18n.js";
const TPL = `
<div class="options-section">
<h4>Content Width</h4>
<h4>${t("max_content_width.title")}</h4>
<p>Trilium by default limits max content width to improve readability for maximized screens on wide screens.</p>
<p>${t("max_content_width.default_description")}</p>
<div class="form-group row">
<div class="col-4">
<label>Max content width in pixels</label>
<label>${t("max_content_width.max_width_label")}</label>
<input type="number" min="200" step="10" class="max-content-width form-control options-number-input">
</div>
</div>
<p>
To apply content width changes, click on
<button class="btn btn-micro reload-frontend-button">reload frontend</button>
${t("max_content_width.apply_changes_description")}
<button class="btn btn-micro reload-frontend-button">${t("max_content_width.reload_button")}</button>
</p>
</div>`;
@ -29,7 +30,7 @@ export default class MaxContentWidthOptions extends OptionsWidget {
this.$maxContentWidth.on('change', async () =>
this.updateOption('maxContentWidth', this.$maxContentWidth.val()))
this.$widget.find(".reload-frontend-button").on("click", () => utils.reloadFrontendApp("changes from appearance options"));
this.$widget.find(".reload-frontend-button").on("click", () => utils.reloadFrontendApp(t("max_content_width.reload_description")));
}
async optionsLoaded(options) {

Some files were not shown because too many files have changed in this diff Show More