Merge branch 'develop' into revisions_number_limit

This commit is contained in:
SiriusXT 2024-09-13 19:46:07 +08:00
commit 84bf0cbae5
96 changed files with 5805 additions and 4944 deletions

View File

@ -1,143 +0,0 @@
on:
workflow_dispatch:
env:
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
TEST_TAG: triliumnext/notes:test
PLATFORMS: linux/amd64
jobs:
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Build and export to Docker
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run the container in the background
run: docker run -d --rm --name trilium_local ${{ env.TEST_TAG }}
- name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v1
with:
container: trilium_local
wait-time: 50
require-status: running
require-healthy: true
build_docker:
name: Build Docker images
runs-on: ubuntu-latest
needs:
- test_docker
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Extract metadata (tags, labels) for GHCR image
id: ghcr-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Extract metadata (tags, labels) for DockerHub image
id: dh-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Log in to the GHCR container registry
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Build and push container image to GHCR
uses: docker/build-push-action@v6
id: ghcr-push
with:
file: ./Dockerfile.alpine
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.ghcr-meta.outputs.tags }}
labels: ${{ steps.ghcr-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to GHCR
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
push-to-registry: true
- name: Log in to the DockerHub container registry
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image to DockerHub
uses: docker/build-push-action@v6
id: dh-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.dh-meta.outputs.tags }}
labels: ${{ steps.dh-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to DockerHub
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.dh-push.outputs.digest }}
push-to-registry: true

View File

