Merge branch 'develop' of ssh://github.com/TriliumNext/Notes into develop

This commit is contained in:
Elian Doran 2024-10-13 13:52:39 +03:00
commit e7378306a2
No known key found for this signature in database
34 changed files with 1790 additions and 10751 deletions

126
.github/workflows/nightly.yml vendored Normal file
View File

@ -0,0 +1,126 @@
name: Nightly Release
on:
# This can be used to automatically publish nightlies at UTC nighttime
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
# This can be used to allow manually triggering nightlies from the web interface
workflow_dispatch:
env:
GITHUB_UPLOAD_URL: https://uploads.github.com/repos/TriliumNext/Notes/releases/179589950/assets{?name,label}
GITHUB_RELEASE_ID: 179589950
permissions:
contents: write
jobs:
nightly-electron:
name: Deploy nightly
strategy:
fail-fast: false
matrix:
arch: [x64, arm64]
os:
- name: macos
image: macos-latest
extension: dmg
- name: linux
image: ubuntu-latest
extension: deb
- name: windows
image: windows-latest
extension: exe
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Python for appdmg to be installed
if: ${{ matrix.os.name == 'macos' }}
run: brew install python-setuptools
- name: Install dependencies
run: npm ci
- name: Update build info
run: npm run update-build-info
- name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix)
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find out/make -name '*.zip' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Prepare artifacts (Windows)
if: runner.os == 'windows'
run: |
mkdir upload
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Publish artifacts
uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.zip
overwrite: true
- name: Publish installer artifacts
uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.${{ matrix.os.extension }}
overwrite: true
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip # path to archive to upload
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
- name: Deploy installer release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }} # path to archive to upload
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.${{ matrix.os.extension }} # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
nightly-server:
name: Deploy server nightly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Linux server build (x86_64)
run: |
npm run update-build-info
./bin/build-server.sh
- name: Prepare artifacts
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find dist -name '*.tar.xz' -print -quit)
cp "$file" "upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz"
- uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes linux server x64
path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz
overwrite: true
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz # path to archive to upload
asset_name: TriliumNextNotes-linux-x64-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API

View File

@ -18,4 +18,7 @@
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"github-actions.workflows.pinned.workflows": [
".github/workflows/nightly.yml"
],
}

View File

@ -24,11 +24,12 @@ rm -r $PKG_DIR/node/include/node
rm -r $PKG_DIR/node_modules/electron*
rm -r $PKG_DIR/electron*.js
printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh
printf "#!/bin/sh\n./node/bin/node src/main" > $PKG_DIR/trilium.sh
chmod 755 $PKG_DIR/trilium.sh
cp bin/tpl/anonymize-database.sql $PKG_DIR/
cp -r translations $PKG_DIR/
cp -r dump-db $PKG_DIR/
rm -rf $PKG_DIR/dump-db/node_modules

View File

@ -35,7 +35,7 @@ const copy = async () => {
await fs.copy(file, path.join(DEST_DIR, file));
}
const dirsToCopy = ["images", "libraries", "db"];
const dirsToCopy = ["images", "libraries", "translations", "db"];
for (const dir of dirsToCopy) {
log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR, dir));

View File