@ -9,22 +9,35 @@ on:
- "bin/**"
tags:
- "v*"
workflow_dispatch:
workflow_dispatch:
env:
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
TEST_TAG: triliumnext/notes:test
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
IMAGE_NAME: ${{ github.repository_owner }}/notes
TEST_TAG: ${{ github.repository_owner }}/notes:test
permissions:
contents: read
packages: write
jobs:
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
strategy:
matrix:
include:
- dockerfile: Dockerfile.alpine
- dockerfile: Dockerfile
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@ -47,6 +60,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
@ -63,7 +77,7 @@ jobs:
require-status: running
require-healthy: true
build_docker:
build:
name: Build Docker images
runs-on: ubuntu-latest
needs:
@ -73,26 +87,48 @@ jobs:
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Extract metadata (tags, labels) for GHCR image
id: ghcr-meta
strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
- dockerfile: Dockerfile
platform: linux/arm64
- dockerfile: Dockerfile
platform: linux/arm/v7
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Extract metadata (tags, labels) for DockerHub image
id: dh-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
@ -103,50 +139,103 @@ jobs:
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Log in to the GHCR container registry
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Build and push container image to GHCR
uses: docker/build-push-action@v6
id: ghcr-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.ghcr-meta.outputs.tags }}
labels: ${{ steps.ghcr-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to GHCR
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
push-to-registry: true
- name: Log in to the DockerHub container registry
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image to DockerHub
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
id: dh-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.dh-meta.outputs.tags }}
labels: ${{ steps.dh-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to DockerHub
uses: actions/attest-build-provenance@v1
file: ${{ matrix.dockerfile }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.dh-push.outputs.digest }}
push-to-registry: true
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
name: Merge manifest lists
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
# Extract the branch or tag name from the ref
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
# Create and push the manifest list with both the branch/tag name and the commit SHA
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

View File

@ -42,9 +42,9 @@ const NOTE_TYPE_ICONS = {
"code": "bx bx-code",
"render": "bx bx-extension",
"search": "bx bx-file-find",
"relationMap": "bx bx-map-alt",
"relationMap": "bx bxs-network-chart",
"book": "bx bx-book",
"noteMap": "bx bx-map-alt",
"noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection",
"canvas": "bx bx-pen",
"webView": "bx bx-globe-alt",
@ -570,7 +570,7 @@ class FNote {
return workspaceIconClass;
}
else if (this.noteId === 'root') {
return "bx bx-chevrons-right";
return "bx bx-home-alt-2";
}
if (this.noteId === '_share') {
return "bx bx-share-alt";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8752
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@
"build-frontend-docs": "rimraf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "cross-env node --import ./loader-register.js node_modules/webpack/bin/webpack.js -c webpack.config.ts",
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/.bin/jasmine",
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/jasmine/bin/jasmine.js",
"test-es6": "tsx -r esm spec-es6/attribute_parser.spec.ts",
"test": "npm run test-jasmine && npm run test-es6",
"start-electron-forge": "npm run prepare-dist && electron-forge start",
@ -56,9 +56,9 @@
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
"autocomplete.js": "^0.38.1",
"axios": "^1.7.2",
"better-sqlite3": "^11.1.2",
"bootstrap": "^4.6.2",
"axios": "^1.7.7",
"better-sqlite3": "^11.3.0",
"bootstrap": "^5.3.3",
"boxicons": "2.1.4",
"chokidar": "3.6.0",
"cls-hooked": "4.2.2",
@ -66,17 +66,17 @@
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"csurf": "1.11.0",
"dayjs": "^1.11.12",
"dayjs": "^1.11.13",
"dayjs-plugin-utc": "0.1.2",
"debounce": "^2.1.0",
"ejs": "^3.1.10",
"electron-debug": "3.2.0",
"electron-dl": "3.5.2",
"electron-debug": "^4.0.1",
"electron-dl": "^4.0.0",
"electron-squirrel-startup": "^1.0.0",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"eslint": "^9.9.0",
"express": "^4.19.2",
"eslint": "^9.10.0",
"express": "^4.21.0",
"express-partial-content": "1.0.2",
"express-rate-limit": "^7.3.1",
"express-session": "1.18.0",
@ -87,11 +87,11 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "^7.0.5",
"i18next": "^23.14.0",
"i18next": "^23.15.1",
"i18next-fs-backend": "^2.3.2",
"i18next-http-backend": "^2.6.1",
"image-type": "4.1.0",
"ini": "^4.1.3",
"ini": "^5.0.0",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.22.12",
@ -99,13 +99,13 @@
"jquery": "3.7.1",
"jquery-hotkeys": "0.2.2",
"jquery.fancytree": "^2.38.3",
"jsdom": "^24.1.0",
"jsdom": "^25.0.0",
"jsplumb": "^2.15.6",
"katex": "^0.16.11",
"knockout": "^3.5.1",
"mark.js": "^8.11.1",
"marked": "^13.0.2",
"mermaid": "^10.9.1",
"marked": "^14.1.2",
"mermaid": "^11.1.1",
"mime-types": "2.1.35",
"mind-elixir": "^4.0.5",
"multer": "1.4.5-lts.1",
@ -177,12 +177,12 @@
"@types/ws": "^8.5.12",
"@types/xml2js": "^0.4.14",
"cross-env": "7.0.3",
"electron": "^31.2.1",
"electron": "^31.3.1",
"electron-packager": "17.1.2",
"electron-rebuild": "3.2.9",
"esm": "3.2.25",
"iconsur": "^1.7.0",
"jasmine": "5.1.0",
"jasmine": "^5.3.0",
"jsdoc": "^4.0.3",
"lorem-ipsum": "2.0.8",
"nodemon": "^3.1.4",
@ -190,7 +190,7 @@
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"tslib": "^2.7.0",
"tsx": "^4.19.0",
"tsx": "^4.19.1",
"typescript": "^5.5.4",
"webpack": "^5.93.0",
"webpack-cli": "5.1.4"

View File

@ -14,9 +14,9 @@ const NOTE_TYPE_ICONS = {
"code": "bx bx-code",
"render": "bx bx-extension",
"search": "bx bx-file-find",
"relationMap": "bx bx-map-alt",
"relationMap": "bx bxs-network-chart",
"book": "bx bx-book",
"noteMap": "bx bx-map-alt",
"noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection",
"canvas": "bx bx-pen",
"webView": "bx bx-globe-alt",
@ -543,7 +543,7 @@ class FNote {
return workspaceIconClass;
}
else if (this.noteId === 'root') {
return "bx bx-chevrons-right";
return "bx bx-home-alt-2";
}
if (this.noteId === '_share') {
return "bx bx-share-alt";

View File

@ -38,6 +38,8 @@ import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import EditButton from "../widgets/buttons/edit_button.js";
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
import MermaidWidget from "../widgets/mermaid.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
@ -160,6 +162,8 @@ export default class DesktopLayout {
.child(new WatchedFileUpdateStatusWidget())
.child(new FloatingButtons()
.child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new CopyImageReferenceButton())

View File

@ -12,8 +12,9 @@ export async function initLocale() {
lng: locale,
fallbackLng: "en",
backend: {
loadPath: `/${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
}
loadPath: `${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
},
returnEmptyString: false
});
}

View File

@ -105,28 +105,23 @@ function initNoteAutocomplete($el, options) {
$el.addClass("note-autocomplete-input");
const $clearTextButton = $("<a>")
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
.prop("title", "Clear text field");
const $clearTextButton = $("<button>")
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
.prop("title", "Clear text field");
const $showRecentNotesButton = $("<a>")
.addClass("input-group-text show-recent-notes-button bx bx-time")
.prop("title", "Show recent notes");
const $showRecentNotesButton = $("<button>")
.addClass("input-group-text show-recent-notes-button bx bx-time")
.prop("title", "Show recent notes");
const $goToSelectedNoteButton = $("<a>")
const $goToSelectedNoteButton = $("<button>")
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
const $sideButtons = $("<div>")
.addClass("input-group-append")
.append($clearTextButton)
.append($showRecentNotesButton);
$el.after($clearTextButton).after($showRecentNotesButton);
if (!options.hideGoToSelectedNoteButton) {
$sideButtons.append($goToSelectedNoteButton);
$el.after($goToSelectedNoteButton);
}
$el.after($sideButtons);
$clearTextButton.on('click', () => clearText($el));
$showRecentNotesButton.on('click', e => {
@ -180,13 +175,13 @@ function initNoteAutocomplete($el, options) {
}
if (suggestion.action === 'create-note') {
const {success, noteType, templateNoteId} = await noteCreateService.chooseNoteType();
const { success, noteType, templateNoteId } = await noteCreateService.chooseNoteType();
if (!success) {
return;
}
const {note} = await noteCreateService.createNote(suggestion.parentNoteId, {
const { note } = await noteCreateService.createNote(suggestion.parentNoteId, {
title: suggestion.noteTitle,
activate: false,
type: noteType,

View File

@ -7,8 +7,8 @@ async function getNoteTypeItems(command) {
{ title: t("note_types.text"), command: command, type: "text", uiIcon: "bx bx-note" },
{ title: t("note_types.code"), command: command, type: "code", uiIcon: "bx bx-code" },
{ title: t("note_types.saved-search"), command: command, type: "search", uiIcon: "bx bx-file-find" },
{ title: t("note_types.relation-map"), command: command, type: "relationMap", uiIcon: "bx bx-map-alt" },
{ title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bx-map-alt" },
{ title: t("note_types.relation-map"), command: command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.render-note"), command: command, type: "render", uiIcon: "bx bx-extension" },
{ title: t("note_types.book"), command: command, type: "book", uiIcon: "bx bx-book" },
{ title: t("note_types.mermaid-diagram"), command: command, type: "mermaid", uiIcon: "bx bx-selection" },

View File

@ -166,6 +166,23 @@ function getHost() {
return `${url.protocol}//${url.hostname}:${url.port}`;
}
async function openDirectory(directory) {
try {
if (utils.isElectron()) {
const electron = utils.dynamicRequire('electron');
const res = await electron.shell.openPath(directory);
if (res) {
console.error('Failed to open directory:', res);
}
} else {
console.error('Not running in an Electron environment.');
}
} catch (err) {
// Handle file system errors (e.g. path does not exist or is inaccessible)
console.error('Error:', err.message);
}
}
export default {
download,
downloadFileNote,
@ -176,4 +193,5 @@ export default {
openAttachmentExternally,
openNoteCustom,
openAttachmentCustom,
openDirectory
}

View File

@ -2,15 +2,18 @@ import ws from "./ws.js";
import utils from "./utils.js";
function toast(options) {
const $toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto"><span class="bx bx-${options.icon}"></span> <span class="toast-title"></span></strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body"></div>
</div>`);
const $toast = $(
`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">
<span class="bx bx-${options.icon}"></span>
<span class="toast-title"></span>
</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body"></div>
</div>`
);
$toast.find('.toast-title').text(options.title);
$toast.find('.toast-body').text(options.message);

View File

@ -201,7 +201,7 @@ function getMimeTypeClass(mime) {
function closeActiveDialog() {
if (glob.activeDialog) {
glob.activeDialog.modal('hide');
bootstrap.Modal.getOrCreateInstance(glob.activeDialog).hide();
glob.activeDialog = null;
}
}
@ -245,8 +245,7 @@ async function openDialog($dialog, closeActDialog = true) {
}
saveFocusedElement();
$dialog.modal();
bootstrap.Modal.getOrCreateInstance($dialog).show();
$dialog.on('hidden.bs.modal', () => {
$(".aa-input").autocomplete("close");

View File

@ -97,7 +97,7 @@ const TPL = `
<tr class="attr-row-promoted"
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>
<td><input type="checkbox" class="attr-input-promoted form-check" /></td>
</tr>
<tr class="attr-row-promoted-alias">
<th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th>
@ -134,9 +134,7 @@ const TPL = `
<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">${t('attribute_detail.digits')}</span>
</div>
<span class="input-group-text">${t('attribute_detail.digits')}</span>
</div>
</td>
</tr>
@ -150,7 +148,7 @@ const TPL = `
</tr>
<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>
<td><input type="checkbox" class="attr-input-inheritable form-check" /></td>
</tr>
</table>
@ -349,7 +347,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
this.$inputTargetNote = this.$widget.find('.attr-input-target-note');
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, {allowCreatingNotes: true})
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, { allowCreatingNotes: true })
.on('autocomplete:noteselected', (event, suggestion, dataset) => {
if (!suggestion.notePath) {
return false;
@ -403,7 +401,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
});
}
async showAttributeDetail({allAttributes, attribute, isOwned, x, y, focus}) {
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus }) {
if (!attribute) {
this.hide();
@ -545,7 +543,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
}
return {left, right};
return { left, right };
}
async saveAndClose() {
@ -589,19 +587,19 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
async updateRelatedNotes() {
let {results, count} = await server.post('search-related', this.attribute);
let { results, count } = await server.post('search-related', this.attribute);
for (const res of results) {
res.noteId = res.notePathArray[res.notePathArray.length - 1];
}
results = results.filter(({noteId}) => noteId !== this.noteId);
results = results.filter(({ noteId }) => noteId !== this.noteId);
if (results.length === 0) {
this.$relatedNotesContainer.hide();
} else {
this.$relatedNotesContainer.show();
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", {attributeType: this.attribute.type, attributeName: this.attribute.name}));
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", { attributeType: this.attribute.type, attributeName: this.attribute.name }));
this.$relatedNotesList.empty();
@ -611,7 +609,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
for (const note of displayedNotes) {
const notePath = note.getBestNotePathString(hoistedNoteId);
const $noteLink = await linkService.createLink(notePath, {showNotePath: true});
const $noteLink = await linkService.createLink(notePath, { showNotePath: true });
this.$relatedNotesList.append(
$("<li>").append($noteLink)
@ -619,7 +617,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
if (results.length > DISPLAYED_NOTES) {
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", {count: count - DISPLAYED_NOTES}));
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", { count: count - DISPLAYED_NOTES }));
} else {
this.$relatedNotesMoreNotes.hide();
}

View File

@ -15,7 +15,7 @@ const TPL = `
<td class="button-column">
<div style="display: flex">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('execute_script.help_text')}

View File

@ -21,7 +21,7 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("add_label.help_text")}</p>
@ -30,7 +30,7 @@ const TPL = `
<li>${t("add_label.help_text_item2")}</li>
</ul>
<p>${t("add_label.help_text_note")}</p>
${t("add_label.help_text_note")}
</div>
</div>

View File

@ -21,11 +21,11 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("update_label_value.help_text")}</p>
<p>${t("update_label_value.help_text_note")}</p>
${t("update_label_value.help_text_note")}
</div>
</div>

View File

@ -10,13 +10,13 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("delete_note.delete_matched_notes_description")}</p>
<p>${t("delete_note.undelete_notes_instruction")}</p>
<p>${t("delete_note.erase_notes_instruction")}</p>
${t("delete_note.erase_notes_instruction")}
</div>
</div>

View File

@ -9,7 +9,7 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('delete_revisions.all_past_note_revisions')}
</div>

View File

@ -18,11 +18,11 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('move_note.on_all_matched_notes')}:</p>
<ul>
<ul style="margin-bottom: 0;">
<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>

View File

@ -16,7 +16,7 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('rename_note.evaluated_as_js_string')}</p>

View File

@ -25,9 +25,9 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('add_relation.create_relation_on_all_matched_notes')}</p>
${t('add_relation.create_relation_on_all_matched_notes')}
</div>
</div>

View File

@ -25,11 +25,11 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('update_relation_target.on_all_matched_notes')}:</p>
<ul>
<ul style="margin-bottom: 0;">
<li>${t('update_relation_target.create_given_relation')}</li>
<li>${t('update_relation_target.change_target_note')}</li>
</ul>

View File

@ -1,7 +1,7 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `<button class="button-widget bx"
data-toggle="tooltip"
data-bs-toggle="tooltip"
title=""></button>`;
export default class AbstractButtonWidget extends NoteContextAwareWidget {
@ -22,10 +22,13 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.tooltip = new bootstrap.Tooltip(this.$widget, {
html: true, title: () => this.getTitle(), trigger: 'hover'
})
if (this.settings.onContextMenu) {
this.$widget.on("contextmenu", e => {
this.$widget.tooltip("hide");
this.tooltip.hide();
this.settings.onContextMenu(e);
@ -35,12 +38,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
this.$widget.attr("data-placement", this.settings.titlePlacement);
this.$widget.tooltip({
html: true,
title: () => this.getTitle(),
trigger: "hover"
});
super.doRender();
}

View File

@ -27,7 +27,7 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true"
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"
style="position: relative; top: 3px;"></button>
@ -61,7 +61,8 @@ export default class AttachmentActionsWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input");
this.$uploadNewRevisionInput.on('change', async () => {
@ -84,7 +85,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
.addClass("disabled")
.append($('<span class="disabled-tooltip"> (?)</span>')
.attr("title", t('attachments_actions.open_externally_detail_page'))
);
);
if (isElectron) {
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton
@ -94,7 +95,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
);
}
}
if (!isElectron){
if (!isElectron) {
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton
.addClass("disabled")
@ -138,7 +139,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
return;
}
const {note: newNote} = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
const { note: newNote } = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
toastService.showMessage(t('attachments_actions.convert_success', { title: this.attachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
@ -155,6 +156,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
return;
}
await server.put(`attachments/${this.attachmentId}/rename`, {title: attachmentTitle});
await server.put(`attachments/${this.attachmentId}/rename`, { title: attachmentTitle });
}
}

View File

@ -74,12 +74,12 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
doRender() {
super.doRender();
this.$month = this.$dropdownContent.find('[data-calendar-area="month"]');
this.$weekHeader = this.$dropdownContent.find(".calendar-week");
this.manageFirstDayOfWeek();
// Month navigation
this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]');
this.$monthSelect.on("input", (e) => {
@ -88,10 +88,10 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
});
this.$next = this.$dropdownContent.find('[data-calendar-toggle="next"]');
this.$next.on('click', () => {
this.date.setMonth(this.date.getMonth() + 1);
this.date.setMonth(this.date.getMonth() + 1);
this.createMonth();
});
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
this.$previous.on('click', e => {
this.date.setMonth(this.date.getMonth() - 1);
this.createMonth();
@ -108,7 +108,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.date.setFullYear(this.date.getFullYear() + 1);
this.createMonth();
});
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
this.$previousYear.on('click', e => {
this.date.setFullYear(this.date.getFullYear() - 1);
this.createMonth();
@ -123,11 +123,13 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (note) {
appContext.tabManager.getActiveContext().setNote(note.noteId);
this.hideDropdown();
this.dropdown.hide();
}
else {
toastService.showError(t("calendar.cannot_find_day_note"));
}
ev.stopPropagation();
});
// Prevent dismissing the calendar popup by clicking on an empty space inside it.
@ -138,9 +140,9 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.firstDayOfWeek = options.getInt("firstDayOfWeek");
// Generate the list of days of the week taking into consideration the user's selected first day of week.
let localeDaysOfWeek = [ ...DAYS_OF_WEEK ];
let localeDaysOfWeek = [...DAYS_OF_WEEK];
const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
localeDaysOfWeek = [ ...localeDaysOfWeek, ...daysToBeAddedAtEnd ];
localeDaysOfWeek = [...localeDaysOfWeek, ...daysToBeAddedAtEnd];
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`));
}
@ -184,7 +186,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (dateNoteId) {
$newDay.addClass('calendar-date-exists');
$newDay.attr("href", `#root/${dateNoteId}`);
$newDay.attr("data-href", `#root/${dateNoteId}`);
}
if (this.isEqual(this.date, this.activeDate)) {
@ -236,11 +238,11 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.$yearSelect.val(this.date.getFullYear());
}
async entitiesReloadedEvent({loadResults}) {
async entitiesReloadedEvent({ loadResults }) {
if (!loadResults.getOptionNames().includes("firstDayOfWeek")) {
return;
}
this.manageFirstDayOfWeek();
this.createMonth();
}

View File

@ -11,7 +11,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget {
if (this.settings.command) {
this.$widget.on("click", () => {
this.$widget.tooltip("hide");
this.tooltip.hide();
this.triggerCommand(this._command);
});

View File

@ -5,7 +5,7 @@ import UpdateAvailableWidget from "./update_available.js";
import options from "../../services/options.js";
const TPL = `
<div class="dropdown global-menu dropright">
<div class="dropdown global-menu dropend">
<style>
.global-menu {
width: 53px;
@ -102,10 +102,9 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" data-placement="right"
aria-haspopup="true" aria-expanded="false"
class="icon-action global-menu-button" title="${t('global_menu.menu')}">
<svg viewBox="0 0 256 256">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action global-menu-button">
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}">
<g>
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
@ -126,7 +125,7 @@ const TPL = `
<ul class="dropdown-menu dropdown-menu-right">
<li class="dropdown-item" data-trigger-command="showOptions">
<span class="bx bx-slider"></span>
<span class="bx bx-cog"></span>
${t('global_menu.options')}
</li>
@ -177,7 +176,7 @@ const TPL = `
<li class="dropdown-item dropdown-submenu">
<span class="dropdown-toggle">
<span class="bx bx-empty"></span>
<span class="bx bx-chip"></span>
${t('global_menu.advanced')}
</span>
@ -195,43 +194,43 @@ const TPL = `
</li>
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
<span class="bx bx-empty"></span>
<span class="bx bx-data"></span>
${t('global_menu.open_sql_console_history')}
</li>
<li class="dropdown-item" data-trigger-command="showSearchHistory">
<span class="bx bx-empty"></span>
<span class="bx bx-search-alt"></span>
${t('global_menu.open_search_history')}
</li>
<li class="dropdown-item" data-trigger-command="showBackendLog">
<span class="bx bx-empty"></span>
<span class="bx bx-detail"></span>
${t('global_menu.show_backend_log')}
<kbd data-command="showBackendLog"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
title="${t('global_menu.reload_hint')}">
<span class="bx bx-empty"></span>
<span class="bx bx-refresh"></span>
${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>
<span class="bx bx-hide"></span>
${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>
<span class="bx bx-help-circle"></span>
${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>
<span class="bx bx-info-circle"></span>
${t('global_menu.about')}
</li>
@ -259,10 +258,9 @@ export default class GlobalMenuWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$dropdown = this.$widget.find("[data-toggle='dropdown']");
const $button = this.$widget.find(".global-menu-button");
$button.tooltip({ trigger: "hover" });
$button.on("click", () => $button.tooltip("hide"));
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" });
this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog"));
@ -278,8 +276,11 @@ export default class GlobalMenuWidget extends BasicWidget {
return;
}
this.$dropdown.dropdown('toggle');
this.dropdown.toggle();
});
this.$widget.on('click', '.dropdown-submenu', e => {
e.stopPropagation();
})
this.$widget.find(".global-menu-button-update-available").append(
this.updateAvailableWidget.render()
@ -292,7 +293,12 @@ export default class GlobalMenuWidget extends BasicWidget {
}
this.$zoomState = this.$widget.find(".zoom-state");
this.$widget.on('show.bs.dropdown', () => this.updateZoomState());
this.$widget.on('show.bs.dropdown', () => {
this.updateZoomState();
this.tooltip.hide();
this.tooltip.disable();
});
this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable());
this.$widget.find(".zoom-buttons").on("click",
// delay to wait for the actual zoom change
@ -342,10 +348,10 @@ export default class GlobalMenuWidget extends BasicWidget {
}
activeContextChangedEvent() {
this.$dropdown.dropdown('hide');
this.dropdown.hide();
}
noteSwitchedEvent() {
this.$dropdown.dropdown('hide');
this.dropdown.hide();
}
}

View File

@ -27,7 +27,7 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true"
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
<div class="dropdown-menu dropdown-menu-right">
@ -82,7 +82,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.$importNoteButton = this.$widget.find('.import-files-button');
this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId}));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown('toggle'));
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");

View File

@ -1,17 +1,19 @@
import BasicWidget from "../basic_widget.js";
const TPL = `
<div class="dropdown right-dropdown-widget dropright">
<div class="dropdown right-dropdown-widget dropend">
<style>
.right-dropdown-widget {
height: 53px;
}
</style>
<button type="button" data-toggle="dropdown" data-placement="right"
<button type="button" data-bs-toggle="dropdown" data-placement="right"
aria-haspopup="true" aria-expanded="false"
class="bx right-dropdown-button launcher-button"></button>
<div class="tooltip-trigger"></div>
<div class="dropdown-menu dropdown-menu-right"></div>
</div>
`;
@ -28,12 +30,16 @@ export default class RightDropdownButtonWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$dropdownMenu = this.$widget.find(".dropdown-menu");
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
const $button = this.$widget.find(".right-dropdown-button")
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
this.tooltip = new bootstrap.Tooltip(this.$tooltip);
this.$widget.find(".right-dropdown-button")
.addClass(this.iconClass)
.attr("title", this.title)
.tooltip({ trigger: "hover" })
.on("click", () => $button.tooltip("hide"));
.on("click", () => this.tooltip.hide())
.on('mouseenter', () => this.tooltip.show())
.on('mouseleave', () => this.tooltip.hide());
this.$widget.on('show.bs.dropdown', async () => {
await this.dropdownShown();
@ -51,10 +57,5 @@ export default class RightDropdownButtonWidget extends BasicWidget {
}
// to be overridden
async dropdownShow() {}
hideDropdown() {
this.$widget.dropdown("hide");
this.$dropdownMenu.removeClass("show");
}
async dropdownShow() { }
}

View File

@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
export default class ShowHighlightsListWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}
constructor() {
super();
this.icon("bx-highlight")
.title(t("show_highlights_list_widget_button.show_highlights_list"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.highlightsListTemporarilyHidden = false;
appContext.triggerEvent("showHighlightsListWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}
async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.highlightsListTemporarilyHidden);
}
async reEvaluateHighlightsListWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'hideHighlightWidget')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
export default class ShowTocWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}
constructor() {
super();
this.icon("bx-objects-horizontal-left")
.title(t("show_toc_widget_button.show_toc"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.tocTemporarilyHidden = false;
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}
async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
}
async reEvaluateTocWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'toc')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@ -2,17 +2,16 @@ import server from "../../services/server.js";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import openService from "../../services/open.js";
const TPL = `
<div class="about-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 mr-auto">${t("about.title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t("about.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<table class="table table-borderless text-nowrap">
@ -72,7 +71,18 @@ export default class AboutDialog extends BasicWidget {
this.$buildDate.text(appInfo.buildDate);
this.$buildRevision.text(appInfo.buildRevision);
this.$buildRevision.attr('href', `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`);
this.$dataDirectory.text(appInfo.dataDirectory);
if (utils.isElectron()) {
this.$dataDirectory.html($('<a></a>', {
href: '#',
text: appInfo.dataDirectory,
}));
this.$dataDirectory.find("a").on('click', (event) => {
event.preventDefault();
openService.openDirectory(appInfo.dataDirectory);
})
} else {
this.$dataDirectory.text(appInfo.dataDirectory);
}
}
async openAboutDialogEvent() {

View File

@ -9,13 +9,9 @@ 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">${t('add_link.add_link')}</h5>
<h5 class="modal-title flex-grow-1">${t('add_link.add_link')}</h5>
<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="${t('add_link.close')}" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('add_link.close')}"></button>
</div>
<form class="add-link-form">
<div class="modal-body">

View File

@ -14,13 +14,9 @@ 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">${t('branch_prefix.edit_branch_prefix')}</h5>
<h5 class="modal-title flex-grow-1">${t('branch_prefix.edit_branch_prefix')}</h5>
<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="${t('branch_prefix.close')}" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('branch_prefix.close')}"></button>
</div>
<div class="modal-body">
<div class="form-group">
@ -28,10 +24,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
<div class="input-group">
<input class="branch-prefix-input form-control">
<div class="input-group-append">
<div class="branch-prefix-note-title input-group-text"></div>
</div>
<div class="branch-prefix-note-title input-group-text"></div>
</div>
</div>
</div>
@ -46,6 +39,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
export default class BranchPrefixDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".branch-prefix-form");
this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
this.$noteTitle = this.$widget.find('.branch-prefix-note-title');
@ -60,7 +54,7 @@ export default class BranchPrefixDialog extends BasicWidget {
}
async refresh(notePath) {
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
@ -97,9 +91,9 @@ export default class BranchPrefixDialog extends BasicWidget {
async savePrefix() {
const prefix = this.$treePrefixInput.val();
await server.put(`branches/${branchId}/set-prefix`, {prefix: prefix});
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
this.$widget.modal('hide');
this.modal.hide();
toastService.showMessage(t('branch_prefix.branch_prefix_saved'));
}

View File

@ -38,20 +38,15 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('bulk_actions.bulk_actions')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('bulk_actions.close')}">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t('bulk_actions.bulk_actions')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('bulk_actions.close')}"></button>
</div>
<div class="modal-body">
<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="">
${t('bulk_actions.include_descendants')}
</label>
<input class="include-descendants form-check-input" type="checkbox" value="">
<label class="form-check-label">${t('bulk_actions.include_descendants')}</label>
</div>
<h4>${t('bulk_actions.available_actions')}</h4>
@ -72,7 +67,6 @@ const TPL = `
export default class BulkActionsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$includeDescendants = this.$widget.find(".include-descendants");
this.$includeDescendants.on("change", () => this.refresh());

View File

@ -13,13 +13,9 @@ 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">${t('clone_to.clone_notes_to')}</h5>
<h5 class="modal-title flex-grow-1">${t('clone_to.clone_notes_to')}</h5>
<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>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="clone-to-form">
<div class="modal-body">
@ -81,9 +77,9 @@ export default class CloneToDialog extends BasicWidget {
});
}
async cloneNoteIdsToEvent({noteIds}) {
async cloneNoteIdsToEvent({ noteIds }) {
if (!noteIds || noteIds.length === 0) {
noteIds = [ appContext.tabManager.getActiveContextNoteId() ];
noteIds = [appContext.tabManager.getActiveContextNoteId()];
}
this.clonedNoteIds = [];
@ -111,7 +107,7 @@ export default class CloneToDialog extends BasicWidget {
}
async cloneNotesTo(notePath) {
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
for (const cloneNoteId of this.clonedNoteIds) {
@ -120,7 +116,7 @@ export default class CloneToDialog extends BasicWidget {
const clonedNote = await froca.getNote(cloneNoteId);
const targetNote = await froca.getBranch(targetBranchId).getNote();
toastService.showMessage(t('clone_to.note_cloned', {clonedTitle: clonedNote.title, targetTitle: targetNote.title}));
toastService.showMessage(t('clone_to.note_cloned', { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
}
}
}

View File

@ -8,11 +8,8 @@ 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">${t('confirm.confirmation')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t('confirm.confirmation')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="confirm-dialog-content"></div>
@ -40,6 +37,7 @@ export default class ConfirmDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$confirmContent = this.$widget.find(".confirm-dialog-content");
this.$okButton = this.$widget.find(".confirm-dialog-ok-button");
this.$cancelButton = this.$widget.find(".confirm-dialog-cancel-button");
@ -62,7 +60,7 @@ export default class ConfirmDialog extends BasicWidget {
this.$okButton.on('click', () => this.doResolve(true));
}
showConfirmDialogEvent({message, callback}) {
showConfirmDialogEvent({ message, callback }) {
this.$originallyFocused = $(':focus');
this.$custom.hide();
@ -75,15 +73,15 @@ export default class ConfirmDialog extends BasicWidget {
this.$confirmContent.empty().append(message);
this.$widget.modal();
this.modal.show();
this.resolve = callback;
}
showConfirmDeleteNoteBoxWithNoteDialogEvent({title, callback}) {
showConfirmDeleteNoteBoxWithNoteDialogEvent({ title, callback }) {
glob.activeDialog = this.$widget;
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', {title: title})}`);
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', { title: title })}`);
this.$custom.empty()
.append("<br/>")
@ -104,7 +102,7 @@ export default class ConfirmDialog extends BasicWidget {
this.$custom.show();
this.$widget.modal();
this.modal.show();
this.resolve = callback;
}
@ -117,6 +115,6 @@ export default class ConfirmDialog extends BasicWidget {
this.resolve = null;
this.$widget.modal("hide");
this.modal.hide();
}
}

View File

@ -10,27 +10,18 @@ const TPL = `
<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">${t('delete_notes.delete_notes_preview')}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">${t('delete_notes.delete_notes_preview')}</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="checkbox">
<label>
<input class="delete-all-clones" value="1" type="checkbox">
${t('delete_notes.delete_all_clones_description')}
</label>
<div class="form-checkbox">
<input class="delete-all-clones form-check-input" value="1" type="checkbox">
<label class="form-check-label">${t('delete_notes.delete_all_clones_description')}</label>
</div>
<div class="checkbox">
<label title="${t('delete_notes.erase_notes_description')}">
<input class="erase-notes" value="1" type="checkbox">
${t('delete_notes.erase_notes_warning')}
</label>
<div class="form-checkbox" style="margin-bottom: 1rem">
<input class="erase-notes form-check-input" value="1" type="checkbox">
<label class="form-check-label">${t('delete_notes.erase_notes_warning')}</label>
</div>
<div class="delete-notes-list-wrapper">

View File

@ -34,9 +34,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('export.close')}"></button>
</div>
<form class="export-form">
<div class="modal-body">
@ -127,6 +125,7 @@ export default class ExportDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".export-form");
this.$noteTitle = this.$widget.find(".export-note-title");
this.$subtreeFormats = this.$widget.find(".export-subtree-formats");
@ -137,7 +136,7 @@ export default class ExportDialog extends BasicWidget {
this.$opmlVersions = this.$widget.find(".opml-versions");
this.$form.on('submit', () => {
this.$widget.modal('hide');
this.modal.hide();
const exportType = this.$widget.find("input[name='export-type']:checked").val();
@ -188,7 +187,7 @@ export default class ExportDialog extends BasicWidget {
});
}
async showExportDialogEvent({notePath, defaultType}) {
async showExportDialogEvent({ notePath, defaultType }) {
this.taskId = '';
this.$exportButton.removeAttr("disabled");
@ -208,7 +207,7 @@ export default class ExportDialog extends BasicWidget {
utils.openDialog(this.$widget);
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
this.branchId = await froca.getBranchId(parentNoteId, noteId);
this.$noteTitle.text(await treeService.getNoteTitle(noteId));

View File

@ -7,14 +7,11 @@ const TPL = `
<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">${t('help.fullDocumentation')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('help.close')}">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t('help.fullDocumentation')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('help.close')}"></button>
</div>
<div class="modal-body" style="overflow: auto; height: calc(100vh - 70px);">
<div class="card-columns help-cards">
<div class="help-cards row row-cols-3 g-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">${t('help.noteNavigation')}</h5>

View File

@ -11,9 +11,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('import.close')}"></button>
</div>
<form class="import-form">
<div class="modal-body">
@ -29,21 +27,21 @@ const TPL = `
<strong>${t('import.options')}:</strong>
<div class="checkbox">
<label data-toggle="tooltip" title="${t('import.safeImportTooltip')}">
<label data-bs-toggle="tooltip" title="${t('import.safeImportTooltip')}">
<input class="safe-import-checkbox" value="1" type="checkbox" checked>
<span>${t('import.safeImport')}</span>
</label>
</div>
<div class="checkbox">
<label data-toggle="tooltip" title="${t('import.explodeArchivesTooltip')}">
<label data-bs-toggle="tooltip" title="${t('import.explodeArchivesTooltip')}">
<input class="explode-archives-checkbox" value="1" type="checkbox" checked>
<span>${t('import.explodeArchives')}</span>
</label>
</div>
<div class="checkbox">
<label data-toggle="tooltip" title="${t('import.shrinkImagesTooltip')}">
<label data-bs-toggle="tooltip" title="${t('import.shrinkImagesTooltip')}">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t('import.shrinkImages')}</span>
</label>
</div>
@ -86,6 +84,8 @@ export default class ImportDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".import-form");
this.$noteTitle = this.$widget.find(".import-note-title");
this.$fileUploadInput = this.$widget.find(".import-file-upload-input");
@ -115,12 +115,14 @@ export default class ImportDialog extends BasicWidget {
}
});
this.$widget.find('[data-toggle="tooltip"]').tooltip({
html: true
let _ = [...this.$widget.find('[data-bs-toggle="tooltip"]')].forEach(element => {
bootstrap.Tooltip.getOrCreateInstance(element, {
html: true
});
});
}
async showImportDialogEvent({noteId}) {
async showImportDialogEvent({ noteId }) {
this.parentNoteId = noteId;
this.$fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below

View File

@ -11,9 +11,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="include-note-form">
<div class="modal-body">
@ -27,22 +25,16 @@ const TPL = `
${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">
${t('include_note.box_size_small')}
</label>
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
<label class="form-check-label">${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>
${t('include_note.box_size_medium')}
</label>
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
<label class="form-check-label">${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">
${t('include_note.box_size_full')}
</label>
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
<label class="form-check-label">${t('include_note.box_size_full')}</label>
</div>
</div>
<div class="modal-footer">
@ -56,13 +48,14 @@ const TPL = `
export default class IncludeNoteDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".include-note-form");
this.$autoComplete = this.$widget.find(".include-note-autocomplete");
this.$form.on('submit', () => {
const notePath = this.$autoComplete.getSelectedNotePath();
if (notePath) {
this.$widget.modal('hide');
this.modal.hide();
this.includeNote(notePath);
} else {
logError("No noteId to include.");
@ -72,7 +65,7 @@ export default class IncludeNoteDialog extends BasicWidget {
})
}
async showIncludeNoteDialogEvent({textTypeWidget}) {
async showIncludeNoteDialogEvent({ textTypeWidget }) {
this.textTypeWidget = textTypeWidget;
await this.refresh();
utils.openDialog(this.$widget);

View File

@ -7,11 +7,8 @@ const TPL = `
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("info.modalTitle")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t("info.closeButton")}">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t("info.modalTitle")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("info.closeButton")}"></button>
</div>
<div class="modal-body">
<div class="info-dialog-content"></div>
@ -33,6 +30,7 @@ export default class InfoDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$infoContent = this.$widget.find(".info-dialog-content");
this.$okButton = this.$widget.find(".info-dialog-ok-button");
@ -49,10 +47,10 @@ export default class InfoDialog extends BasicWidget {
}
});
this.$okButton.on('click', () => this.$widget.modal("hide"));
this.$okButton.on('click', () => this.modal.hide());
}
showInfoDialogEvent({message, callback}) {
showInfoDialogEvent({ message, callback }) {
this.$originallyFocused = $(':focus');
this.$infoContent.text(message);

View File

@ -12,10 +12,7 @@ const TPL = `<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="
<div class="input-group">
<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">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="algolia-autocomplete-container jump-to-note-results"></div>
@ -38,6 +35,8 @@ export default class JumpToNoteDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$autoComplete = this.$widget.find(".jump-to-note-autocomplete");
this.$results = this.$widget.find(".jump-to-note-results");
this.$showInFullTextButton = this.$widget.find(".show-in-full-text-button");
@ -94,8 +93,8 @@ export default class JumpToNoteDialog extends BasicWidget {
const searchString = this.$autoComplete.val();
this.triggerCommand('searchNotes', {searchString});
this.triggerCommand('searchNotes', { searchString });
this.$widget.modal('hide');
this.modal.hide();
}
}

View File

@ -12,9 +12,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>${t("markdown_import.modal_body_text")}</p>
@ -37,6 +35,7 @@ export default class MarkdownImportDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$importTextarea = this.$widget.find('.markdown-import-textarea');
this.$importButton = this.$widget.find('.markdown-import-button');
@ -48,7 +47,7 @@ export default class MarkdownImportDialog extends BasicWidget {
}
async convertMarkdownToHtml(markdownContent) {
const {htmlContent} = await server.post('other/render-markdown', { markdownContent });
const { htmlContent } = await server.post('other/render-markdown', { markdownContent });
const textEditor = await appContext.tabManager.getActiveContext().getTextEditor();
@ -70,7 +69,7 @@ export default class MarkdownImportDialog extends BasicWidget {
}
if (utils.isElectron()) {
const {clipboard} = utils.dynamicRequire('electron');
const { clipboard } = utils.dynamicRequire('electron');
const text = clipboard.readText();
this.convertMarkdownToHtml(text);
@ -83,7 +82,7 @@ export default class MarkdownImportDialog extends BasicWidget {
async sendForm() {
const text = this.$importTextarea.val();
this.$widget.modal('hide');
this.modal.hide();
await this.convertMarkdownToHtml(text);

View File

@ -5,18 +5,15 @@ 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
import { t } from "../../services/i18n.js";
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">${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>
</button>
<h5 class="modal-title me-auto">${t("move_to.dialog_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="move-to-form">
<div class="modal-body">

View File

@ -23,17 +23,16 @@ 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">${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>
<h5 class="modal-title">${t("note_type_chooser.modal_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
${t("note_type_chooser.modal_body")}
<div class="dropdown">
<button class="note-type-dropdown-trigger" type="button" style="display: none;" data-toggle="dropdown"></button>
<div class="dropdown" style="display: flex;">
<button class="note-type-dropdown-trigger" type="button" style="display: none;"
data-bs-toggle="dropdown" data-bs-display="static">
</button>
<div class="note-type-dropdown dropdown-menu"></div>
</div>
@ -53,13 +52,14 @@ export default class NoteTypeChooserDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
this.$noteTypeDropdownTrigger = this.$widget.find(".note-type-dropdown-trigger");
this.$noteTypeDropdownTrigger.dropdown();
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find(".note-type-dropdown-trigger"));
this.$widget.on("hidden.bs.modal", () => {
if (this.resolve) {
this.resolve({success: false});
this.resolve({ success: false });
}
if (this.$originalFocused) {
@ -94,7 +94,7 @@ export default class NoteTypeChooserDialog extends BasicWidget {
});
}
async chooseNoteTypeEvent({callback}) {
async chooseNoteTypeEvent({ callback }) {
this.$originalFocused = $(':focus');
const noteTypes = await noteTypesService.getNoteTypeItems();
@ -116,11 +116,11 @@ export default class NoteTypeChooserDialog extends BasicWidget {
}
}
this.$noteTypeDropdownTrigger.dropdown('show');
this.dropdown.show();
this.$originalDialog = glob.activeDialog;
glob.activeDialog = this.$widget;
this.$widget.modal();
glob.activeDialog = this.modal;
this.modal.show();
this.$noteTypeDropdown.find(".dropdown-item:first").focus();
@ -139,6 +139,6 @@ export default class NoteTypeChooserDialog extends BasicWidget {
});
this.resolve = null;
this.$widget.modal("hide");
this.modal.hide();
}
}

View File

@ -7,11 +7,8 @@ const TPL = `
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-header">
<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>
<h5 class="modal-title">${t("password_not_set.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
${t("password_not_set.body1")}
@ -26,8 +23,10 @@ const TPL = `
export default class PasswordNoteSetDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$openPasswordOptionsButton = this.$widget.find(".open-password-options-button");
this.$openPasswordOptionsButton.on("click", () => {
this.modal.hide();
this.triggerCommand("showOptions", { section: '_optionsPassword' });
});
}

View File

@ -8,13 +8,10 @@ const TPL = `
<div class="modal-content">
<form class="prompt-dialog-form">
<div class="modal-header">
<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>
</div>
<div class="modal-body">
<h5 class="prompt-title modal-title">${t("prompt.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button class="prompt-dialog-ok-button btn btn-primary btn-sm">${t("prompt.ok")}</button>
</div>
@ -33,6 +30,7 @@ export default class PromptDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$dialogBody = this.$widget.find(".modal-body");
this.$form = this.$widget.find(".prompt-dialog-form");
this.$question = null;
@ -61,7 +59,7 @@ export default class PromptDialog extends BasicWidget {
e.preventDefault();
this.resolve(this.$answer.val());
this.$widget.modal('hide');
this.modal.hide();
});
}

View File

@ -8,22 +8,14 @@ const TPL = `
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("protected_session_password.modal_title")}</h5>
<h5 class="modal-title flex-grow-1">${t("protected_session_password.modal_title")}</h5>
<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="${t("protected_session_password.close_label")}" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("protected_session_password.close_label")}"></button>
</div>
<form class="protected-session-password-form">
<div class="modal-body">
<div class="form-group">
<label>
${t("protected_session_password.form_label")}
<input class="form-control protected-session-password" type="password">
</label>
</div>
<label class="col-form-label">${t("protected_session_password.form_label")}</label>
<input class="form-control protected-session-password" type="password">
</div>
<div class="modal-footer">
<button class="btn btn-primary">${t("protected_session_password.start_button")}</button>
@ -36,6 +28,8 @@ const TPL = `
export default class ProtectedSessionPasswordDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$passwordForm = this.$widget.find(".protected-session-password-form");
this.$passwordInput = this.$widget.find(".protected-session-password");
this.$passwordForm.on('submit', () => {
@ -55,6 +49,6 @@ export default class ProtectedSessionPasswordDialog extends BasicWidget {
}
closeProtectedSessionPasswordDialogEvent() {
this.$widget.modal('hide');
this.modal.hide();
}
}

View File

@ -15,14 +15,9 @@ 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">${t('recent_changes.title')}</h5>
<button class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px">
${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>
</button>
<h5 class="modal-title flex-grow-1">${t('recent_changes.title')}</h5>
<button class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px">${t('recent_changes.erase_notes_button')}</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="recent-changes-content"></div>
@ -34,6 +29,8 @@ const TPL = `
export default class RecentChangesDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$content = this.$widget.find(".recent-changes-content");
this.$eraseDeletedNotesNow = this.$widget.find(".erase-deleted-notes-now-button");
this.$eraseDeletedNotesNow.on("click", () => {
@ -45,7 +42,7 @@ export default class RecentChangesDialog extends BasicWidget {
});
}
async showRecentChangesEvent({ancestorNoteId}) {
async showRecentChangesEvent({ ancestorNoteId }) {
this.ancestorNoteId = ancestorNoteId;
await this.refresh();
@ -93,7 +90,7 @@ export default class RecentChangesDialog extends BasicWidget {
if (await dialogService.confirm(text)) {
await server.put(`notes/${change.noteId}/undelete`);
this.$widget.modal('hide');
this.modal.hide();
await ws.waitForMaxKnownEntityChangeId();

View File

@ -40,21 +40,18 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("revisions.note_revisions")}</h5>
<h5 class="modal-title flex-grow-1">${t("revisions.note_revisions")}</h5>
<button class="revisions-erase-all-revisions-button btn btn-sm"
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="${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>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</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"></button>
<button class="revision-list-dropdown" type="button" style="display: none;"
data-bs-toggle="dropdown" data-bs-display="static">
</button>
<div class="revision-list dropdown-menu" style="position: static; height: 100%; overflow: auto;"></div>
</div>
@ -84,6 +81,8 @@ export default class RevisionsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$list = this.$widget.find(".revision-list");
this.$listDropdown = this.$widget.find(".revision-list-dropdown");
this.$content = this.$widget.find(".revision-content");
@ -111,7 +110,7 @@ export default class RevisionsDialog extends BasicWidget {
if (await dialogService.confirm(text)) {
await server.remove(`notes/${this.note.noteId}/revisions`);
this.$widget.modal('hide');
this.modal.hide();
toastService.showMessage(t("revisions.revisions_deleted"));
}
@ -131,7 +130,7 @@ export default class RevisionsDialog extends BasicWidget {
});
}
async showRevisionsEvent({noteId = appContext.tabManager.getActiveContextNoteId()}) {
async showRevisionsEvent({ noteId = appContext.tabManager.getActiveContextNoteId() }) {
utils.openDialog(this.$widget);
await this.loadRevisions(noteId);
@ -191,7 +190,7 @@ export default class RevisionsDialog extends BasicWidget {
if (await dialogService.confirm(text)) {
await server.post(`revisions/${revisionItem.revisionId}/restore`);
this.$widget.modal('hide');
this.modal.hide();
toastService.showMessage(t("revisions.revision_restored"));
}
@ -241,7 +240,7 @@ export default class RevisionsDialog extends BasicWidget {
if (this.$content.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement(this.$content[0], {trust: true});
renderMathInElement(this.$content[0], { trust: true });
}
} else if (revisionItem.type === 'code') {
this.$content.html($("<pre>").text(fullRevision.content));
@ -274,7 +273,7 @@ export default class RevisionsDialog extends BasicWidget {
}
this.$content.html($table);
} else if ([ "canvas", "mindMap" ].includes(revisionItem.type)) {
} else if (["canvas", "mindMap"].includes(revisionItem.type)) {
const encodedTitle = encodeURIComponent(revisionItem.title);
this.$content.html($("<img>")

View File

@ -7,61 +7,45 @@ 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">${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>
<h5 class="modal-title">${t("sort_child_notes.sort_children_by")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="sort-child-notes-form">
<div class="modal-body">
<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>
${t("sort_child_notes.title")}
</label>
<input class="form-check-input" type="radio" name="sort-by" value="title" checked>
<label class="form-check-label">${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">
${t("sort_child_notes.date_created")}
</label>
<input class="form-check-input" type="radio" name="sort-by" value="dateCreated">
<label class="form-check-label">${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">
${t("sort_child_notes.date_modified")}
</label>
<input class="form-check-input" type="radio" name="sort-by" value="dateModified">
<label class="form-check-label">${t("sort_child_notes.date_modified")}</label>
</div>
<br/>
<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>
${t("sort_child_notes.ascending")}
</label>
<input class="form-check-input" type="radio" name="sort-direction" value="asc" checked>
<label class="form-check-label">${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">
${t("sort_child_notes.descending")}
</label>
<input class="form-check-input" type="radio" name="sort-direction" value="desc">
<label class="form-check-label">${t("sort_child_notes.descending")}</label>
</div>
<br />
<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">
${t("sort_child_notes.sort_folders_at_top")}
</label>
<input class="form-check-input" type="checkbox" name="sort-folders-first" value="1">
<label class="form-check-label">${t("sort_child_notes.sort_folders_at_top")}</label>
</div>
<br />
<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">
${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
</label>
<input class="form-check-input" type="checkbox" name="sort-natural" value="1">
<label class="form-check-label">${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}</label>
</div>
<br />
<div class="form-check">

View File

@ -11,9 +11,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="upload-attachment-form">
<div class="modal-body">
@ -26,8 +24,8 @@ const TPL = `
<div class="form-group">
<strong>${t("upload_attachments.options")}:</strong>
<div class="checkbox">
<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 data-bs-toggle="tooltip" title="${t("upload_attachments.tooltip")}">
<input class="shrink-images-checkbox form-check-input" value="1" type="checkbox" checked> <span>${t("upload_attachments.shrink_images")}</span>
</label>
</div>
</div>
@ -49,6 +47,8 @@ export default class UploadAttachmentsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".upload-attachment-form");
this.$noteTitle = this.$widget.find(".upload-attachment-note-title");
this.$fileUploadInput = this.$widget.find(".upload-attachment-file-upload-input");
@ -71,12 +71,12 @@ export default class UploadAttachmentsDialog extends BasicWidget {
}
});
this.$widget.find('[data-toggle="tooltip"]').tooltip({
bootstrap.Tooltip.getOrCreateInstance(this.$widget.find('[data-bs-toggle="tooltip"]'), {
html: true
});
}
async showUploadAttachmentsDialogEvent({noteId}) {
async showUploadAttachmentsDialogEvent({ noteId }) {
this.parentNoteId = noteId;
this.$fileUploadInput.val('').trigger('change'); // to trigger upload button disabling listener below
@ -96,7 +96,7 @@ export default class UploadAttachmentsDialog extends BasicWidget {
shrinkImages: boolToString(this.$shrinkImagesCheckbox),
};
this.$widget.modal('hide');
this.modal.hide();
await importService.uploadFiles('attachments', parentNoteId, files, options);
}

View File

@ -15,7 +15,7 @@ const TPL = `
white-space: normal;
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle editability-button">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle editability-button">
<span class="editability-active-desc">${t("editability_select.auto")}</span>
<span class="caret"></span>
</button>
@ -43,11 +43,13 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$editabilityActiveDesc = this.$widget.find(".editability-active-desc");
this.$widget.on('click', '.dropdown-item',
async e => {
this.$widget.find('.dropdown-toggle').dropdown('toggle');
this.dropdown.toggle();
const editability = $(e.target).closest("[data-editability]").attr("data-editability");
@ -85,7 +87,7 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget {
this.$editabilityActiveDesc.text(labels[editability]);
}
entitiesReloadedEvent({loadResults}) {
entitiesReloadedEvent({ loadResults }) {
if (loadResults.getAttributeRows().find(attr => attr.noteId === this.noteId)) {
this.refresh();
}

View File

@ -3,6 +3,7 @@
* https://github.com/antoniotejada/Trilium-FindWidget
*/
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import FindInText from "./find_in_text.js";
import FindInCode from "./find_in_code.js";
@ -47,24 +48,18 @@ const TPL = `
<div class="find-widget-box">
<div class="input-group find-widget-search-term-input-group">
<input type="text" class="form-control find-widget-search-term-input">
<div class="input-group-append">
<button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button>
<button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
</div>
<button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button>
<button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
</div>
<div class="form-check">
<label tabIndex="-1" class="form-check-label">
<input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">
case sensitive
</label>
<input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">
<label tabIndex="-1" class="form-check-label">${t('find.case_sensitive')}</label>
</div>
<div class="form-check">
<label tabIndex="-1" class="form-check-label">
<input type="checkbox" class="form-check-input find-widget-match-words-checkbox">
match words
</label>
<input type="checkbox" class="form-check-input find-widget-match-words-checkbox">
<label tabIndex="-1" class="form-check-label">${t('find.match_words')}</label>
</div>
<div class="find-widget-found-wrapper">
@ -279,7 +274,7 @@ export default class FindWidget extends NoteContextAwareWidget {
async entitiesReloadedEvent({loadResults}) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
this.$totalFound.text("?")
}
this.$totalFound.text("?")
}
}
}

View File

@ -5,11 +5,13 @@
* - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries
*/
import { t } from "../services/i18n.js";
import attributeService from "../services/attributes.js";
import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js";
import appContext from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
const TPL = `<div class="highlights-list-widget">
<style>
@ -28,7 +30,6 @@ const TPL = `<div class="highlights-list-widget">
cursor: pointer;
margin-bottom: 3px;
text-align: justify;
text-justify: distribute;
word-wrap: break-word;
hyphens: auto;
}
@ -43,16 +44,16 @@ const TPL = `<div class="highlights-list-widget">
export default class HighlightsListWidget extends RightPanelWidget {
get widgetTitle() {
return "Highlights List";
return t("highlights_list_2.title");
}
get widgetButtons() {
return [
new OnClickButtonWidget()
.icon("bx-slider")
.title("Options")
.icon("bx-cog")
.title(t("highlights_list_2.options"))
.titlePlacement("left")
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true}))
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', { activate: true }))
.class("icon-action"),
new OnClickButtonWidget()
.icon("bx-x")
@ -97,8 +98,8 @@ export default class HighlightsListWidget extends RightPanelWidget {
let $highlightsList = "", hlLiCount = -1;
// Check for type text unconditionally in case alwaysShowWidget is set
if (this.note.type === 'text') {
const {content} = await note.getNoteComplement();
({$highlightsList, hlLiCount} = this.getHighlightList(content, optionsHighlightsList));
const { content } = await note.getNoteComplement();
({ $highlightsList, hlLiCount } = await this.getHighlightList(content, optionsHighlightsList));
}
this.$highlightsList.empty().append($highlightsList);
if (hlLiCount > 0) {
@ -112,7 +113,79 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility");
}
getHighlightList(content, optionsHighlightsList) {
extractOuterTag(htmlStr) {
if (htmlStr === null) {
return null
}
// Regular expressions that match only the outermost tag
const regex = /^<([a-zA-Z]+)([^>]*)>/;
const match = htmlStr.match(regex);
if (match) {
const tagName = match[1].toLowerCase(); // Extract tag name
const attributes = match[2].trim(); // Extract label attributes
return { tagName, attributes };
}
return null;
}
areOuterTagsConsistent(str1, str2) {
const tag1 = this.extractOuterTag(str1);
const tag2 = this.extractOuterTag(str2);
// If one of them has no label, returns false
if (!tag1 || !tag2) {
return false;
}
// Compare tag names and attributes to see if they are the same
return tag1.tagName === tag2.tagName && tag1.attributes === tag2.attributes;
}
/**
* Rendering formulas in strings using katex
*
* @param {string} html Note's html content
* @returns {string} The HTML content with mathematical formulas rendered by KaTeX.
*/
async replaceMathTextWithKatax(html) {
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
var matches = [...html.matchAll(mathTextRegex)];
let modifiedText = html;
if (matches.length > 0) {
// Process all matches asynchronously
for (const match of matches) {
let latexCode = match[1];
let rendered;
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (e) {
if (e instanceof ReferenceError && e.message.includes('katex is not defined')) {
// Load KaTeX if it is not already loaded
await libraryLoader.requireLibrary(libraryLoader.KATEX);
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (renderError) {
console.error("KaTeX rendering error after loading library:", renderError);
rendered = match[0]; // Fall back to original if error persists
}
} else {
console.error("KaTeX rendering error:", e);
rendered = match[0]; // Fall back to original on error
}
}
// Replace the matched formula in the modified text
modifiedText = modifiedText.replace(match[0], rendered);
}
}
return modifiedText;
}
async getHighlightList(content, optionsHighlightsList) {
// matches a span containing background-color
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
// matches a span containing color
@ -152,6 +225,10 @@ export default class HighlightsListWidget extends RightPanelWidget {
const combinedRegex = new RegExp(combinedRegexStr, 'gi');
const $highlightsList = $("<ol>");
let prevEndIndex = -1, hlLiCount = 0;
let prevSubHtml = null;
// Used to determine if a string is only a formula
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;
for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
const subHtml = match[0];
const startIndex = match.index;
@ -166,11 +243,19 @@ export default class HighlightsListWidget extends RightPanelWidget {
const hasText = $(subHtml).text().trim();
if (hasText) {
$highlightsList.append(
$('<li>')
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
const substring = content.substring(prevEndIndex, startIndex);
//If the two elements have the same style and there are only formulas in between, append the formulas and the current element to the end of the previous element.
if (this.areOuterTagsConsistent(prevSubHtml, subHtml) && onlyMathRegex.test(substring)) {
const $lastLi = $highlightsList.children('li').last();
$lastLi.append(await this.replaceMathTextWithKatax(substring));
$lastLi.append(subHtml);
} else {
$highlightsList.append(
$('<li>')
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
}
hlLiCount++;
} else {
@ -179,6 +264,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
}
}
prevEndIndex = endIndex;
prevSubHtml = subHtml;
}
return {
$highlightsList,
@ -232,9 +318,17 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.noteContext.viewScope.highlightsListTemporarilyHidden = true;
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
appContext.triggerEvent("reEvaluateHighlightsListWidgetVisibility", { noteId: this.noteId });
}
async entitiesReloadedEvent({loadResults}) {
async showHighlightsListWidgetEvent({ noteId }) {
if (this.noteId === noteId) {
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'

View File

@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import libraryLoader from "../services/library_loader.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import server from "../services/server.js";
@ -27,7 +28,7 @@ const TPL = `<div class="mermaid-widget">
</style>
<div class="mermaid-error alert alert-warning">
<p><strong>The diagram could not be displayed. See <a href="https://mermaid-js.github.io/mermaid/#/flowchart?id=graph">help and examples</a>.</strong></p>
<p><strong>${t('mermaid.diagram_error')}</strong></p>
<p class="error-content"></p>
</div>

View File

@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import attributeService from "../services/attributes.js";
import server from "../services/server.js";
@ -66,7 +67,7 @@ const TPL = `
}
</style>
<button class="btn dropdown-toggle note-icon" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Change note icon"></button>
<button class="btn dropdown-toggle note-icon" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="${t("note_icon.change_note_icon")}"></button>
<div class="dropdown-menu" aria-labelledby="note-path-list-button" style="width: 610px;">
<div class="filter-row">
<span>Category:</span> <select name="icon-category" class="form-control"></select>

View File

@ -106,7 +106,7 @@ const TPL = `
title="Scroll to active note"
data-trigger-command="scrollToActiveNote"></button>
<button class="tree-floating-button bx bx-cog tree-settings-button"
<button class="tree-floating-button bx bxs-tree tree-settings-button"
title="Tree settings"></button>
</div>

View File

@ -35,7 +35,7 @@ const TPL = `
overflow-x: hidden;
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle note-type-button">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle note-type-button">
<span class="note-type-desc"></span>
<span class="caret"></span>
</button>
@ -47,14 +47,15 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$widget.on('show.bs.dropdown', () => this.renderDropdown());
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
this.$noteTypeButton = this.$widget.find(".note-type-button");
this.$noteTypeDesc = this.$widget.find(".note-type-desc");
this.$widget.on('click', '.dropdown-item',
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
}
async refreshWithNote(note) {
@ -63,7 +64,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime));
this.$noteTypeButton.dropdown('hide');
this.dropdown.hide();
}
/** the actual body is rendered lazily on note-type button click */
@ -92,7 +93,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
.append('<span class="check">&check;</span> ')
.append($('<strong>').text(noteType.title));
}
if (this.note.type === noteType.type) {
$typeLink.addClass("selected");
}
@ -161,7 +162,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
return await dialogService.confirm("It is not recommended to change note type when note content is not empty. Do you want to continue anyway?");
}
async entitiesReloadedEvent({loadResults}) {
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteReloaded(this.noteId)) {
this.refresh();
}

View File

@ -30,13 +30,12 @@ const TPL = `
</style>
<div class="input-group-prepend">
<button class="btn btn-outline-secondary search-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-secondary search-button" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="bx bx-search"></span>
</button>
<div class="dropdown-menu dropdown-menu-left"></div>
</div>
<input type="text" class="form-control form-control-sm search-string" placeholder="Quick search">
</div>
</div>`;
const MAX_DISPLAYED_NOTES = 15;
@ -44,21 +43,20 @@ const MAX_DISPLAYED_NOTES = 15;
export default class QuickSearchWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$searchString = this.$widget.find('.search-string');
this.$dropdownMenu = this.$widget.find('.dropdown-menu');
this.$dropdownToggle = this.$widget.find('.search-button');
this.$dropdownToggle.dropdown();
this.$widget.find('.input-group-prepend').on('shown.bs.dropdown', () => this.search());
if(utils.isMobile()) {
this.$searchString.keydown(e =>{
if(e.which === 13) {
if (utils.isMobile()) {
this.$searchString.keydown(e => {
if (e.which === 13) {
if (this.$dropdownMenu.is(":visible")) {
this.search(); // just update already visible dropdown
} else {
this.$dropdownToggle.dropdown('show');
this.dropdown.show();
}
e.preventDefault();
e.stopPropagation();
@ -70,7 +68,7 @@ export default class QuickSearchWidget extends BasicWidget {
if (this.$dropdownMenu.is(":visible")) {
this.search(); // just update already visible dropdown
} else {
this.$dropdownToggle.dropdown('show');
this.dropdown.show();
}
this.$searchString.focus();
@ -81,7 +79,7 @@ export default class QuickSearchWidget extends BasicWidget {
});
shortcutService.bindElShortcut(this.$searchString, 'esc', () => {
this.$dropdownToggle.dropdown('hide');
this.dropdown.hide();
});
return this.$widget;
@ -91,25 +89,25 @@ export default class QuickSearchWidget extends BasicWidget {
const searchString = this.$searchString.val().trim();
if (!searchString) {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
return;
}
this.$dropdownMenu.empty();
this.$dropdownMenu.append('<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span> Searching ...</span>');
const {searchResultNoteIds, error} = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
if (error) {
this.$searchString.tooltip({
let tooltip = new bootstrap.Tooltip(this.$searchString, {
trigger: 'manual',
title: `Search error: ${error}`,
placement: 'right'
});
this.$searchString.tooltip("show");
tooltip.show();
setTimeout(() => this.$searchString.tooltip("dispose"), 4000);
setTimeout(() => tooltip.dispose(), 4000);
}
const displayedNoteIds = searchResultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResultNoteIds.length));
@ -121,11 +119,11 @@ export default class QuickSearchWidget extends BasicWidget {
}
for (const note of await froca.getNotes(displayedNoteIds)) {
const $link = await linkService.createLink(note.noteId, {showNotePath: true});
const $link = await linkService.createLink(note.noteId, { showNotePath: true });
$link.addClass('dropdown-item');
$link.attr("tabIndex", "0");
$link.on('click', e => {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
if (!e.target || e.target.nodeName !== 'A') {
// click on the link is handled by link handling, but we want the whole item clickable
@ -133,7 +131,7 @@ export default class QuickSearchWidget extends BasicWidget {
}
});
shortcutService.bindElShortcut($link, 'return', () => {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
appContext.tabManager.getActiveContext().setNote(note.noteId);
});
@ -156,11 +154,11 @@ export default class QuickSearchWidget extends BasicWidget {
shortcutService.bindElShortcut(this.$dropdownMenu.find('.dropdown-item:first'), 'up', () => this.$searchString.focus());
this.$dropdownToggle.dropdown('update');
this.dropdown.update();
}
async showInFullSearch() {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
await appContext.triggerCommand('searchNotes', {
searchString: this.$searchString.val()

View File

@ -18,7 +18,7 @@ const TPL = `
<div style="display: flex; align-items: baseline">
<span style="white-space: nowrap">${t("book_properties.view_type")}:&nbsp; &nbsp;</span>
<select class="view-type-select form-control form-control-sm">
<select class="view-type-select form-select form-select-sm">
<option value="grid">${t("book_properties.grid")}</option>
<option value="list">${t("book_properties.list")}</option>
</select>

View File

@ -52,7 +52,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
title: t("note_map.title"),
icon: 'bx bx-map-alt'
icon: 'bx bxs-network-chart'
};
}

View File

@ -71,7 +71,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
if (promotedDefAttrs.length === 0) {
return {show: false};
return { show: false };
}
return {
@ -167,7 +167,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
return;
}
attributeValues = attributeValues.map(attribute => ({value: attribute}));
attributeValues = attributeValues.map(attribute => ({ value: attribute }));
$input.autocomplete({
appendTo: document.querySelector('body'),
@ -229,9 +229,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
.prop("title", t("promoted_attributes.open_external_link"))
.on('click', () => window.open($input.val(), '_blank'));
$input.after($("<div>")
.addClass("input-group-append")
.append($openButton));
$input.after($openButton);
}
else {
ws.logError(t("promoted_attributes.unknown_label_type", { type: definitionAttr.labelType }));
@ -244,7 +242,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
if (utils.isDesktop()) {
// no need to wait for this
noteAutocompleteService.initNoteAutocomplete($input, {allowCreatingNotes: true});
noteAutocompleteService.initNoteAutocomplete($input, { allowCreatingNotes: true });
$input.on('autocomplete:noteselected', (event, suggestion, dataset) => {
this.promotedAttributeChanged(event);
@ -257,7 +255,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
}
}
else {
ws.logError(t(`promoted_attributes.unknown_attribute_type`, {type: valueAttr.type}));
ws.logError(t(`promoted_attributes.unknown_attribute_type`, { type: valueAttr.type }));
return;
}
@ -346,7 +344,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
this.$widget.find(".promoted-attribute-input:first").focus();
}
entitiesReloadedEvent({loadResults}) {
entitiesReloadedEvent({ loadResults }) {
if (loadResults.getAttributeRows(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refresh();
}

View File

@ -119,7 +119,7 @@ const TPL = `
</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">
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="bx bxs-zap"></span>
${t('search_definition.action')}
</button>
@ -219,7 +219,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
});
this.$widget.on('click', '[data-action-add]', async event => {
this.$widget.find('.action-add-toggle').dropdown('toggle');
bootstrap.Dropdown.getOrCreateInstance(this.$widget.find('.action-add-toggle'));
const actionName = $(event.target).attr('data-action-add');

View File

@ -13,7 +13,7 @@ const TPL = `
<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">
<select name="depth" class="form-select d-inline ancestor-depth" style="flex-shrink: 3">
<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>

View File

@ -9,10 +9,10 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("debug.debug_info")}</p>
<p>${t("debug.access_info")}</p>
${t("debug.access_info")}
</div>
</div>
<span class="bx bx-x icon-action search-option-del"></span>

View File

@ -9,7 +9,7 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('fast_search.description')}
</div>

View File

@ -12,7 +12,7 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('limit.take_first_x_results')}
</div>

View File

@ -14,7 +14,7 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('search_script.description1')}</p>
@ -24,7 +24,7 @@ const TPL = `
<pre>${t('search_script.example_code')}</pre>
<p>${t('search_script.note')}</p>
${t('search_script.note')}
</div>
</div>

View File

@ -13,11 +13,11 @@ const TPL = `
</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>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<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>
<strong>${t('search_string.search_syntax')}</strong> - ${t('search_string.also_see')} <a href="#" data-help-page="search.html">${t('search_string.complete_help')}</a>
<ul style="marigin-bottom: 0;">
<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>
@ -26,7 +26,6 @@ const TPL = `
<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>
</div>
@ -74,16 +73,16 @@ export default class SearchString extends AbstractSearchOption {
return $option;
}
showSearchErrorEvent({error}) {
this.$searchString.tooltip({
showSearchErrorEvent({ error }) {
let tooltip = new bootstrap.Tooltip(this.$searchString, {
trigger: 'manual',
title: `${t('search_string.error', {error})}`,
title: `${t('search_string.error', { error })}`,
placement: 'bottom'
});
this.$searchString.tooltip("show");
tooltip.show();
setTimeout(() => this.$searchString.tooltip("dispose"), 4000);
setTimeout(() => tooltip.dispose(), 4000);
}
focusOnSearchDefinitionEvent() {

View File

@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import BasicWidget from "./basic_widget.js";
import ws from "../services/ws.js";
import options from "../services/options.js";
@ -39,36 +40,30 @@ const TPL = `
<div class="sync-status">
<span class="sync-status-icon sync-status-unknown bx bx-time"
data-toggle="tooltip"
data-placement="right"
title="<p>Sync status will be known once the next sync attempt starts.</p><p>Click to trigger sync now.</p>">
data-bs-toggle="tooltip"
title="${t("sync_status.unknown")}">
</span>
<span class="sync-status-icon sync-status-connected-with-changes bx bx-wifi"
data-toggle="tooltip"
data-placement="right"
title="<p>Connected to the sync server. <br>There are some outstanding changes yet to be synced.</p><p>Click to trigger sync.</p>">
data-bs-toggle="tooltip"
title="${t("sync_status.connected_with_changes")}">
<span class="bx bxs-star sync-status-sub-icon"></span>
</span>
<span class="sync-status-icon sync-status-connected-no-changes bx bx-wifi"
data-toggle="tooltip"
data-placement="right"
title="<p>Connected to the sync server.<br>All changes have been already synced.</p><p>Click to trigger sync.</p>">
data-bs-toggle="tooltip"
title="${t("sync_status.connected_no_changes")}">
</span>
<span class="sync-status-icon sync-status-disconnected-with-changes bx bx-wifi-off"
data-toggle="tooltip"
data-placement="right"
title="<p>Establishing the connection to the sync server was unsuccessful.<br>There are some outstanding changes yet to be synced.</p><p>Click to trigger sync.</p>">
data-bs-toggle="tooltip"
title="${t("sync_status.disconnected_with_changes")}">
<span class="bx bxs-star sync-status-sub-icon"></span>
</span>
<span class="sync-status-icon sync-status-disconnected-no-changes bx bx-wifi-off"
data-toggle="tooltip"
data-placement="right"
title="<p>Establishing the connection to the sync server was unsuccessful.<br>All known changes have been synced.</p><p>Click to trigger sync.</p>">
data-bs-toggle="tooltip"
title="${t("sync_status.disconnected_no_changes")}">
</span>
<span class="sync-status-icon sync-status-in-progress bx bx-analyse bx-spin"
data-toggle="tooltip"
data-placement="right"
title="Sync with the server is in progress.">
data-bs-toggle="tooltip"
title="${t("sync_status.in_progress")}">
</span>
</div>
</div>
@ -86,10 +81,6 @@ export default class SyncStatusWidget extends BasicWidget {
this.$widget = $(TPL);
this.$widget.hide();
this.$widget.find('[data-toggle="tooltip"]').tooltip({
html: true
});
this.$widget.find('.sync-status-icon:not(.sync-status-in-progress)')
.on('click', () => syncService.syncNow());
@ -102,6 +93,11 @@ export default class SyncStatusWidget extends BasicWidget {
return;
}
bootstrap.Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`), {
html: true,
placement: 'right',
});
this.$widget.show();
this.$widget.find('.sync-status-icon').hide();
this.$widget.find(`.sync-status-${className}`).show();

View File

@ -19,6 +19,7 @@ import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js";
import appContext from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
const TPL = `<div class="toc-widget">
<style>
@ -40,7 +41,6 @@ const TPL = `<div class="toc-widget">
.toc li {
cursor: pointer;
text-align: justify;
text-justify: distribute;
word-wrap: break-word;
hyphens: auto;
}
@ -61,7 +61,7 @@ export default class TocWidget extends RightPanelWidget {
get widgetButtons() {
return [
new OnClickButtonWidget()
.icon("bx-slider")
.icon("bx-cog")
.title("Options")
.titlePlacement("left")
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true}))
@ -120,6 +120,52 @@ export default class TocWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility");
}
/**
* Rendering formulas in strings using katex
*
* @param {string} html Note's html content
* @returns {string} The HTML content with mathematical formulas rendered by KaTeX.
*/
async replaceMathTextWithKatax(html) {
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
var matches = [...html.matchAll(mathTextRegex)];
let modifiedText = html;
if (matches.length > 0) {
// Process all matches asynchronously
for (const match of matches) {
let latexCode = match[1];
let rendered;
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (e) {
if (e instanceof ReferenceError && e.message.includes('katex is not defined')) {
// Load KaTeX if it is not already loaded
await libraryLoader.requireLibrary(libraryLoader.KATEX);
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (renderError) {
console.error("KaTeX rendering error after loading library:", renderError);
rendered = match[0]; // Fall back to original if error persists
}
} else {
console.error("KaTeX rendering error:", e);
rendered = match[0]; // Fall back to original on error
}
}
// Replace the matched formula in the modified text
modifiedText = modifiedText.replace(match[0], rendered);
}
}
return modifiedText;
}
/**
* Builds a jquery table of contents.
*
@ -128,7 +174,7 @@ export default class TocWidget extends RightPanelWidget {
* with an onclick event that will cause the document to scroll to
* the desired position.
*/
getToc(html) {
async getToc(html) {
// Regular expression for headings <h1>...</h1> using non-greedy
// matching and backreferences
const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi;
@ -167,8 +213,8 @@ export default class TocWidget extends RightPanelWidget {
// Create the list item and set up the click callback
//
const headingText = $("<div>").html(m[2]).text();
const $li = $('<li>').text(headingText);
const headingText = await this.replaceMathTextWithKatax(m[2])
const $li = $('<li>').html(headingText);
$li.on("click", () => this.jumpToHeading(headingIndex));
$ols[$ols.length - 1].append($li);
headingCount = headingIndex;
@ -228,9 +274,17 @@ export default class TocWidget extends RightPanelWidget {
this.noteContext.viewScope.tocTemporarilyHidden = true;
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
appContext.triggerEvent("reEvaluateTocWidgetVisibility", { noteId: this.noteId });
}
async entitiesReloadedEvent({loadResults}) {
async showTocWidgetEvent({ noteId }) {
if (this.noteId === noteId) {
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'

View File

@ -34,6 +34,12 @@ export default class MindMapWidget extends TypeWidget {
if (e.key === "F1") {
e.stopPropagation();
}
// Zoom controls
const isCtrl = e.ctrlKey && !e.altKey && !e.metaKey;
if (isCtrl && (e.key == "-" || e.key == "=" || e.key == "0")) {
e.stopPropagation();
}
});
super.doRender();

View File

@ -37,7 +37,7 @@ const TPL = `
<div class="form-group row">
<div class="col-6">
<label>${t("fonts.font_family")}</label>
<select class="main-font-family form-control"></select>
<select class="main-font-family form-select"></select>
</div>
<div class="col-6">
@ -45,9 +45,7 @@ const TPL = `
<div class="input-group">
<input type="number" class="main-font-size form-control options-number-input" min="50" max="200" step="10"/>
<div class="input-group-append">
<span class="input-group-text">%</span>
</div>
<span class="input-group-text">%</span>
</div>
</div>
</div>
@ -57,7 +55,7 @@ const TPL = `
<div class="form-group row">
<div class="col-4">
<label>${t("fonts.font_family")}</label>
<select class="tree-font-family form-control"></select>
<select class="tree-font-family form-select"></select>
</div>
<div class="col-6">
@ -65,9 +63,7 @@ const TPL = `
<div class="input-group">
<input type="number" class="tree-font-size form-control options-number-input" min="50" max="200" step="10"/>
<div class="input-group-append">
<span class="input-group-text">%</span>
</div>
<span class="input-group-text">%</span>
</div>
</div>
</div>
@ -77,7 +73,7 @@ const TPL = `
<div class="form-group row">
<div class="col-4">
<label>${t("fonts.font_family")}</label>
<select class="detail-font-family form-control"></select>
<select class="detail-font-family form-select"></select>
</div>
<div class="col-6">
@ -85,9 +81,7 @@ const TPL = `
<div class="input-group">
<input type="number" class="detail-font-size form-control options-number-input" min="50" max="200" step="10"/>
<div class="input-group-append">
<span class="input-group-text">%</span>
</div>
<span class="input-group-text">%</span>
</div>
</div>
</div>
@ -97,7 +91,7 @@ const TPL = `
<div class="form-group row">
<div class="col-4">
<label>${t("fonts.font_family")}</label>
<select class="monospace-font-family form-control"></select>
<select class="monospace-font-family form-select"></select>
</div>
<div class="col-6">
@ -105,9 +99,7 @@ const TPL = `
<div class="input-group">
<input type="number" class="monospace-font-size form-control options-number-input" min="50" max="200" step="10"/>
<div class="input-group-append">
<span class="input-group-text">%</span>
</div>
<span class="input-group-text">%</span>
</div>
</div>
</div>

View File

@ -10,12 +10,12 @@ const TPL = `
<div class="form-group row">
<div class="col-6">
<label>${t("i18n.language")}</label>
<select class="locale-select form-control"></select>
<select class="locale-select form-select"></select>
</div>
<div class="col-6">
<label>${t("i18n.first-day-of-the-week")}</label>
<select class="first-day-of-week-select form-control">
<select class="first-day-of-week-select form-select">
<option value="0">${t("i18n.sunday")}</option>
<option value="1">${t("i18n.monday")}</option>
</select>

View File

@ -5,12 +5,12 @@ const TPL = `
<div class="options-section">
<h4>${t('ribbon.widgets')}</h4>
<label>
<input type="checkbox" class="promoted-attributes-open-in-ribbon">
<input type="checkbox" class="promoted-attributes-open-in-ribbon form-check-input">
${t('ribbon.promoted_attributes_message')}
</label>
<label>
<input type="checkbox" class="edited-notes-open-in-ribbon">
<input type="checkbox" class="edited-notes-open-in-ribbon form-check-input">
${t('ribbon.edited_notes_message')}
</label>
</div>`;

View File

@ -10,12 +10,14 @@ const TPL = `
<div class="form-group row">
<div class="col-6">
<label>${t("theme.theme_label")}</label>
<select class="theme-select form-control"></select>
<select class="theme-select form-select"></select>
</div>
<div class="col-6">
<label>${t("theme.override_theme_fonts_label")}</label>
<input type="checkbox" class="override-theme-fonts form-control">
<div class="form-check">
<input type="checkbox" class="override-theme-fonts form-check-input">
</div>
</div>
</div>
</div>`;

View File

@ -12,19 +12,19 @@ const TPL = `
<ul style="list-style: none">
<li>
<label>
<input type="checkbox" class="daily-backup-enabled">
<input type="checkbox" class="daily-backup-enabled form-check-input">
${t('backup.enable_daily_backup')}
</label>
</li>
<li>
<label>
<input type="checkbox" class="weekly-backup-enabled">
<input type="checkbox" class="weekly-backup-enabled form-check-input">
${t('backup.enable_weekly_backup')}
</label>
</li>
<li>
<label>
<input type="checkbox" class="monthly-backup-enabled">
<input type="checkbox" class="monthly-backup-enabled form-check-input">
${t('backup.enable_monthly_backup')}
</label>
</li>

View File

@ -24,7 +24,7 @@ export default class CodeMimeTypesOptions extends OptionsWidget {
const id = "code-mime-type-" + (idCtr++);
this.$mimeTypes.append($("<li>")
.append($('<input type="checkbox">')
.append($('<input type="checkbox" class="form-check-input">')
.attr("id", id)
.attr("data-mime-type", mimeType.mime)
.prop("checked", mimeType.enabled))

View File

@ -5,7 +5,7 @@ const TPL = `
<div class="options-section">
<h4>${t('vim_key_bindings.use_vim_keybindings_in_code_notes')}</h4>
<label>
<input type="checkbox" class="vim-keymap-enabled">
<input type="checkbox" class="vim-keymap-enabled form-check-input">
${t('vim_key_bindings.enable_vim_keybindings')}
</label>
</div>`;

View File

@ -5,7 +5,7 @@ const TPL = `
<div class="options-section">
<h4>${t("wrap_lines.wrap_lines_in_code_notes")}</h4>
<label>
<input type="checkbox" class="line-wrap-enabled">
<input type="checkbox" class="line-wrap-enabled form-check-input">
${t("wrap_lines.enable_line_wrap")}
</label>
</div>`;

View File

@ -6,7 +6,7 @@ const TPL = `
<h4>${t("network_connections.network_connections_title")}</h4>
<label>
<input class="check-for-updates" type="checkbox" name="check-for-updates">
<input class="check-for-updates form-check-input" type="checkbox" name="check-for-updates">
${t("network_connections.check_for_updates")}
</label>
</div>`;

View File

@ -5,7 +5,7 @@ const TPL = `
<div class="options-section">
<h4>${t("heading_style.title")}</h4>
<select class="heading-style form-control">
<select class="heading-style form-select">
<option value="plain">${t("heading_style.plain")}</option>
<option value="underline">${t("heading_style.underline")}</option>
<option value="markdown">${t("heading_style.markdown")}</option>

View File

@ -8,11 +8,11 @@ const TPL = `
<p>${t("highlights_list.description")}</p>
</div>
<label><input type="checkbox" class="highlights-list-check" value="bold"> ${t("highlights_list.bold")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check" value="italic"> ${t("highlights_list.italic")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check" value="underline"> ${t("highlights_list.underline")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check" value="color"> ${t("highlights_list.color")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check" value="bgColor"> ${t("highlights_list.bg_color")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check form-check-input" value="bold"> ${t("highlights_list.bold")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check form-check-input" value="italic"> ${t("highlights_list.italic")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check form-check-input" value="underline"> ${t("highlights_list.underline")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check form-check-input" value="color"> ${t("highlights_list.color")} &nbsp;</label>
<label><input type="checkbox" class="highlights-list-check form-check-input" value="bgColor"> ${t("highlights_list.bg_color")} &nbsp;</label>
</div>
<br/><br/>

View File

@ -100,6 +100,10 @@
font-weight: bold;
}
.calendar-dropdown-widget a {
text-decoration: none;
}
.calendar-dropdown-widget .calendar-date-exists {
text-decoration: underline !important;
}
@ -107,3 +111,4 @@
.calendar-dropdown-widget .calendar-date:not(.calendar-date-active) {
cursor: pointer;
}

View File

@ -36,22 +36,78 @@ a, a:visited, a:hover {
color: var(--link-color);
}
input, select, textarea {
color: var(--input-text-color) !important;
background: var(--input-background-color) !important;
.note-list-widget a {
text-decoration: none;
}
#left-pane input, select, textarea {
color: var(--left-pane-text-color) !important;
background: var(--left-pane-background-color) !important;
.note-list-widget a:hover {
text-decoration: underline;
}
input::placeholder {
input,
select,
textarea,
.form-control,
.form-select {
color: var(--input-text-color);
background: var(--input-background-color);
}
.form-control:focus {
color: var(--input-text-color);
background: var(--input-background-color);
}
.form-select {
background: var(--input-background-color) url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='%23ffffff' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") right .75rem center/15px 20px no-repeat;
}
/* Hide number input arrows */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox browser */
input[type="number"] {
-moz-appearance: textfield;
}
/* Show number input arrows when focus or hover */
input[type="number"]:focus::-webkit-inner-spin-button,
input[type="number"]:focus::-webkit-outer-spin-button,
input[type="number"]:hover::-webkit-inner-spin-button,
input[type="number"]:hover::-webkit-outer-spin-button {
-webkit-appearance: inner-spin-button;
}
/* Restore default apperance */
input[type="number"]:focus,
input[type="number"]:hover {
appearance: auto;
}
#left-pane input,
#left-pane select,
#left-pane textarea {
color: var(--left-pane-text-color);
background: var(--left-pane-background-color);
}
input::placeholder,
.form-control::placeholder,
#left-pane input::placeholder {
color: var(--muted-text-color);
}
table td, table th {
.table thead th,
.table td,
.table th {
/* Fix center vertical alignment of table cells */
vertical-align: middle;
color: var(--main-text-color);
background: transparent;
}
.ck .todo-list__checkmark {
@ -184,10 +240,15 @@ div.ui-tooltip {
display: none;
}
.dropdown-divider {
background-color: var(--menu-text-color);
}
.dropdown-menu {
border: 1px solid var(--dropdown-border-color);
color: var(--menu-text-color) !important;
background-color: var(--menu-background-color) !important;
font-size: inherit;
}
.dropdown-menu .disabled {
@ -281,6 +342,10 @@ body .CodeMirror {
border-color: var(--button-border-color);
}
.btn:hover {
border-color: var(--hover-item-border-color);
}
button.btn, button.btn-sm {
font-size: inherit;
}
@ -397,48 +462,83 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
font-size: var(--main-font-size) !important;
}
.bs-tooltip-bottom .arrow::before { border-bottom-color: var(--main-border-color) !important; }
.bs-tooltip-top .arrow::before { border-top-color: var(--main-border-color) !important; }
.bs-tooltip-left .arrow::before { border-left-color: var(--main-border-color) !important; }
.bs-tooltip-right .arrow::before { border-right-color: var(--main-border-color) !important; }
.bs-tooltip-bottom .arrow::after { border-bottom-color: var(--tooltip-background-color) !important; }
.bs-tooltip-top .arrow::after { border-top-color: var(--tooltip-background-color) !important; }
.bs-tooltip-left .arrow::after { border-left-color: var(--tooltip-background-color) !important; }
.bs-tooltip-right .arrow::after { border-right-color: var(--tooltip-background-color) !important; }
.tooltip .arrow::after {
.tooltip-trigger {
position: absolute;
content: '';
border-color: transparent;
border-style: solid;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
pointer-events: none;
}
.bs-tooltip-auto[x-placement^='left'] .arrow::after,
.bs-tooltip-left .arrow::after {
.bs-tooltip-bottom .tooltip-arrow::before { border-bottom-color: var(--main-border-color) !important; }
.bs-tooltip-top .tooltip-arrow::before { border-top-color: var(--main-border-color) !important; }
.bs-tooltip-left .tooltip-arrow::before { border-left-color: var(--main-border-color) !important; }
.bs-tooltip-right .tooltip-arrow::before { border-right-color: var(--main-border-color) !important; }
.bs-tooltip-bottom .tooltip-arrow::after { border-bottom-color: var(--tooltip-background-color) !important; }
.bs-tooltip-top .tooltip-arrow::after { border-top-color: var(--tooltip-background-color) !important; }
.bs-tooltip-left .tooltip-arrow::after { border-left-color: var(--tooltip-background-color) !important; }
.bs-tooltip-right .tooltip-arrow::after { border-right-color: var(--tooltip-background-color) !important; }
.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::before,
.bs-tooltip-left .tooltip-arrow::before {
left: -1px;
border-width: 0.4rem 0 0.4rem 0.4rem;
border-left-color: var(--main-border-color) !important;
}
.bs-tooltip-auto[x-placement^='bottom'] .arrow::after,
.bs-tooltip-bottom .arrow::after {
.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::before,
.bs-tooltip-bottom .tooltip-arrow::before {
bottom: -1px;
border-width: 0 0.4rem 0.4rem;
border-bottom-color: var(--main-border-color) !important;
}
.bs-tooltip-auto[x-placement^='right'] .arrow::after,
.bs-tooltip-right .arrow::after {
.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::before,
.bs-tooltip-right .tooltip-arrow::before {
right: -1px;
border-width: 0.4rem 0.4rem 0.4rem 0;
border-right-color: var(--main-border-color) !important;
}
.bs-tooltip-auto[x-placement^='top'] .arrow::after,
.bs-tooltip-top .arrow::after {
.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::before,
.bs-tooltip-top .tooltip-arrow::before {
top: -1px;
border-width: 0.4rem 0.4rem 0;
border-top-color: var(--main-border-color) !important;
}
.note-tooltip.tooltip .arrow {
.bs-tooltip-auto[data-popper-placement^='left'] .tooltip-arrow::after,
.bs-tooltip-left .tooltip-arrow::after {
left: -1px;
border-width: 0.4rem 0 0.4rem 0.4rem;
border-left-color: var(--tooltip-background-color) !important;
}
.bs-tooltip-auto[data-popper-placement^='bottom'] .tooltip-arrow::after,
.bs-tooltip-bottom .tooltip-arrow::after {
bottom: -1px;
border-width: 0 0.4rem 0.4rem;
border-bottom-color: var(--tooltip-background-color) !important;
}
.bs-tooltip-auto[data-popper-placement^='right'] .tooltip-arrow::after,
.bs-tooltip-right .tooltip-arrow::after {
right: -1px;
border-width: 0.4rem 0.4rem 0.4rem 0;
border-right-color: var(--tooltip-background-color) !important;
}
.bs-tooltip-auto[data-popper-placement^='top'] .tooltip-arrow::after,
.bs-tooltip-top .tooltip-arrow::after {
top: -1px;
border-width: 0.4rem 0.4rem 0;
border-top-color: var(--tooltip-background-color) !important;
}
.note-tooltip.tooltip .tooltip-arrow {
display: none;
}
@ -651,6 +751,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
}
.card {
color: inherit !important;
background-color: inherit !important;
border-color: var(--main-border-color) !important;
}
@ -667,7 +768,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
}
.modal-header {
padding: 0.7rem 1rem 0 1rem !important; /* make modal header padding slightly smaller */
padding: 0.5rem 1rem 0.5rem 1rem !important; /* make modal header padding slightly smaller */
}
#toast-container {
@ -682,7 +783,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
color: var(--main-text-color) !important;
z-index: 9999999999 !important;
pointer-events: all;
flex-basis: 0; /* otherwise toast is always at least 350px tall (happens since 4.5.1, perhaps a bug) */
}
.toast-header {
@ -789,10 +889,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
cursor: row-resize;
}
.dropdown-menu {
font-size: inherit;
}
#context-menu-container {
max-height: 100vh;
/* !!! Cannot set overflow: auto, submenus will break !!! */
@ -846,7 +942,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
background-color: inherit;
}
[data-toggle="tooltip"]:not(.button-widget) span {
[data-bs-toggle="tooltip"]:not(.button-widget) span {
padding-bottom: 0;
border-bottom: 1px dotted;
}
@ -954,11 +1050,6 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
background-color: var(--launcher-pane-background-color);
}
input {
background-color: transparent !important;
}
#right-pane {
overflow: auto;
}
@ -1059,7 +1150,6 @@ button.close:hover {
}
.options-number-input {
text-align: right;
/* overriding settings from .form-control */
width: 10em !important;
flex-grow: 0 !important;
@ -1069,12 +1159,6 @@ textarea {
cursor: auto;
}
.table thead th,
.table td, .table th {
/* Fix center vertical alignment of table cells */
vertical-align: middle;
}
.ck-powered-by-balloon {
display: none !important;
}
@ -1092,7 +1176,6 @@ textarea {
.jump-to-note-dialog .modal-header {
align-items: center;
padding-bottom: 1rem !important;
}
.jump-to-note-dialog .modal-body {

View File

@ -88,3 +88,7 @@ body .CodeMirror {
body .todo-list input[type="checkbox"]:not(:checked):before {
border-color: var(--muted-text-color) !important;
}
.btn-close {
filter: invert(1);
}

View File

@ -132,7 +132,7 @@
"pasteNotes": "将笔记粘贴为活动笔记的子笔记(根据是复制还是剪切到剪贴板来决定是移动还是克隆)",
"deleteNotes": "删除笔记/子树",
"editingNotes": "编辑笔记",
"editNoteTitle": "",
"editNoteTitle": "在树形笔记树中,焦点会从笔记树切换到笔记标题。按下 Enter 键会将焦点从笔记标题切换到文本编辑器。按下 <kbd>Ctrl+.</kbd> 会将焦点从编辑器切换回笔记树。",
"createEditLink": "创建/编辑外部链接",
"createInternalLink": "创建内部链接",
"followLink": "跟随光标下的链接",
@ -187,6 +187,12 @@
"search_placeholder": "按笔记名称搜索",
"search_button": "全文搜索 <kbd>Ctrl+回车</kbd>"
},
"markdown_import": {
"dialog_title": "Markdown 导入",
"modal_body_text": "由于浏览器沙箱的限制,无法直接从 JavaScript 读取剪贴板内容。请将要导入的 Markdown 文本粘贴到下面的文本框中,然后点击导入按钮",
"import_button": "导入 Ctrl+回车",
"import_success": "Markdown 内容已成功导入文档。"
},
"move_to": {
"dialog_title": "移动笔记到...",
"notes_to_move": "需要移动的笔记",
@ -196,12 +202,6 @@
"error_no_path": "没有可以移动到的路径。",
"move_success_message": "所选笔记已移动到 "
},
"markdown_import": {
"dialog_title": "Markdown 导入",
"modal_body_text": "由于浏览器沙箱的限制,无法直接从 JavaScript 读取剪贴板内容。请将要导入的 Markdown 文本粘贴到下面的文本框中,然后点击导入按钮",
"import_button": "导入 Ctrl+回车",
"import_success": "Markdown 内容已成功导入文档。"
},
"note_type_chooser": {
"modal_title": "选择笔记类型",
"modal_body": "选择新笔记的类型或模板:",
@ -468,8 +468,8 @@
"to": "到",
"target_parent_note": "目标父笔记",
"on_all_matched_notes": "对于所有匹配的笔记",
"move_note_new_parent": "",
"clone_note_new_parent": "",
"move_note_new_parent": "如果笔记只有一个父级(即旧分支被移除并创建新分支到新父级),则将笔记移动到新父级",
"clone_note_new_parent": "如果笔记有多个克隆/分支(不清楚应该移除哪个分支),则将笔记克隆到新父级",
"nothing_will_happen": "如果笔记无法移动到目标笔记(即这会创建一个树循环),则不会发生任何事情"
},
"rename_note": {
@ -477,7 +477,7 @@
"rename_note_title_to": "重命名笔记标题为",
"new_note_title": "新笔记标题",
"click_help_icon": "点击右侧的帮助图标查看所有选项",
"evaluated_as_js_string": "给定的值被评估为JavaScript字符串因此可以通过注入的<code>note</code>变量(正在重命名的笔记)丰富动态内容。 例如:",
"evaluated_as_js_string": "给定的值被评估为 JavaScript 字符串,因此可以通过注入的 <code>note</code> 变量(正在重命名的笔记)丰富动态内容。 例如:",
"example_note": "<code>Note</code> - 所有匹配的笔记都被重命名为“Note”",
"example_new_title": "<code>NEW: ${note.title}</code> - 匹配的笔记标题以“NEW: ”为前缀",
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - 匹配的笔记以笔记的创建月份-日期为前缀",
@ -567,6 +567,12 @@
"edit_button": {
"edit_this_note": "编辑此笔记"
},
"show_toc_widget_button": {
"show_toc": "显示目录"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "显示高亮列表"
},
"global_menu": {
"menu": "菜单",
"options": "选项",
@ -593,9 +599,17 @@
"about": "关于 TriliumNext 笔记",
"logout": "登出"
},
"sync_status": {
"unknown": "<p>同步状态将在下一次同步尝试开始后显示。</p><p>点击以立即触发同步。</p>",
"connected_with_changes": "<p>已连接到同步服务器。<br>有一些未同步的变更。</p><p>点击以触发同步。</p>",
"connected_no_changes": "<p>已连接到同步服务器。<br>所有变更均已同步。</p><p>点击以触发同步。</p>",
"disconnected_with_changes": "<p>连接同步服务器失败。<br>有一些未同步的变更。</p><p>点击以触发同步。</p>",
"disconnected_no_changes": "<p>连接同步服务器失败。<br>所有已知变更均已同步。</p><p>点击以触发同步。</p>",
"in_progress": "正在与服务器进行同步。"
},
"left_pane_toggle": {
"hide_panel": "隐藏面板",
"show_panel": "显示面板"
"show_panel": "显示面板",
"hide_panel": "隐藏面板"
},
"move_pane_button": {
"move_left": "向左移动",
@ -645,6 +659,9 @@
"hide_floating_buttons_button": {
"button_title": "隐藏按钮"
},
"svg_export_button": {
"button_title": "导出SVG格式图片"
},
"relation_map_buttons": {
"create_child_note_title": "创建新的子笔记并添加到关系图",
"reset_pan_zoom_title": "重置平移和缩放到初始坐标和放大倍率",
@ -662,6 +679,9 @@
"error_cannot_get_branch_id": "无法获取 notePath '{{notePath}}' 的 branchId",
"error_unrecognized_command": "无法识别的命令 {{command}}"
},
"note_icon": {
"change_note_icon": "更改笔记图标"
},
"basic_properties": {
"note_type": "笔记类型",
"editable": "可编辑",
@ -1023,8 +1043,8 @@
"title": "下拉菜单可用的MIME文件类型"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "",
"enable_vim_keybindings": ""
"use_vim_keybindings_in_code_notes": "Vim 快捷键",
"enable_vim_keybindings": "在代码笔记中启用 Vim 快捷键(不包含 ex 模式)"
},
"wrap_lines": {
"wrap_lines_in_code_notes": "代码笔记自动换行",
@ -1090,11 +1110,11 @@
"highlights_list": {
"title": "高亮列表",
"description": "您可以自定义右侧面板中显示的高亮列表:",
"bold": "",
"italic": "",
"underline": "",
"color": "",
"bg_color": "",
"bold": "粗体",
"italic": "斜体",
"underline": "下划线",
"color": "字体颜色",
"bg_color": "背景颜色",
"visibility_title": "高亮列表可见性",
"visibility_description": "您可以通过添加 #hideHighlightWidget 标签来隐藏每个笔记的高亮小部件。",
"shortcut_info": "您可以在选项 -> 快捷键中为快速切换右侧面板(包括高亮列表)配置键盘快捷键(名称为 'toggleRightPane')。"
@ -1112,7 +1132,10 @@
},
"i18n": {
"title": "本地化",
"language": "语言"
"language": "语言",
"first-day-of-the-week": "一周的第一天",
"sunday": "星期日",
"monday": "星期一"
},
"backup": {
"automatic_backup": "自动备份",
@ -1238,5 +1261,90 @@
"note_is_editable": "笔记如果不太长则可编辑。",
"note_is_read_only": "笔记为只读,但可以通过点击按钮进行编辑。",
"note_is_always_editable": "无论笔记长度如何,始终可编辑。"
},
"note-map": {
"button-link-map": "链接地图",
"button-tree-map": "树形地图"
},
"tree-context-menu": {
"open-in-a-new-tab": "在新标签页中打开",
"open-in-a-new-split": "在新分栏中打开",
"insert-note-after": "在后面插入笔记",
"insert-child-note": "插入子笔记",
"delete": "删除",
"search-in-subtree": "在子树中搜索",
"edit-branch-prefix": "编辑分支前缀",
"advanced": "高级",
"expand-subtree": "展开子树",
"collapse-subtree": "折叠子树",
"sort-by": "排序方式...",
"recent-changes-in-subtree": "子树中的最近更改",
"convert-to-attachment": "转换为附件",
"copy-note-path-to-clipboard": "复制笔记路径到剪贴板",
"protect-subtree": "保护子树",
"unprotect-subtree": "取消保护子树",
"copy-clone": "复制 / 克隆",
"clone-to": "克隆到...",
"cut": "剪切",
"move-to": "移动到...",
"paste-into": "粘贴到里面",
"paste-after": "粘贴到后面",
"duplicate-subtree": "复制子树",
"export": "导出",
"import-into-note": "导入到笔记",
"apply-bulk-actions": "应用批量操作"
},
"shared_info": {
"shared_publicly": "此笔记已公开分享在",
"shared_locally": "此笔记已在本地分享在",
"help_link": "如需帮助,请访问 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>。"
},
"note_types": {
"text": "文本",
"code": "代码",
"saved-search": "保存的搜索",
"relation-map": "关系图",
"note-map": "笔记地图",
"render-note": "渲染笔记",
"book": "书",
"mermaid-diagram": "美人鱼图",
"canvas": "画布",
"web-view": "网页视图",
"mind-map": "思维导图",
"file": "文件",
"image": "图片",
"launcher": "启动器",
"doc": "文档",
"widget": "小部件"
},
"protect_note": {
"toggle-on": "保护笔记",
"toggle-off": "取消保护笔记",
"toggle-on-hint": "笔记未受保护,点击以保护",
"toggle-off-hint": "笔记已受保护,点击以取消保护"
},
"shared_switch": {
"shared": "已分享",
"toggle-on-title": "分享笔记",
"toggle-off-title": "取消分享笔记",
"shared-branch": "此笔记仅作为共享笔记存在,取消共享将删除它。你确定要继续并删除此笔记吗?",
"inherited": "此笔记无法在此处取消共享,因为它通过继承自上级笔记共享。"
},
"template_switch": {
"template": "模板",
"toggle-on-hint": "将此笔记设为模板",
"toggle-off-hint": "取消笔记模板设置"
},
"open-help-page": "打开帮助页面",
"find": {
"case_sensitive": "区分大小写",
"match_words": "匹配单词"
},
"highlights_list_2": {
"title": "高亮列表",
"options": "选项"
},
"mermaid": {
"diagram_error": "图表无法显示。 请参考 <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">帮助文档和示例</a>。"
}
}

View File

@ -567,6 +567,12 @@
"edit_button": {
"edit_this_note": "Edit this note"
},
"show_toc_widget_button": {
"show_toc": "Show Table of Contents"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Show Highlights List"
},
"global_menu": {
"menu": "Menu",
"options": "Options",
@ -593,6 +599,14 @@
"about": "About TriliumNext Notes",
"logout": "Logout"
},
"sync_status": {
"unknown": "<p>Sync status will be known once the next sync attempt starts.</p><p>Click to trigger sync now.</p>",
"connected_with_changes": "<p>Connected to the sync server. <br>There are some outstanding changes yet to be synced.</p><p>Click to trigger sync.</p>",
"connected_no_changes": "<p>Connected to the sync server.<br>All changes have been already synced.</p><p>Click to trigger sync.</p>",
"disconnected_with_changes": "<p>Establishing the connection to the sync server was unsuccessful.<br>There are some outstanding changes yet to be synced.</p><p>Click to trigger sync.</p>",
"disconnected_no_changes": "<p>Establishing the connection to the sync server was unsuccessful.<br>All known changes have been synced.</p><p>Click to trigger sync.</p>",
"in_progress": "Sync with the server is in progress."
},
"left_pane_toggle": {
"show_panel": "Show panel",
"hide_panel": "Hide panel"
@ -665,6 +679,9 @@
"error_cannot_get_branch_id": "Cannot get branchId for notePath '{{notePath}}'",
"error_unrecognized_command": "Unrecognized command {{command}}"
},
"note_icon": {
"change_note_icon": "Change note icon"
},
"basic_properties": {
"note_type": "Note type",
"editable": "Editable",
@ -1325,5 +1342,16 @@
"toggle-on-hint": "Make the note a template",
"toggle-off-hint": "Remove the note as a template"
},
"open-help-page": "Open help page"
"open-help-page": "Open help page",
"find": {
"case_sensitive": "case sensitive",
"match_words": "match words"
},
"highlights_list_2": {
"title": "Highlights List",
"options": "Options"
},
"mermaid": {
"diagram_error": "The diagram could not be displayed. See <a href=\"https://mermaid-js.github.io/mermaid/#/flowchart?id=graph\">help and examples</a>."
}
}

View File

@ -48,7 +48,7 @@ const HIDDEN_SUBTREE_DEFINITION: Item = {
id: '_hidden',
title: 'Hidden Notes',
type: 'doc',
icon: 'bx bx-chip',
icon: 'bx bx-hide',
// we want to keep the hidden subtree always last, otherwise there will be problems with e.g., keyboard navigation
// over tree when it's in the middle
notePosition: 999_999_999,
@ -222,7 +222,7 @@ const HIDDEN_SUBTREE_DEFINITION: Item = {
{ id: '_lbJumpTo', title: 'Jump to Note', type: 'launcher', command: 'jumpToNote', icon: 'bx bx-send', attributes: [
{ type: 'label', name: 'desktopOnly' }
] },
{ id: '_lbNoteMap', title: 'Note Map', type: 'launcher', targetNoteId: '_globalNoteMap', icon: 'bx bx-map-alt' },
{ id: '_lbNoteMap', title: 'Note Map', type: 'launcher', targetNoteId: '_globalNoteMap', icon: 'bx bxs-network-chart' },
{ id: '_lbCalendar', title: 'Calendar', type: 'launcher', builtinWidget: 'calendar', icon: 'bx bx-calendar' },
{ id: '_lbRecentChanges', title: 'Recent Changes', type: 'launcher', command: 'showRecentChanges', icon: 'bx bx-history', attributes: [
{ type: 'label', name: 'desktopOnly' }
@ -241,6 +241,7 @@ const HIDDEN_SUBTREE_DEFINITION: Item = {
id: '_options',
title: 'Options',
type: 'book',
icon: 'bx-cog',
children: [
{ id: '_optionsAppearance', title: 'Appearance', type: 'contentWidget', icon: 'bx-layout' },
{ id: '_optionsShortcuts', title: 'Shortcuts', type: 'contentWidget', icon: 'bxs-keyboard' },

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
#!/bin/sh
[[ ! -z "${USER_UID}" ]] && usermod -u ${USER_UID} node || echo "No USER_UID specified, leaving 1000"
[[ ! -z "${USER_GID}" ]] && groupmod -og ${USER_GID} node || echo "No USER_GID specified, leaving 1000"
[ ! -z "${USER_UID}" ] && usermod -u ${USER_UID} node || echo "No USER_UID specified, leaving 1000"
[ ! -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/www" node