@ -75,7 +75,11 @@ module.exports = {
function getExtraResourcesForPlatform() {
let resources = ['dump-db/', './bin/tpl/anonymize-database.sql']
let resources = [
'dump-db/',
'./bin/tpl/anonymize-database.sql',
'translations/'
];
const scripts = ['trilium-portable', 'trilium-safe-mode', 'trilium-no-cert-check']
switch (process.platform) {
case 'win32':

File diff suppressed because it is too large Load Diff

700
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -61,7 +61,7 @@
"boxicons": "2.1.4",
"chokidar": "3.6.0",
"cls-hooked": "4.2.2",
"codemirror": "5.65.17",
"codemirror": "5.65.18",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"csurf": "1.11.0",
@ -86,9 +86,9 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.5",
"i18next": "23.15.1",
"i18next": "23.15.2",
"i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.1",
"i18next-http-backend": "2.6.2",
"image-type": "4.1.0",
"ini": "5.0.0",
"is-animated": "2.0.2",
@ -104,9 +104,9 @@
"knockout": "3.5.1",
"mark.js": "8.11.1",
"marked": "14.1.2",
"mermaid": "11.2.0",
"mermaid": "11.3.0",
"mime-types": "2.1.35",
"mind-elixir": "4.1.1",
"mind-elixir": "4.1.5",
"multer": "1.4.5-lts.1",
"node-abi": "3.67.0",
"normalize-strings": "1.1.1",
@ -138,12 +138,12 @@
"yauzl": "3.1.3"
},
"devDependencies": {
"@electron-forge/cli": "7.4.0",
"@electron-forge/maker-deb": "7.4.0",
"@electron-forge/maker-dmg": "7.4.0",
"@electron-forge/maker-squirrel": "7.4.0",
"@electron-forge/maker-zip": "7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "7.4.0",
"@electron-forge/cli": "7.5.0",
"@electron-forge/maker-deb": "7.5.0",
"@electron-forge/maker-dmg": "7.5.0",
"@electron-forge/maker-squirrel": "7.5.0",
"@electron-forge/maker-zip": "7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "7.5.0",
"@playwright/test": "1.47.1",
"@types/archiver": "6.0.2",
"@types/better-sqlite3": "7.6.11",
@ -182,17 +182,17 @@
"electron-rebuild": "3.2.9",
"esm": "3.2.25",
"iconsur": "1.7.0",
"jasmine": "5.3.0",
"jasmine": "5.3.1",
"jsdoc": "4.0.3",
"lorem-ipsum": "2.0.8",
"nodemon": "3.1.4",
"nodemon": "3.1.7",
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
"tslib": "2.7.0",
"tsx": "4.19.1",
"typescript": "5.6.2",
"webpack": "5.94.0",
"typescript": "5.6.3",
"webpack": "5.95.0",
"webpack-cli": "5.1.4"
}
}

View File

@ -53,6 +53,7 @@ app.use(cookieParser());
app.use(express.static(path.join(scriptDir, 'public/root')));
app.use(`/manifest.webmanifest`, express.static(path.join(scriptDir, 'public/manifest.webmanifest')));
app.use(`/robots.txt`, express.static(path.join(scriptDir, 'public/robots.txt')));
app.use(`/icon.png`, express.static(path.join(scriptDir, 'public/icon.png')));
app.use(sessionParser);
app.use(favicon(`${scriptDir}/../images/app-icons/icon.ico`));

View File

@ -3,6 +3,8 @@
import protectedSessionService from "../../services/protected_session.js";
import log from "../../services/log.js";
import sql from "../../services/sql.js";
import optionService from "../../services/options.js";
import eraseService from "../../services/erase.js";
import utils from "../../services/utils.js";
import dateUtils from "../../services/date_utils.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
@ -68,7 +70,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
/** set during the deletion operation, before it is completed (removed from becca completely). */
isBeingDeleted!: boolean;
isDecrypted!: boolean;
ownedAttributes!: BAttribute[];
parentBranches!: BBranch[];
parents!: BNote[];
@ -455,8 +457,8 @@ class BNote extends AbstractBeccaEntity<BNote> {
return this.getAttributes().find(
attr => attr.name.toLowerCase() === name
&& (!value || attr.value.toLowerCase() === value)
&& attr.type === type);
&& (!value || attr.value.toLowerCase() === value)
&& attr.type === type);
}
getRelationTarget(name: string) {
@ -1107,7 +1109,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
}
getRevisions(): BRevision[] {
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ? ORDER BY revisions.utcDateCreated ASC", [this.noteId])
.map(row => new BRevision(row));
}
@ -1612,10 +1614,31 @@ class BNote extends AbstractBeccaEntity<BNote> {
revision.setContent(noteContent);
this.eraseExcessRevisionSnapshots()
return revision;
});
}
// Limit the number of Snapshots to revisionSnapshotNumberLimit
// Delete older Snapshots that exceed the limit
eraseExcessRevisionSnapshots() {
// lable has a higher priority
let revisionSnapshotNumberLimit = parseInt(this.getLabelValue("versioningLimit") ?? "");
if (!Number.isInteger(revisionSnapshotNumberLimit)) {
revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit'));
}
if (revisionSnapshotNumberLimit >= 0) {
const revisions = this.getRevisions();
if (revisions.length - revisionSnapshotNumberLimit > 0) {
const revisionIds = revisions
.slice(0, revisions.length - revisionSnapshotNumberLimit)
.map(revision => revision.revisionId)
.filter((id): id is string => id !== undefined);
eraseService.eraseRevisions(revisionIds);
}
}
}
/**
* @param matchBy - choose by which property we detect if to update an existing attachment.
* Supported values are either 'attachmentId' (default) or 'title'

View File

@ -22,15 +22,31 @@ function setupContextMenu($image) {
command: "copyImageReferenceToClipboard",
uiIcon: "bx bx-empty"
},
{title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty"},
{ title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty" },
],
selectMenuItemHandler: ({command}) => {
selectMenuItemHandler: async ({ command }) => {
if (command === 'copyImageReferenceToClipboard') {
imageService.copyImageReferenceToClipboard($image);
} else if (command === 'copyImageToClipboard') {
const webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
utils.dynamicRequire('electron');
webContents.copyImageAt(e.pageX, e.pageY);
try {
const nativeImage = utils.dynamicRequire('electron').nativeImage;
const clipboard = utils.dynamicRequire('electron').clipboard;
const response = await fetch(
$image.attr('src')
);
const blob = await response.blob();
clipboard.writeImage(
nativeImage.createFromBuffer(
Buffer.from(
await blob.arrayBuffer()
)
)
);
} catch (error) {
console.error('Failed to copy image to clipboard:', error);
}
} else {
throw new Error(`Unrecognized command '${command}'`);
}
@ -41,4 +57,4 @@ function setupContextMenu($image) {
export default {
setupContextMenu
};
};

View File

@ -2,8 +2,7 @@ const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
const CODE_MIRROR = {
js: [
"libraries/codemirror/codemirror.js",
// "node_modules/codemirror/lib/codemirror.js",
"node_modules/codemirror/lib/codemirror.js",
"node_modules/codemirror/addon/display/placeholder.js",
"node_modules/codemirror/addon/edit/matchbrackets.js",
"node_modules/codemirror/addon/edit/matchtags.js",

View File

@ -8,6 +8,7 @@ import openService from "../../services/open.js";
import protectedSessionHolder from "../../services/protected_session_holder.js";
import BasicWidget from "../basic_widget.js";
import dialogService from "../../services/dialog.js";
import options from "../../services/options.js";
const TPL = `
<div class="revisions-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@ -66,6 +67,11 @@ const TPL = `
<div class="revision-content"></div>
</div>
</div>
<div class="modal-footer py-0">
<span class="revisions-snapshot-interval flex-grow-1 my-0 py-0"></span>
<span class="maximum-revisions-for-current-note flex-grow-1 my-0 py-0"></span>
<button class="revision-settings-button icon-action bx bx-cog my-0 py-0" title="${t("revisions.settings")}"></button>
</div>
</div>
</div>
</div>`;
@ -85,20 +91,29 @@ export default class RevisionsDialog extends BasicWidget {
this.$list = this.$widget.find(".revision-list");
this.$listDropdown = this.$widget.find(".revision-list-dropdown");
this.listDropdown = bootstrap.Dropdown.getOrCreateInstance(this.$listDropdown);
this.$content = this.$widget.find(".revision-content");
this.$title = this.$widget.find(".revision-title");
this.$titleButtons = this.$widget.find(".revision-title-buttons");
this.$eraseAllRevisionsButton = this.$widget.find(".revisions-erase-all-revisions-button");
this.$listDropdown.dropdown();
this.$snapshotInterval = this.$widget.find(".revisions-snapshot-interval");
this.$maximumRevisions = this.$widget.find(".maximum-revisions-for-current-note");
this.$revisionSettingsButton = this.$widget.find(".revision-settings-button")
this.listDropdown.show();
this.$listDropdown.parent().on('hide.bs.dropdown', e => {
// prevent closing dropdown by clicking outside
if (e.clickEvent) {
e.preventDefault();
}
// Prevent closing dropdown by pressing ESC and clicking outside
e.preventDefault();
});
document.addEventListener('keydown', e => {
// Close the revision dialog when revision element is focused and ESC is pressed
if (e.key === 'Escape' ||
e.target.classList.contains(['dropdown-item', 'active'])) {
this.modal.hide();
}
}, true)
this.$widget.on('shown.bs.modal', () => {
this.$list.find(`[data-revision-id="${this.revisionId}"]`)
.trigger('focus');
@ -116,11 +131,6 @@ export default class RevisionsDialog extends BasicWidget {
}
});
this.$list.on('click', '.dropdown-item', e => {
e.preventDefault();
return false;
});
this.$list.on('focus', '.dropdown-item', e => {
this.$list.find('.dropdown-item').each((i, el) => {
$(el).toggleClass('active', el === e.target);
@ -128,6 +138,10 @@ export default class RevisionsDialog extends BasicWidget {
this.setContentPane();
});
this.$revisionSettingsButton.on('click', async () => {
appContext.tabManager.openContextWithNote('_optionsOther', { activate: true });
});
}
async showRevisionsEvent({ noteId = appContext.tabManager.getActiveContextNoteId() }) {
@ -153,7 +167,7 @@ export default class RevisionsDialog extends BasicWidget {
);
}
this.$listDropdown.dropdown('show');
this.listDropdown.show();
if (this.revisionItems.length > 0) {
if (!this.revisionId) {
@ -165,6 +179,17 @@ export default class RevisionsDialog extends BasicWidget {
}
this.$eraseAllRevisionsButton.toggle(this.revisionItems.length > 0);
// Show the footer of the revisions dialog
this.$snapshotInterval.text(t("revisions.snapshot_interval", { seconds: options.getInt('revisionSnapshotTimeInterval') }))
let revisionsNumberLimit = parseInt(this.note.getLabelValue("versioningLimit") ?? "");
if (!Number.isInteger(revisionsNumberLimit)) {
revisionsNumberLimit = parseInt(options.getInt('revisionSnapshotNumberLimit'));
}
if (revisionsNumberLimit === -1) {
revisionsNumberLimit = "∞"
}
this.$maximumRevisions.text(t("revisions.maximum_revisions", { number: revisionsNumberLimit }))
}
async setContentPane() {
@ -245,12 +270,20 @@ export default class RevisionsDialog extends BasicWidget {
} else if (revisionItem.type === 'code') {
this.$content.html($("<pre>").text(fullRevision.content));
} else if (revisionItem.type === 'image') {
this.$content.html($("<img>")
// the reason why we put this inline as base64 is that we do not want to let user copy this
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
.css("max-width", "100%")
.css("max-height", "100%"));
if (fullRevision.mime === "image/svg+xml") {
let encodedSVG = encodeURIComponent(fullRevision.content); //Base64 of other format images may be embedded in svg
this.$content.html($("<img>")
.attr("src", `data:${fullRevision.mime};utf8,${encodedSVG}`)
.css("max-width", "100%")
.css("max-height", "100%"));
} else {
this.$content.html($("<img>")
// the reason why we put this inline as base64 is that we do not want to let user copy this
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
.css("max-width", "100%")
.css("max-height", "100%"));
}
} else if (revisionItem.type === 'file') {
const $table = $("<table cellpadding='10'>")
.append($("<tr>").append(

View File

@ -23,6 +23,7 @@ import SearchEngineOptions from "./options/other/search_engine.js";
import TrayOptions from "./options/other/tray.js";
import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js";
import RevisionsSnapshotIntervalOptions from "./options/other/revisions_snapshot_interval.js";
import RevisionSnapshotsLimitOptions from "./options/other/revision_snapshots_limit.js";
import NetworkConnectionsOptions from "./options/other/network_connections.js";
import AdvancedSyncOptions from "./options/advanced/sync.js";
import DatabaseIntegrityCheckOptions from "./options/advanced/database_integrity_check.js";
@ -88,6 +89,7 @@ const CONTENT_WIDGETS = {
NoteErasureTimeoutOptions,
AttachmentErasureTimeoutOptions,
RevisionsSnapshotIntervalOptions,
RevisionSnapshotsLimitOptions,
NetworkConnectionsOptions
],
_optionsAdvanced: [

View File

@ -0,0 +1,42 @@
import OptionsWidget from "../options_widget.js";
import { t } from "../../../../services/i18n.js";
import server from "../../../../services/server.js";
import toastService from "../../../../services/toast.js";
const TPL = `
<div class="options-section">
<h4>${t("revisions_snapshot_limit.note_revisions_snapshot_limit_title")}</h4>
<p>${t("revisions_snapshot_limit.note_revisions_snapshot_limit_description")}</p>
<div class="form-group">
<label>${t("revisions_snapshot_limit.snapshot_number_limit_label")}</label>
<input class="revision-snapshot-number-limit form-control options-number-input" type="number" min="-1">
</div>
<button class="erase-excess-revision-snapshots-now-button btn btn-sm" style="padding: 0 10px">
${t('revisions_snapshot_limit.erase_excess_revision_snapshots')}</button>
</div>`;
export default class RevisionSnapshotsLimitOptions extends OptionsWidget {
doRender() {
this.$widget = $(TPL);
this.$revisionSnapshotsNumberLimit = this.$widget.find(".revision-snapshot-number-limit");
this.$revisionSnapshotsNumberLimit.on('change', () => {
let revisionSnapshotNumberLimit = this.$revisionSnapshotsNumberLimit.val();
if (!isNaN(revisionSnapshotNumberLimit) && revisionSnapshotNumberLimit >= -1) {
this.updateOption('revisionSnapshotNumberLimit', revisionSnapshotNumberLimit)
}
});
this.$eraseExcessRevisionSnapshotsButton = this.$widget.find(".erase-excess-revision-snapshots-now-button");
this.$eraseExcessRevisionSnapshotsButton.on('click', () => {
server.post('revisions/erase-all-excess-revisions').then(() => {
toastService.showMessage(t("revisions_snapshot_limit.erase_excess_revision_snapshots_prompt"));
});
});
}
async optionsLoaded(options) {
this.$revisionSnapshotsNumberLimit.val(options.revisionSnapshotNumberLimit);
}
}

BIN
src/public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -9,9 +9,9 @@
"start_url": "/",
"icons": [
{
"src": "favicon.ico",
"sizes": "180x180 512x512",
"type": "image/x-icon"
"src": "icon.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@ -15,6 +15,10 @@
src: url(../fonts/JetBrainsMono-Light.woff2) format('woff');
}
.table {
--bs-table-bg: transparent !important;
}
html {
/* this fixes FF filter vs. position fixed bug: https://github.com/zadam/trilium/issues/233 */
height: 100%;
@ -460,6 +464,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
.tooltip {
font-size: var(--main-font-size) !important;
z-index: calc(var(--ck-z-panel) - 1) !important;
}
.tooltip-trigger {
@ -960,14 +965,6 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
white-space: normal !important;
}
/**
ckeditor's autocomplete
should be higher than 1070 of tooltip
*/
.ck.ck-balloon-panel {
z-index: 1101;
}
.area-expander {
display: flex;
flex-direction: row;

View File

@ -1269,7 +1269,7 @@
"note_is_read_only": "笔记为只读,但可以通过点击按钮进行编辑。",
"note_is_always_editable": "无论笔记长度如何,始终可编辑。"
},
"note-map": {
"note-map": {
"button-link-map": "链接地图",
"button-tree-map": "树形地图"
},

View File

@ -247,6 +247,9 @@
"revisions_deleted": "Note revisions has been deleted.",
"revision_restored": "Note revision has been restored.",
"revision_deleted": "Note revision has been deleted.",
"snapshot_interval": "Note Revisions Snapshot Interval: {{seconds}}s.",
"maximum_revisions": "Maximum revisions for current note: {{number}}.",
"settings": "Settings for Note revisions",
"download_button": "Download",
"mime": "MIME: ",
"file_size": "File size:",
@ -1088,7 +1091,14 @@
"revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "Note Revisions Snapshot Interval",
"note_revisions_snapshot_description": "Note revision snapshot time interval is time in seconds after which a new note revision will be created for the note. See <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> for more info.",
"snapshot_time_interval_label": "Note revision snapshot time interval (in seconds)"
"snapshot_time_interval_label": "Note revision snapshot time interval (in seconds):"
},
"revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "Note Revision Snapshots Limit",
"note_revisions_snapshot_limit_description": "The note revision snapshot number limit refers to the maximum number of revisions that can be saved for each note. Where -1 means no limit, 0 means delete all revisions. You can set the maximum revisions for a single note through the #versioningLimit label.",
"snapshot_number_limit_label": "Note revision snapshot number limit:",
"erase_excess_revision_snapshots": "Erase excess revision snapshots now",
"erase_excess_revision_snapshots_prompt": "Excess revision snapshots have been erased."
},
"search_engine": {
"title": "Search Engine",

View File

@ -1189,7 +1189,7 @@
"password": {
"heading": "Contraseña",
"alert_message": "Tenga cuidado de recordar su nueva contraseña. La contraseña se utiliza para iniciar sesión en la interfaz web y cifrar las notas protegidas. Si olvida su contraseña, todas sus notas protegidas se perderán para siempre.",
"reset_link": "haga clic aquí para restablecerla.",
"reset_link": " clic aquí para restablecerla.",
"old_password": "Contraseña anterior",
"new_password": "Nueva contraseña",
"new_password_confirmation": "Confirmación de nueva contraseña",

View File

@ -998,7 +998,10 @@
"revision_deleted": "Revizia notiței a fost ștearsă.",
"revision_last_edited": "Revizia a fost ultima oară modificată pe {{date}}",
"revision_restored": "Revizia notiței a fost restaurată.",
"revisions_deleted": "Notița reviziei a fost ștearsă."
"revisions_deleted": "Notița reviziei a fost ștearsă.",
"maximum_revisions": "Numărul maxim de revizii pentru notița curentă: {{number}}.",
"settings": "Setări revizii ale notițelor",
"snapshot_interval": "Intervalul de creare a reviziilor pentru notițe: {{seconds}}s."
},
"revisions_button": {
"note_revisions": "Revizii ale notiței"
@ -1376,6 +1379,13 @@
"note_title": {
"placeholder": "introduceți titlul notiței aici..."
},
"revisions_snapshot_limit": {
"erase_excess_revision_snapshots": "Șterge acum reviziile excesive",
"erase_excess_revision_snapshots_prompt": "Reviziile excesive au fost șterse.",
"note_revisions_snapshot_limit_description": "Limita numărului de revizii se referă la numărul maxim de revizii pentru fiecare notiță. -1 reprezintă nicio limită, 0 înseamnă ștergerea tuturor reviziilor. Se poate seta valoarea individual pentru o notiță prin eticheta #versioningLimit.",
"note_revisions_snapshot_limit_title": "Limita de revizii a notițelor",
"snapshot_number_limit_label": "Numărul maxim de revizii pentru notițe:"
},
"search_result": {
"no_notes_found": "Nu au fost găsite notițe pentru parametrii de căutare dați.",
"search_not_executed": "Căutarea n-a fost rulată încă. Clic pe butonul „Căutare” de deasupra pentru a vedea rezultatele."
@ -1387,7 +1397,7 @@
"configure_launchbar": "Configurează bara de lansare"
},
"sql_result": {
"no_rows": "Niciun rând nu a fost identificat pentru această interogare"
"no_rows": "Nu s-a găsit niciun rând pentru această interogare"
},
"sql_table_schemas": {
"tables": "Tabele"

View File

@ -11,6 +11,7 @@ const ALLOWED_OPTIONS = new Set([
'eraseEntitiesAfterTimeInSeconds',
'protectedSessionTimeout',
'revisionSnapshotTimeInterval',
'revisionSnapshotNumberLimit',
'zoomFactor',
'theme',
'syncServerHost',

View File

@ -112,6 +112,13 @@ function eraseRevision(req: Request) {
eraseService.eraseRevisions([req.params.revisionId]);
}
function eraseAllExcessRevisions() {
let allNoteIds = sql.getRows("SELECT noteId FROM notes WHERE SUBSTRING(noteId, 1, 1) != '_'") as { noteId: string }[];
allNoteIds.forEach(row => {
becca.getNote(row.noteId)?.eraseExcessRevisionSnapshots()
});
}
function restoreRevision(req: Request) {
const revision = becca.getRevision(req.params.revisionId);
@ -139,6 +146,8 @@ function restoreRevision(req: Request) {
}
note.title = revision.title;
note.mime = revision.mime;
note.type = revision.type as any;
note.setContent(revisionContent, { forceSave: true });
});
}
@ -211,6 +220,7 @@ export default {
downloadRevision,
getEditedNotesOnDate,
eraseAllRevisions,
eraseAllExcessRevisions,
eraseRevision,
restoreRevision
};

View File

@ -184,6 +184,7 @@ function register(app: express.Application) {
apiRoute(GET, '/api/notes/:noteId/revisions', revisionsApiRoute.getRevisions);
apiRoute(DEL, '/api/notes/:noteId/revisions', revisionsApiRoute.eraseAllRevisions);
apiRoute(PST, '/api/revisions/erase-all-excess-revisions', revisionsApiRoute.eraseAllExcessRevisions);
apiRoute(GET, '/api/revisions/:revisionId', revisionsApiRoute.getRevision);
apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);

View File

@ -6,7 +6,7 @@ import sql_init from "./sql_init.js";
export async function initializeTranslations() {
// Initialize translations
await i18next.use(Backend).init({
lng: await getCurrentLanguage(),
lng: getCurrentLanguage(),
fallbackLng: "en",
ns: "server",
backend: {
@ -18,7 +18,7 @@ export async function initializeTranslations() {
function getCurrentLanguage() {
let language;
if (sql_init.isDbInitialized()) {
language = options.getOption("locale");
language = options.getOptionOrNull("locale");
}
if (!language) {

View File

@ -456,13 +456,12 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo
return;
}
let { mime } = noteMeta ? noteMeta : detectFileTypeAndMime(taskContext, filePath);
let { mime, type: detectedType } = noteMeta ? noteMeta : detectFileTypeAndMime(taskContext, filePath);
const type = resolveNoteType(detectedType);
if (mime == null) {
throw new Error("Unable to resolve mime type.");
}
let type = resolveNoteType(noteMeta?.type);
if (type !== 'file' && type !== 'image') {
content = content.toString("utf-8");
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,15 @@ export interface KeyboardShortcut {
description?: string;
defaultShortcuts?: string[];
effectiveShortcuts?: string[];
scope?: string;
/**
* Scope here means on which element the keyboard shortcuts are attached - this means that for the shortcut to work,
* the focus has to be inside the element.
*
* So e.g. shortcuts with "note-tree" scope work only when the focus is in note tree.
* This allows to have the same shortcut have different actions attached based on the context
* e.g. CTRL-C in note tree does something a bit different from CTRL-C in the text editor.
*/
scope?: "window" | "note-tree" | "text-detail" | "code-detail";
}
export interface KeyboardShortcutWithRequiredActionName extends KeyboardShortcut {

View File

@ -49,6 +49,7 @@ async function initNotSyncedOptions(initialized: boolean, theme: string, opts: N
const defaultOptions: DefaultOption[] = [
{ name: 'revisionSnapshotTimeInterval', value: '600', isSynced: true },
{ name: 'revisionSnapshotNumberLimit', value: '-1', isSynced: true },
{ name: 'protectedSessionTimeout', value: '600', isSynced: true },
{ name: 'zoomFactor', value: process.platform === "win32" ? '0.9' : '1.0', isSynced: false },
{ name: 'overrideThemeFonts', value: 'false', isSynced: false },
@ -125,7 +126,7 @@ function initStartupOptions() {
}
function getKeyboardDefaultOptions() {
return (keyboardActions.DEFAULT_KEYBOARD_ACTIONS
return (keyboardActions.getDefaultKeyboardActions()
.filter(ka => !!ka.actionName) as KeyboardShortcutWithRequiredActionName[])
.map(ka => ({
name: `keyboardShortcuts${ka.actionName.charAt(0).toUpperCase()}${ka.actionName.slice(1)}`,

View File

@ -46,8 +46,8 @@ class OrderByAndLimitExp extends Expression {
notes.sort((a, b) => {
for (const {valueExtractor, smaller, larger} of this.orderDefinitions) {
let valA = valueExtractor.extract(a);
let valB = valueExtractor.extract(b);
let valA: string | number | Date | null = valueExtractor.extract(a);
let valB: string | number | Date | null = valueExtractor.extract(b);
if (valA === undefined) {
valA = null;
@ -68,8 +68,16 @@ class OrderByAndLimitExp extends Expression {
return larger;
}
// if both are dates, then parse them for dates comparison
if (typeof valA === "string" && this.isDate(valA) &&
typeof valB === "string" && this.isDate(valB)) {
valA = new Date(valA);
valB = new Date(valB);
}
// if both are numbers, then parse them for numerical comparison
if (typeof valA === "string" && this.isNumber(valA) &&
else if (typeof valA === "string" && this.isNumber(valA) &&
typeof valB === "string" && this.isNumber(valB)) {
valA = parseFloat(valA);
valB = parseFloat(valB);
@ -99,6 +107,10 @@ class OrderByAndLimitExp extends Expression {
return noteSet;
}
isDate(date: number | string) {
return !isNaN(new Date(date).getTime());
}
isNumber(x: number | string) {
if (typeof x === 'number') {
return true;

View File

@ -4,4 +4,4 @@
[ ! -z "${USER_GID}" ] && groupmod -og ${USER_GID} node || echo "No USER_GID specified, leaving 1000"
chown -R node:node /home/node
exec su -c "node ./src/www" node
exec su -c "node ./src/main" node

View File

@ -1 +1,161 @@
{}
{
"keyboard_actions": {
"open-jump-to-note-dialog": "打开“跳转到笔记”对话框",
"search-in-subtree": "在当前笔记的子树中搜索笔记",
"expand-subtree": "展开当前笔记的子树",
"collapse-tree": "折叠完整的笔记树",
"collapse-subtree": "折叠当前笔记的子树",
"sort-child-notes": "排序子笔记",
"creating-and-moving-notes": "创建和移动笔记",
"create-note-into-inbox": "在收件箱(如果有定义的话)或日记中创建笔记",
"delete-note": "删除笔记",
"move-note-up": "上移笔记",
"move-note-down": "下移笔记",
"move-note-up-in-hierarchy": "在层级中上移笔记",
"move-note-down-in-hierarchy": "在层级中下移笔记",
"edit-note-title": "从笔记树跳转到笔记详情并编辑标题",
"edit-branch-prefix": "显示编辑分支前缀对话框",
"note-clipboard": "笔记剪贴板",
"copy-notes-to-clipboard": "复制选定的笔记到剪贴板",
"paste-notes-from-clipboard": "从剪贴板粘贴笔记到活动笔记中",
"cut-notes-to-clipboard": "剪切选定的笔记到剪贴板",
"select-all-notes-in-parent": "选择当前笔记级别的所有笔记",
"add-note-above-to-the-selection": "将上方笔记添加到选择中",
"add-note-below-to-selection": "将下方笔记添加到选择中",
"duplicate-subtree": "复制子树",
"tabs-and-windows": "标签和窗口",
"open-new-tab": "打开新标签",
"close-active-tab": "关闭活动标签",
"reopen-last-tab": "重新打开最后关闭的标签",
"activate-next-tab": "激活右侧标签",
"activate-previous-tab": "激活左侧标签",
"open-new-window": "打开新空窗口",
"toggle-tray": "显示/隐藏应用程序的系统托盘",
"first-tab": "激活列表中的第一个标签",
"second-tab": "激活列表中的第二个标签",
"third-tab": "激活列表中的第三个标签",
"fourth-tab": "激活列表中的第四个标签",
"fifth-tab": "激活列表中的第五个标签",
"sixth-tab": "激活列表中的第六个标签",
"seventh-tab": "激活列表中的第七个标签",
"eight-tab": "激活列表中的第八个标签",
"ninth-tab": "激活列表中的第九个标签",
"last-tab": "激活列表中的最后一个标签",
"dialogs": "对话框",
"show-note-source": "显示笔记源对话框",
"show-options": "显示选项对话框",
"show-revisions": "显示笔记历史对话框",
"show-recent-changes": "显示最近更改对话框",
"show-sql-console": "显示SQL控制台对话框",
"show-backend-log": "显示后端日志对话框",
"text-note-operations": "文本笔记操作",
"add-link-to-text": "打开对话框以将链接添加到文本",
"follow-link-under-cursor": "跟随光标下的链接",
"insert-date-and-time-to-text": "将当前日期和时间插入文本",
"paste-markdown-into-text": "将剪贴板中的Markdown粘贴到文本笔记中",
"cut-into-note": "从当前笔记中剪切选择并创建包含选定文本的子笔记",
"add-include-note-to-text": "打开对话框以包含笔记",
"edit-readonly-note": "编辑只读笔记",
"attributes-labels-and-relations": "属性(标签和关系)",
"add-new-label": "创建新标签",
"create-new-relation": "创建新关系",
"ribbon-tabs": "功能区标签",
"toggle-basic-properties": "切换基本属性",
"toggle-file-properties": "切换文件属性",
"toggle-image-properties": "切换图像属性",
"toggle-owned-attributes": "切换拥有的属性",
"toggle-inherited-attributes": "切换继承的属性",
"toggle-promoted-attributes": "切换提升的属性",
"toggle-link-map": "切换链接地图",
"toggle-note-info": "切换笔记信息",
"toggle-note-paths": "切换笔记路径",
"toggle-similar-notes": "切换相似笔记",
"other": "其他",
"toggle-right-pane": "切换右侧面板的显示,包括目录和高亮",
"print-active-note": "打印活动笔记",
"open-note-externally": "以默认应用程序打开笔记文件",
"render-active-note": "渲染(重新渲染)活动笔记",
"run-active-note": "运行活动JavaScript前端/后端)代码笔记",
"toggle-note-hoisting": "切换活动笔记的提升",
"unhoist": "从任何地方取消提升",
"reload-frontend-app": "重新加载前端应用",
"open-dev-tools": "打开开发工具",
"toggle-left-note-tree-panel": "切换左侧(笔记树)面板",
"toggle-full-screen": "切换全屏",
"zoom-out": "缩小",
"zoom-in": "放大",
"note-navigation": "笔记导航",
"reset-zoom-level": "重置缩放级别",
"copy-without-formatting": "不带格式复制选定文本",
"force-save-revision": "强制创建/保存当前笔记的历史版本",
"show-help": "显示内置帮助/备忘单",
"toggle-book-properties": "切换书籍属性"
},
"login": {
"title": "登录",
"heading": "Trilium登录",
"incorrect-password": "密码不正确。请再试一次。",
"password": "密码",
"remember-me": "记住我",
"button": "登录"
},
"set_password": {
"heading": "设置密码",
"description": "在您可以从Web开始使用Trilium之前您需要先设置一个密码。然后您将使用此密码登录。",
"password": "密码",
"password-confirmation": "密码确认",
"button": "设置密码"
},
"javascript-required": "Trilium需要启用JavaScript。",
"setup": {
"heading": "TriliumNext笔记设置",
"new-document": "我是新用户我想为我的笔记创建一个新的Trilium文档",
"sync-from-desktop": "我已经有一个桌面实例,我想设置与它的同步",
"sync-from-server": "我已经有一个服务器实例,我想设置与它的同步",
"next": "下一步",
"init-in-progress": "文档初始化进行中",
"redirecting": "您将很快被重定向到应用程序。",
"title": "设置"
},
"setup_sync-from-desktop": {
"heading": "从桌面同步",
"description": "此设置需要从桌面实例启动:",
"step1": "打开您的TriliumNext笔记桌面实例。",
"step2": "从Trilium菜单中点击选项。",
"step3": "点击同步。",
"step4": "将服务器实例地址更改为:{{- host}}并点击保存。",
"step5": "点击“测试同步”按钮以验证连接是否成功。",
"step6": "完成这些步骤后,点击{{- link}}。",
"step6-here": "这里"
},
"setup_sync-from-server": {
"heading": "从服务器同步",
"instructions": "请在下面输入Trilium服务器地址和密码。这将从服务器下载整个Trilium数据库文档并设置同步。根据数据库大小和您的连接速度这可能需要一段时间。",
"server-host": "Trilium服务器地址",
"server-host-placeholder": "https://<主机名称>:<端口>",
"proxy-server": "代理服务器(可选)",
"proxy-server-placeholder": "https://<主机名称>:<端口>",
"note": "注意:",
"proxy-instruction": "如果您将代理设置留空,将使用系统代理(仅适用于桌面应用)",
"password": "密码",
"password-placeholder": "密码",
"back": "返回",
"finish-setup": "完成设置"
},
"setup_sync-in-progress": {
"heading": "同步中",
"successful": "同步已正确设置。初始同步完成可能需要一些时间。完成后,您将被重定向到登录页面。",
"outstanding-items": "未完成的同步项目:",
"outstanding-items-default": "无"
},
"share_404": {
"title": "未找到",
"heading": "未找到"
},
"share_page": {
"parent": "父级:",
"clipped-from": "此笔记最初剪切自 {{- url}}",
"child-notes": "子笔记:",
"no-content": "此笔记没有内容。"
}
}

View File

@ -1 +1,161 @@
{}
{
"keyboard_actions": {
"open-jump-to-note-dialog": "Abrir cuadro de diálogo \"Saltar a nota\"",
"search-in-subtree": "Buscar notas en el subárbol de la nota activa",
"expand-subtree": "Expandir el subárbol de la nota actual",
"collapse-tree": "Colapsa el árbol de notas completo",
"collapse-subtree": "Colapsa el subárbol de la nota actual",
"sort-child-notes": "Ordenar notas hijas",
"creating-and-moving-notes": "Creando y moviendo notas",
"create-note-into-inbox": "Crear una nota en la bandeja de entrada (si está definida) o nota del día",
"delete-note": "Eliminar nota",
"move-note-up": "Mover nota hacia arriba",
"move-note-down": "Mover nota hacia abajo",
"move-note-up-in-hierarchy": "Mover nota hacia arriba en la jerarquía",
"move-note-down-in-hierarchy": "Mover nota hacia abajo en la jerarquía",
"edit-note-title": "Saltar del árbol al detalle de la nota y editar el título",
"edit-branch-prefix": "Mostrar cuadro de diálogo Editar prefijo de rama",
"note-clipboard": "Portapapeles de notas",
"copy-notes-to-clipboard": "Copiar las notas seleccionadas al portapapeles",
"paste-notes-from-clipboard": "Pegar las notas del portapapeles en una nota activa",
"cut-notes-to-clipboard": "Cortar las notas seleccionadas al portapapeles",
"select-all-notes-in-parent": "Seleccionar todas las notas del nivel de la nota actual",
"add-note-above-to-the-selection": "Agregar nota arriba de la selección",
"add-note-below-to-selection": "Agregar nota arriba de la selección",
"duplicate-subtree": "Duplicar subárbol",
"tabs-and-windows": "Pestañas y ventanas",
"open-new-tab": "Abre una nueva pestaña",
"close-active-tab": "Cierra la pestaña activa",
"reopen-last-tab": "Vuelve a abrir la última pestaña cerrada",
"activate-next-tab": "Activa la pestaña de la derecha",
"activate-previous-tab": "Activa la pestaña de la izquierda",
"open-new-window": "Abrir nueva ventana vacía",
"toggle-tray": "Muestra/Oculta la aplicación en la bandeja del sistema",
"first-tab": "Activa la primera pestaña de la lista",
"second-tab": "Activa la segunda pestaña de la lista",
"third-tab": "Activa la tercera pestaña de la lista",
"fourth-tab": "Activa la cuarta pestaña de la lista",
"fifth-tab": "Activa la quinta pestaña de la lista",
"sixth-tab": "Activa la sexta pestaña de la lista",
"seventh-tab": "Activa la séptima pestaña de la lista",
"eight-tab": "Activa la octava pestaña de la lista",
"ninth-tab": "Activa la novena pestaña de la lista",
"last-tab": "Activa la última pestaña de la lista",
"dialogs": "Diálogos",
"show-note-source": "Muestra el cuadro de diálogo Fuente de nota",
"show-options": "Muestra el cuadro de diálogo Opciones",
"show-revisions": "Muestra el cuadro de diálogo Revisiones de notas",
"show-recent-changes": "Muestra el cuadro de diálogo Cambios recientes",
"show-sql-console": "Muestra el cuadro de diálogo Consola SQL",
"show-backend-log": "Muestra el cuadro de diálogo Registro de backend",
"text-note-operations": "Operaciones de notas de texto",
"add-link-to-text": "Abrir cuadro de diálogo para agregar un enlace al texto",
"follow-link-under-cursor": "Seguir el enlace dentro del cual se coloca el cursor",
"insert-date-and-time-to-text": "Insertar fecha y hora actuales en el texto",
"paste-markdown-into-text": "Pega Markdown del portapapeles en la nota de texto",
"cut-into-note": "Corta la selección de la nota actual y crea una subnota con el texto seleccionado",
"add-include-note-to-text": "Abre el cuadro de diálogo para incluir una nota",
"edit-readonly-note": "Editar una nota de sólo lectura",
"attributes-labels-and-relations": "Atributos (etiquetas y relaciones)",
"add-new-label": "Crear nueva etiqueta",
"create-new-relation": "Crear nueva relación",
"ribbon-tabs": "Pestañas de cinta",
"toggle-basic-properties": "Alternar propiedades básicas",
"toggle-file-properties": "Alternar propiedades de archivo",
"toggle-image-properties": "Alternar propiedades de imagen",
"toggle-owned-attributes": "Alternar atributos de propiedad",
"toggle-inherited-attributes": "Alternar atributos heredados",
"toggle-promoted-attributes": "Alternar atributos promocionados",
"toggle-link-map": "Alternar mapa de enlaces",
"toggle-note-info": "Alternar información de nota",
"toggle-note-paths": "Alternar rutas de notas",
"toggle-similar-notes": "Alternar notas similares",
"other": "Otro",
"toggle-right-pane": "Alternar la visualización del panel derecho, que incluye la tabla de contenidos y aspectos destacados",
"print-active-note": "Imprimir nota activa",
"open-note-externally": "Abrir nota como un archivo con la aplicación predeterminada",
"render-active-note": "Renderizar (volver a renderizar) nota activa",
"run-active-note": "Ejecutar nota de código JavaScript activa (frontend/backend)",
"toggle-note-hoisting": "Alterna la elevación de la nota activa",
"unhoist": "Bajar desde cualquier lugar",
"reload-frontend-app": "Recargar frontend de la aplicación",
"open-dev-tools": "Abrir herramientas de desarrollo",
"toggle-left-note-tree-panel": "Alternar panel izquierdo (árbol de notas)",
"toggle-full-screen": "Alternar pantalla completa",
"zoom-out": "Alejar",
"zoom-in": "Acercar",
"note-navigation": "Navegación de notas",
"reset-zoom-level": "Restablecer nivel de zoom",
"copy-without-formatting": "Copiar el texto seleccionado sin formatear",
"force-save-revision": "Forzar la creación/guardado de una nueva revisión de nota de la nota activa",
"show-help": "Muestra ayuda/hoja de referencia integrada",
"toggle-book-properties": "Alternar propiedades del libro"
},
"login": {
"title": "Iniciar sesión",
"heading": "Iniciar sesión en Trillium",
"incorrect-password": "La contraseña es incorrecta. Por favor inténtalo de nuevo.",
"password": "Contraseña",
"remember-me": "Recordarme",
"button": "Iniciar sesión"
},
"set_password": {
"heading": "Establecer contraseña",
"description": "Antes de poder comenzar a usar Trilium desde la web, primero debe establecer una contraseña. Luego utilizará esta contraseña para iniciar sesión.",
"password": "Contraseña",
"password-confirmation": "Confirmación de contraseña",
"button": "Establecer contraseña"
},
"javascript-required": "Trilium requiere que JavaScript esté habilitado.",
"setup": {
"heading": "Configuración de TrilliumNext Notes",
"new-document": "Soy un usuario nuevo y quiero crear un nuevo documento de Trilium para mis notas",
"sync-from-desktop": "Ya tengo una instancia de escritorio y quiero configurar la sincronización con ella",
"sync-from-server": "Ya tengo una instancia de servidor y quiero configurar la sincronización con ella",
"next": "Siguiente",
"init-in-progress": "Inicialización del documento en curso",
"redirecting": "En breve será redirigido a la aplicación.",
"title": "Configuración"
},
"setup_sync-from-desktop": {
"heading": "Sincronizar desde el escritorio",
"description": "Esta configuración debe iniciarse desde la instancia de escritorio:",
"step1": "Abra su instancia de escritorio de TriliumNext Notes.",
"step2": "En el menú Trilium, dé clic en Opciones.",
"step3": "Dé clic en la categoría Sincronizar.",
"step4": "Cambie la dirección de la instancia del servidor a: {{- host}} y dé clic en Guardar.",
"step5": "Dé clic en el botón \"Probar sincronización\" para verificar que la conexión fue exitosa.",
"step6": "Una vez que haya completado estos pasos, dé clic en {{- link}}.",
"step6-here": "aquí"
},
"setup_sync-from-server": {
"heading": "Sincronización desde el servidor",
"instructions": "Por favor, ingrese la dirección y las credenciales del servidor Trilium a continuación. Esto descargará todo el documento de Trilium desde el servidor y configurará la sincronización. Dependiendo del tamaño del documento y de la velocidad de su conexión, esto puede tardar un poco.",
"server-host": "Dirección del servidor Trilium",
"server-host-placeholder": "https://<hostname>:<port>",
"proxy-server": "Servidor proxy (opcional)",
"proxy-server-placeholder": "https://<hostname>:<port>",
"note": "Nota:",
"proxy-instruction": "Si deja la configuración de proxy en blanco, se utilizará el proxy del sistema (aplica únicamente a la aplicación de escritorio)",
"password": "Contraseña",
"password-placeholder": "Contraseña",
"back": "Atrás",
"finish-setup": "Finalizar la configuración"
},
"setup_sync-in-progress": {
"heading": "Sincronización en progreso",
"successful": "La sincronización se ha configurado correctamente. La sincronización inicial tardará algún tiempo en finalizar. Una vez hecho esto, será redirigido a la página de inicio de sesión.",
"outstanding-items": "Elementos de sincronización destacados:",
"outstanding-items-default": "N/A"
},
"share_404": {
"title": "No encontrado",
"heading": "No encontrado"
},
"share_page": {
"parent": "padre:",
"clipped-from": "Esta nota fue recortada originalmente de {{- url}}",
"child-notes": "Notas hijo:",
"no-content": "Esta nota no tiene contenido."
}
}