Merge branch 'develop' into revisions_number_limit

This commit is contained in:
SiriusXT
2024-09-13 19:46:07 +08:00
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

@@ -14,18 +14,31 @@ on:
env: env:
GHCR_REGISTRY: ghcr.io GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository_owner }}/notes
TEST_TAG: triliumnext/notes:test TEST_TAG: ${{ github.repository_owner }}/notes:test
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
permissions:
contents: read
packages: write
jobs: jobs:
test_docker: test_docker:
name: Check Docker build name: Check Docker build
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
include:
- dockerfile: Dockerfile.alpine
- dockerfile: Dockerfile
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4 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 - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -47,6 +60,7 @@ jobs:
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ${{ matrix.dockerfile }}
load: true load: true
tags: ${{ env.TEST_TAG }} tags: ${{ env.TEST_TAG }}
cache-from: type=gha cache-from: type=gha
@@ -63,7 +77,7 @@ jobs:
require-status: running require-status: running
require-healthy: true require-healthy: true
build_docker: build:
name: Build Docker images name: Build Docker images
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
@@ -73,26 +87,48 @@ jobs:
packages: write packages: write
attestations: write attestations: write
id-token: write id-token: write
strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
- dockerfile: Dockerfile
platform: linux/arm64
- dockerfile: Dockerfile
platform: linux/arm/v7
steps: steps:
- uses: actions/checkout@v4 - name: Prepare
- name: Extract metadata (tags, labels) for GHCR image run: |
id: ghcr-meta 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 uses: docker/metadata-action@v4
with: with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }} images: |
tags: | ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
type=ref,event=branch ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
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: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=tag type=ref,event=tag
type=sha 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 - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
@@ -103,50 +139,103 @@ jobs:
run: npx tsc run: npx tsc
- name: Create server-package.json - name: Create server-package.json
run: cat package.json | grep -v electron > 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 uses: docker/login-action@v2
with: with:
registry: ${{ env.GHCR_REGISTRY }} registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Build and push container image to GHCR - name: Login to DockerHub
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
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ${{ env.DOCKERHUB_REGISTRY }} registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} 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 uses: docker/build-push-action@v6
id: dh-push
with: with:
context: . context: .
platforms: ${{ env.PLATFORMS }} file: ${{ matrix.dockerfile }}
push: true platforms: ${{ matrix.platform }}
tags: ${{ steps.dh-meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.dh-meta.outputs.labels }} outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha
cache-to: type=gha,mode=max - name: Export digest
- name: Generate and push artifact attestation to DockerHub run: |
uses: actions/attest-build-provenance@v1 mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with: with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}} name: digests-${{ env.PLATFORM_PAIR }}
subject-digest: ${{ steps.dh-push.outputs.digest }} path: /tmp/digests/*
push-to-registry: true 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", "code": "bx bx-code",
"render": "bx bx-extension", "render": "bx bx-extension",
"search": "bx bx-file-find", "search": "bx bx-file-find",
"relationMap": "bx bx-map-alt", "relationMap": "bx bxs-network-chart",
"book": "bx bx-book", "book": "bx bx-book",
"noteMap": "bx bx-map-alt", "noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection", "mermaid": "bx bx-selection",
"canvas": "bx bx-pen", "canvas": "bx bx-pen",
"webView": "bx bx-globe-alt", "webView": "bx bx-globe-alt",
@@ -570,7 +570,7 @@ class FNote {
return workspaceIconClass; return workspaceIconClass;
} }
else if (this.noteId === 'root') { else if (this.noteId === 'root') {
return "bx bx-chevrons-right"; return "bx bx-home-alt-2";
} }
if (this.noteId === '_share') { if (this.noteId === '_share') {
return "bx bx-share-alt"; 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

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

View File

@@ -14,9 +14,9 @@ const NOTE_TYPE_ICONS = {
"code": "bx bx-code", "code": "bx bx-code",
"render": "bx bx-extension", "render": "bx bx-extension",
"search": "bx bx-file-find", "search": "bx bx-file-find",
"relationMap": "bx bx-map-alt", "relationMap": "bx bxs-network-chart",
"book": "bx bx-book", "book": "bx bx-book",
"noteMap": "bx bx-map-alt", "noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection", "mermaid": "bx bx-selection",
"canvas": "bx bx-pen", "canvas": "bx bx-pen",
"webView": "bx bx-globe-alt", "webView": "bx bx-globe-alt",
@@ -543,7 +543,7 @@ class FNote {
return workspaceIconClass; return workspaceIconClass;
} }
else if (this.noteId === 'root') { else if (this.noteId === 'root') {
return "bx bx-chevrons-right"; return "bx bx-home-alt-2";
} }
if (this.noteId === '_share') { if (this.noteId === '_share') {
return "bx bx-share-alt"; 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 RightPaneContainer from "../widgets/containers/right_pane_container.js";
import EditButton from "../widgets/buttons/edit_button.js"; import EditButton from "../widgets/buttons/edit_button.js";
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.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 MermaidWidget from "../widgets/mermaid.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js"; import NoteWrapperWidget from "../widgets/note_wrapper.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js"; import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
@@ -160,6 +162,8 @@ export default class DesktopLayout {
.child(new WatchedFileUpdateStatusWidget()) .child(new WatchedFileUpdateStatusWidget())
.child(new FloatingButtons() .child(new FloatingButtons()
.child(new EditButton()) .child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget()) .child(new CodeButtonsWidget())
.child(new RelationMapButtons()) .child(new RelationMapButtons())
.child(new CopyImageReferenceButton()) .child(new CopyImageReferenceButton())

View File

@@ -12,8 +12,9 @@ export async function initLocale() {
lng: locale, lng: locale,
fallbackLng: "en", fallbackLng: "en",
backend: { 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"); $el.addClass("note-autocomplete-input");
const $clearTextButton = $("<a>") const $clearTextButton = $("<button>")
.addClass("input-group-text input-clearer-button bx bxs-tag-x") .addClass("input-group-text input-clearer-button bx bxs-tag-x")
.prop("title", "Clear text field"); .prop("title", "Clear text field");
const $showRecentNotesButton = $("<a>") const $showRecentNotesButton = $("<button>")
.addClass("input-group-text show-recent-notes-button bx bx-time") .addClass("input-group-text show-recent-notes-button bx bx-time")
.prop("title", "Show recent notes"); .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"); .addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
const $sideButtons = $("<div>") $el.after($clearTextButton).after($showRecentNotesButton);
.addClass("input-group-append")
.append($clearTextButton)
.append($showRecentNotesButton);
if (!options.hideGoToSelectedNoteButton) { if (!options.hideGoToSelectedNoteButton) {
$sideButtons.append($goToSelectedNoteButton); $el.after($goToSelectedNoteButton);
} }
$el.after($sideButtons);
$clearTextButton.on('click', () => clearText($el)); $clearTextButton.on('click', () => clearText($el));
$showRecentNotesButton.on('click', e => { $showRecentNotesButton.on('click', e => {
@@ -180,13 +175,13 @@ function initNoteAutocomplete($el, options) {
} }
if (suggestion.action === 'create-note') { if (suggestion.action === 'create-note') {
const {success, noteType, templateNoteId} = await noteCreateService.chooseNoteType(); const { success, noteType, templateNoteId } = await noteCreateService.chooseNoteType();
if (!success) { if (!success) {
return; return;
} }
const {note} = await noteCreateService.createNote(suggestion.parentNoteId, { const { note } = await noteCreateService.createNote(suggestion.parentNoteId, {
title: suggestion.noteTitle, title: suggestion.noteTitle,
activate: false, activate: false,
type: noteType, 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.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.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.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.relation-map"), command: command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bx-map-alt" }, { 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.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.book"), command: command, type: "book", uiIcon: "bx bx-book" },
{ title: t("note_types.mermaid-diagram"), command: command, type: "mermaid", uiIcon: "bx bx-selection" }, { 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}`; 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 { export default {
download, download,
downloadFileNote, downloadFileNote,
@@ -176,4 +193,5 @@ export default {
openAttachmentExternally, openAttachmentExternally,
openNoteCustom, openNoteCustom,
openAttachmentCustom, openAttachmentCustom,
openDirectory
} }

View File

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

View File

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

View File

@@ -97,7 +97,7 @@ const TPL = `
<tr class="attr-row-promoted" <tr class="attr-row-promoted"
title="${t('attribute_detail.promoted_title')}"> title="${t('attribute_detail.promoted_title')}">
<th>${t('attribute_detail.promoted')}</th> <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>
<tr class="attr-row-promoted-alias"> <tr class="attr-row-promoted-alias">
<th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th> <th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th>
@@ -134,10 +134,8 @@ const TPL = `
<td> <td>
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control attr-input-number-precision" style="text-align: right"> <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> <span class="input-group-text">${t('attribute_detail.digits')}</span>
</div> </div>
</div>
</td> </td>
</tr> </tr>
<tr class="attr-row-inverse-relation"> <tr class="attr-row-inverse-relation">
@@ -150,7 +148,7 @@ const TPL = `
</tr> </tr>
<tr title="${t('attribute_detail.inheritable_title')}"> <tr title="${t('attribute_detail.inheritable_title')}">
<th>${t('attribute_detail.inheritable')}</th> <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> </tr>
</table> </table>
@@ -349,7 +347,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$rowTargetNote = this.$widget.find('.attr-row-target-note'); this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
this.$inputTargetNote = this.$widget.find('.attr-input-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) => { .on('autocomplete:noteselected', (event, suggestion, dataset) => {
if (!suggestion.notePath) { if (!suggestion.notePath) {
return false; 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) { if (!attribute) {
this.hide(); this.hide();
@@ -545,7 +543,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
} }
} }
return {left, right}; return { left, right };
} }
async saveAndClose() { async saveAndClose() {
@@ -589,19 +587,19 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
} }
async updateRelatedNotes() { 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) { for (const res of results) {
res.noteId = res.notePathArray[res.notePathArray.length - 1]; 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) { if (results.length === 0) {
this.$relatedNotesContainer.hide(); this.$relatedNotesContainer.hide();
} else { } else {
this.$relatedNotesContainer.show(); 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(); this.$relatedNotesList.empty();
@@ -611,7 +609,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
for (const note of displayedNotes) { for (const note of displayedNotes) {
const notePath = note.getBestNotePathString(hoistedNoteId); const notePath = note.getBestNotePathString(hoistedNoteId);
const $noteLink = await linkService.createLink(notePath, {showNotePath: true}); const $noteLink = await linkService.createLink(notePath, { showNotePath: true });
this.$relatedNotesList.append( this.$relatedNotesList.append(
$("<li>").append($noteLink) $("<li>").append($noteLink)
@@ -619,7 +617,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
} }
if (results.length > DISPLAYED_NOTES) { 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 { } else {
this.$relatedNotesMoreNotes.hide(); this.$relatedNotesMoreNotes.hide();
} }

View File

@@ -15,7 +15,7 @@ const TPL = `
<td class="button-column"> <td class="button-column">
<div style="display: flex"> <div style="display: flex">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
${t('execute_script.help_text')} ${t('execute_script.help_text')}

View File

@@ -21,7 +21,7 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("add_label.help_text")}</p> <p>${t("add_label.help_text")}</p>
@@ -30,7 +30,7 @@ const TPL = `
<li>${t("add_label.help_text_item2")}</li> <li>${t("add_label.help_text_item2")}</li>
</ul> </ul>
<p>${t("add_label.help_text_note")}</p> ${t("add_label.help_text_note")}
</div> </div>
</div> </div>

View File

@@ -21,11 +21,11 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("update_label_value.help_text")}</p> <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>
</div> </div>

View File

@@ -10,13 +10,13 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("delete_note.delete_matched_notes_description")}</p> <p>${t("delete_note.delete_matched_notes_description")}</p>
<p>${t("delete_note.undelete_notes_instruction")}</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>
</div> </div>

View File

@@ -9,7 +9,7 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
${t('delete_revisions.all_past_note_revisions')} ${t('delete_revisions.all_past_note_revisions')}
</div> </div>

View File

@@ -18,11 +18,11 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('move_note.on_all_matched_notes')}:</p> <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.move_note_new_parent')}</li>
<li>${t('move_note.clone_note_new_parent')}</li> <li>${t('move_note.clone_note_new_parent')}</li>
<li>${t('move_note.nothing_will_happen')}</li> <li>${t('move_note.nothing_will_happen')}</li>

View File

@@ -16,7 +16,7 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('rename_note.evaluated_as_js_string')}</p> <p>${t('rename_note.evaluated_as_js_string')}</p>

View File

@@ -25,9 +25,9 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <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>
</div> </div>

View File

@@ -25,11 +25,11 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('update_relation_target.on_all_matched_notes')}:</p> <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.create_given_relation')}</li>
<li>${t('update_relation_target.change_target_note')}</li> <li>${t('update_relation_target.change_target_note')}</li>
</ul> </ul>

View File

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

View File

@@ -27,7 +27,7 @@ const TPL = `
} }
</style> </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" aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"
style="position: relative; top: 3px;"></button> style="position: relative; top: 3px;"></button>
@@ -61,7 +61,8 @@ export default class AttachmentActionsWidget extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); 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 = this.$widget.find(".attachment-upload-new-revision-input");
this.$uploadNewRevisionInput.on('change', async () => { this.$uploadNewRevisionInput.on('change', async () => {
@@ -94,7 +95,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
); );
} }
} }
if (!isElectron){ if (!isElectron) {
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton $openAttachmentCustomButton
.addClass("disabled") .addClass("disabled")
@@ -138,7 +139,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
return; 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 })); toastService.showMessage(t('attachments_actions.convert_success', { title: this.attachment.title }));
await ws.waitForMaxKnownEntityChangeId(); await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newNote.noteId); await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
@@ -155,6 +156,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
return; return;
} }
await server.put(`attachments/${this.attachmentId}/rename`, {title: attachmentTitle}); await server.put(`attachments/${this.attachmentId}/rename`, { title: attachmentTitle });
} }
} }

View File

@@ -123,11 +123,13 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (note) { if (note) {
appContext.tabManager.getActiveContext().setNote(note.noteId); appContext.tabManager.getActiveContext().setNote(note.noteId);
this.hideDropdown(); this.dropdown.hide();
} }
else { else {
toastService.showError(t("calendar.cannot_find_day_note")); toastService.showError(t("calendar.cannot_find_day_note"));
} }
ev.stopPropagation();
}); });
// Prevent dismissing the calendar popup by clicking on an empty space inside it. // 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"); this.firstDayOfWeek = options.getInt("firstDayOfWeek");
// Generate the list of days of the week taking into consideration the user's selected first day of week. // 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); const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
localeDaysOfWeek = [ ...localeDaysOfWeek, ...daysToBeAddedAtEnd ]; localeDaysOfWeek = [...localeDaysOfWeek, ...daysToBeAddedAtEnd];
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`)); this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`));
} }
@@ -184,7 +186,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (dateNoteId) { if (dateNoteId) {
$newDay.addClass('calendar-date-exists'); $newDay.addClass('calendar-date-exists');
$newDay.attr("href", `#root/${dateNoteId}`); $newDay.attr("data-href", `#root/${dateNoteId}`);
} }
if (this.isEqual(this.date, this.activeDate)) { if (this.isEqual(this.date, this.activeDate)) {
@@ -236,7 +238,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.$yearSelect.val(this.date.getFullYear()); this.$yearSelect.val(this.date.getFullYear());
} }
async entitiesReloadedEvent({loadResults}) { async entitiesReloadedEvent({ loadResults }) {
if (!loadResults.getOptionNames().includes("firstDayOfWeek")) { if (!loadResults.getOptionNames().includes("firstDayOfWeek")) {
return; return;
} }

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ const TPL = `
} }
</style> </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> aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
<div class="dropdown-menu dropdown-menu-right"> <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 = this.$widget.find('.import-files-button');
this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId})); 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.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button"); this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");

View File

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

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 utils from "../../services/utils.js";
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js"; import BasicWidget from "../basic_widget.js";
import openService from "../../services/open.js";
const TPL = ` const TPL = `
<div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog"> <div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t("about.title")}</h5> <h5 class="modal-title">${t("about.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<table class="table table-borderless text-nowrap"> <table class="table table-borderless text-nowrap">
@@ -72,8 +71,19 @@ export default class AboutDialog extends BasicWidget {
this.$buildDate.text(appInfo.buildDate); this.$buildDate.text(appInfo.buildDate);
this.$buildRevision.text(appInfo.buildRevision); this.$buildRevision.text(appInfo.buildRevision);
this.$buildRevision.attr('href', `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`); this.$buildRevision.attr('href', `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`);
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); this.$dataDirectory.text(appInfo.dataDirectory);
} }
}
async openAboutDialogEvent() { async openAboutDialogEvent() {
await this.refresh(); await this.refresh();

View File

@@ -9,13 +9,9 @@ const TPL = `
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document"> <div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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="help-button" title="${t('add_link.help_on_links')}" data-help-page="links.html">?</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('add_link.close')}"></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>
</div> </div>
<form class="add-link-form"> <form class="add-link-form">
<div class="modal-body"> <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"> <form class="branch-prefix-form">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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 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="btn-close" data-bs-dismiss="modal" aria-label="${t('branch_prefix.close')}"></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>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
@@ -28,13 +24,10 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
<div class="input-group"> <div class="input-group">
<input class="branch-prefix-input form-control"> <input class="branch-prefix-input form-control">
<div class="input-group-append">
<div class="branch-prefix-note-title input-group-text"></div> <div class="branch-prefix-note-title input-group-text"></div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary btn-sm">${t('branch_prefix.save')}</button> <button class="btn btn-primary btn-sm">${t('branch_prefix.save')}</button>
</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 { export default class BranchPrefixDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".branch-prefix-form"); this.$form = this.$widget.find(".branch-prefix-form");
this.$treePrefixInput = this.$widget.find(".branch-prefix-input"); this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
this.$noteTitle = this.$widget.find('.branch-prefix-note-title'); this.$noteTitle = this.$widget.find('.branch-prefix-note-title');
@@ -60,7 +54,7 @@ export default class BranchPrefixDialog extends BasicWidget {
} }
async refresh(notePath) { async refresh(notePath) {
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath); const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) { if (!noteId || !parentNoteId) {
return; return;
@@ -97,9 +91,9 @@ export default class BranchPrefixDialog extends BasicWidget {
async savePrefix() { async savePrefix() {
const prefix = this.$treePrefixInput.val(); 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')); 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-dialog modal-xl" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t('bulk_actions.bulk_actions')}</h5> <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>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('bulk_actions.close')}">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h4>${t('bulk_actions.affected_notes')}: <span class="affected-note-count">0</span></h4> <h4>${t('bulk_actions.affected_notes')}: <span class="affected-note-count">0</span></h4>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="include-descendants form-check-input" type="checkbox" value=""> <input class="include-descendants form-check-input" type="checkbox" value="">
${t('bulk_actions.include_descendants')} <label class="form-check-label">${t('bulk_actions.include_descendants')}</label>
</label>
</div> </div>
<h4>${t('bulk_actions.available_actions')}</h4> <h4>${t('bulk_actions.available_actions')}</h4>
@@ -72,7 +67,6 @@ const TPL = `
export default class BulkActionsDialog extends BasicWidget { export default class BulkActionsDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$includeDescendants = this.$widget.find(".include-descendants"); this.$includeDescendants = this.$widget.find(".include-descendants");
this.$includeDescendants.on("change", () => this.refresh()); 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-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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="help-button" title="${t('clone_to.help_on_links')}" data-help-page="cloning-notes.html">?</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<form class="clone-to-form"> <form class="clone-to-form">
<div class="modal-body"> <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) { if (!noteIds || noteIds.length === 0) {
noteIds = [ appContext.tabManager.getActiveContextNoteId() ]; noteIds = [appContext.tabManager.getActiveContextNoteId()];
} }
this.clonedNoteIds = []; this.clonedNoteIds = [];
@@ -111,7 +107,7 @@ export default class CloneToDialog extends BasicWidget {
} }
async cloneNotesTo(notePath) { async cloneNotesTo(notePath) {
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath); const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
const targetBranchId = await froca.getBranchId(parentNoteId, noteId); const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
for (const cloneNoteId of this.clonedNoteIds) { for (const cloneNoteId of this.clonedNoteIds) {
@@ -120,7 +116,7 @@ export default class CloneToDialog extends BasicWidget {
const clonedNote = await froca.getNote(cloneNoteId); const clonedNote = await froca.getNote(cloneNoteId);
const targetNote = await froca.getBranch(targetBranchId).getNote(); 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-dialog modal-dialog-scrollable" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t('confirm.confirmation')}</h5> <h5 class="modal-title">${t('confirm.confirmation')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="confirm-dialog-content"></div> <div class="confirm-dialog-content"></div>
@@ -40,6 +37,7 @@ export default class ConfirmDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$confirmContent = this.$widget.find(".confirm-dialog-content"); this.$confirmContent = this.$widget.find(".confirm-dialog-content");
this.$okButton = this.$widget.find(".confirm-dialog-ok-button"); this.$okButton = this.$widget.find(".confirm-dialog-ok-button");
this.$cancelButton = this.$widget.find(".confirm-dialog-cancel-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)); this.$okButton.on('click', () => this.doResolve(true));
} }
showConfirmDialogEvent({message, callback}) { showConfirmDialogEvent({ message, callback }) {
this.$originallyFocused = $(':focus'); this.$originallyFocused = $(':focus');
this.$custom.hide(); this.$custom.hide();
@@ -75,15 +73,15 @@ export default class ConfirmDialog extends BasicWidget {
this.$confirmContent.empty().append(message); this.$confirmContent.empty().append(message);
this.$widget.modal(); this.modal.show();
this.resolve = callback; this.resolve = callback;
} }
showConfirmDeleteNoteBoxWithNoteDialogEvent({title, callback}) { showConfirmDeleteNoteBoxWithNoteDialogEvent({ title, callback }) {
glob.activeDialog = this.$widget; 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() this.$custom.empty()
.append("<br/>") .append("<br/>")
@@ -104,7 +102,7 @@ export default class ConfirmDialog extends BasicWidget {
this.$custom.show(); this.$custom.show();
this.$widget.modal(); this.modal.show();
this.resolve = callback; this.resolve = callback;
} }
@@ -117,6 +115,6 @@ export default class ConfirmDialog extends BasicWidget {
this.resolve = null; 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-dialog modal-dialog-scrollable modal-xl" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title mr-auto">${t('delete_notes.delete_notes_preview')}</h4> <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>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="checkbox"> <div class="form-checkbox">
<label> <input class="delete-all-clones form-check-input" value="1" type="checkbox">
<input class="delete-all-clones" value="1" type="checkbox"> <label class="form-check-label">${t('delete_notes.delete_all_clones_description')}</label>
${t('delete_notes.delete_all_clones_description')}
</label>
</div> </div>
<div class="checkbox"> <div class="form-checkbox" style="margin-bottom: 1rem">
<label title="${t('delete_notes.erase_notes_description')}"> <input class="erase-notes form-check-input" value="1" type="checkbox">
<input class="erase-notes" value="1" type="checkbox"> <label class="form-check-label">${t('delete_notes.erase_notes_warning')}</label>
${t('delete_notes.erase_notes_warning')}
</label>
</div> </div>
<div class="delete-notes-list-wrapper"> <div class="delete-notes-list-wrapper">

View File

@@ -34,9 +34,7 @@ const TPL = `
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">${t('export.export_note_title')} <span class="export-note-title"></span></h5> <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')}"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('export.close')}"></button>
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<form class="export-form"> <form class="export-form">
<div class="modal-body"> <div class="modal-body">
@@ -127,6 +125,7 @@ export default class ExportDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".export-form"); this.$form = this.$widget.find(".export-form");
this.$noteTitle = this.$widget.find(".export-note-title"); this.$noteTitle = this.$widget.find(".export-note-title");
this.$subtreeFormats = this.$widget.find(".export-subtree-formats"); 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.$opmlVersions = this.$widget.find(".opml-versions");
this.$form.on('submit', () => { this.$form.on('submit', () => {
this.$widget.modal('hide'); this.modal.hide();
const exportType = this.$widget.find("input[name='export-type']:checked").val(); 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.taskId = '';
this.$exportButton.removeAttr("disabled"); this.$exportButton.removeAttr("disabled");
@@ -208,7 +207,7 @@ export default class ExportDialog extends BasicWidget {
utils.openDialog(this.$widget); utils.openDialog(this.$widget);
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath); const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
this.branchId = await froca.getBranchId(parentNoteId, noteId); this.branchId = await froca.getBranchId(parentNoteId, noteId);
this.$noteTitle.text(await treeService.getNoteTitle(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-dialog" role="document" style="min-width: 100%; height: 100%; margin: 0;">
<div class="modal-content" style="height: auto;"> <div class="modal-content" style="height: auto;">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t('help.fullDocumentation')}</h5> <h5 class="modal-title">${t('help.fullDocumentation')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('help.close')}"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('help.close')}">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body" style="overflow: auto; height: calc(100vh - 70px);"> <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">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">${t('help.noteNavigation')}</h5> <h5 class="card-title">${t('help.noteNavigation')}</h5>

View File

@@ -11,9 +11,7 @@ const TPL = `
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">${t('import.importIntoNote')}</h5> <h5 class="modal-title">${t('import.importIntoNote')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('import.close')}"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('import.close')}"></button>
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<form class="import-form"> <form class="import-form">
<div class="modal-body"> <div class="modal-body">
@@ -29,21 +27,21 @@ const TPL = `
<strong>${t('import.options')}:</strong> <strong>${t('import.options')}:</strong>
<div class="checkbox"> <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> <input class="safe-import-checkbox" value="1" type="checkbox" checked>
<span>${t('import.safeImport')}</span> <span>${t('import.safeImport')}</span>
</label> </label>
</div> </div>
<div class="checkbox"> <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> <input class="explode-archives-checkbox" value="1" type="checkbox" checked>
<span>${t('import.explodeArchives')}</span> <span>${t('import.explodeArchives')}</span>
</label> </label>
</div> </div>
<div class="checkbox"> <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> <input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t('import.shrinkImages')}</span>
</label> </label>
</div> </div>
@@ -86,6 +84,8 @@ export default class ImportDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".import-form"); this.$form = this.$widget.find(".import-form");
this.$noteTitle = this.$widget.find(".import-note-title"); this.$noteTitle = this.$widget.find(".import-note-title");
this.$fileUploadInput = this.$widget.find(".import-file-upload-input"); 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({ let _ = [...this.$widget.find('[data-bs-toggle="tooltip"]')].forEach(element => {
bootstrap.Tooltip.getOrCreateInstance(element, {
html: true html: true
}); });
});
} }
async showImportDialogEvent({noteId}) { async showImportDialogEvent({ noteId }) {
this.parentNoteId = noteId; this.parentNoteId = noteId;
this.$fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below 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-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">${t('include_note.dialog_title')}</h5> <h5 class="modal-title">${t('include_note.dialog_title')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<form class="include-note-form"> <form class="include-note-form">
<div class="modal-body"> <div class="modal-body">
@@ -27,22 +25,16 @@ const TPL = `
${t('include_note.box_size_prompt')} ${t('include_note.box_size_prompt')}
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="small"> <input class="form-check-input" type="radio" name="include-note-box-size" value="small">
${t('include_note.box_size_small')} <label class="form-check-label">${t('include_note.box_size_small')}</label>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked> <input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
${t('include_note.box_size_medium')} <label class="form-check-label">${t('include_note.box_size_medium')}</label>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="full"> <input class="form-check-input" type="radio" name="include-note-box-size" value="full">
${t('include_note.box_size_full')} <label class="form-check-label">${t('include_note.box_size_full')}</label>
</label>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@@ -56,13 +48,14 @@ const TPL = `
export default class IncludeNoteDialog extends BasicWidget { export default class IncludeNoteDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".include-note-form"); this.$form = this.$widget.find(".include-note-form");
this.$autoComplete = this.$widget.find(".include-note-autocomplete"); this.$autoComplete = this.$widget.find(".include-note-autocomplete");
this.$form.on('submit', () => { this.$form.on('submit', () => {
const notePath = this.$autoComplete.getSelectedNotePath(); const notePath = this.$autoComplete.getSelectedNotePath();
if (notePath) { if (notePath) {
this.$widget.modal('hide'); this.modal.hide();
this.includeNote(notePath); this.includeNote(notePath);
} else { } else {
logError("No noteId to include."); logError("No noteId to include.");
@@ -72,7 +65,7 @@ export default class IncludeNoteDialog extends BasicWidget {
}) })
} }
async showIncludeNoteDialogEvent({textTypeWidget}) { async showIncludeNoteDialogEvent({ textTypeWidget }) {
this.textTypeWidget = textTypeWidget; this.textTypeWidget = textTypeWidget;
await this.refresh(); await this.refresh();
utils.openDialog(this.$widget); utils.openDialog(this.$widget);

View File

@@ -7,11 +7,8 @@ const TPL = `
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t("info.modalTitle")}</h5> <h5 class="modal-title">${t("info.modalTitle")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("info.closeButton")}"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="${t("info.closeButton")}">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="info-dialog-content"></div> <div class="info-dialog-content"></div>
@@ -33,6 +30,7 @@ export default class InfoDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$infoContent = this.$widget.find(".info-dialog-content"); this.$infoContent = this.$widget.find(".info-dialog-content");
this.$okButton = this.$widget.find(".info-dialog-ok-button"); 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.$originallyFocused = $(':focus');
this.$infoContent.text(message); 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"> <div class="input-group">
<input class="jump-to-note-autocomplete form-control" placeholder="${t('jump_to_note.search_placeholder')}"> <input class="jump-to-note-autocomplete form-control" placeholder="${t('jump_to_note.search_placeholder')}">
</div> </div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="algolia-autocomplete-container jump-to-note-results"></div> <div class="algolia-autocomplete-container jump-to-note-results"></div>
@@ -38,6 +35,8 @@ export default class JumpToNoteDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$autoComplete = this.$widget.find(".jump-to-note-autocomplete"); this.$autoComplete = this.$widget.find(".jump-to-note-autocomplete");
this.$results = this.$widget.find(".jump-to-note-results"); this.$results = this.$widget.find(".jump-to-note-results");
this.$showInFullTextButton = this.$widget.find(".show-in-full-text-button"); 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(); 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-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">${t("markdown_import.dialog_title")}</h5> <h5 class="modal-title">${t("markdown_import.dialog_title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>${t("markdown_import.modal_body_text")}</p> <p>${t("markdown_import.modal_body_text")}</p>
@@ -37,6 +35,7 @@ export default class MarkdownImportDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$importTextarea = this.$widget.find('.markdown-import-textarea'); this.$importTextarea = this.$widget.find('.markdown-import-textarea');
this.$importButton = this.$widget.find('.markdown-import-button'); this.$importButton = this.$widget.find('.markdown-import-button');
@@ -48,7 +47,7 @@ export default class MarkdownImportDialog extends BasicWidget {
} }
async convertMarkdownToHtml(markdownContent) { 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(); const textEditor = await appContext.tabManager.getActiveContext().getTextEditor();
@@ -70,7 +69,7 @@ export default class MarkdownImportDialog extends BasicWidget {
} }
if (utils.isElectron()) { if (utils.isElectron()) {
const {clipboard} = utils.dynamicRequire('electron'); const { clipboard } = utils.dynamicRequire('electron');
const text = clipboard.readText(); const text = clipboard.readText();
this.convertMarkdownToHtml(text); this.convertMarkdownToHtml(text);
@@ -83,7 +82,7 @@ export default class MarkdownImportDialog extends BasicWidget {
async sendForm() { async sendForm() {
const text = this.$importTextarea.val(); const text = this.$importTextarea.val();
this.$widget.modal('hide'); this.modal.hide();
await this.convertMarkdownToHtml(text); await this.convertMarkdownToHtml(text);

View File

@@ -5,18 +5,15 @@ import froca from "../../services/froca.js";
import branchService from "../../services/branches.js"; import branchService from "../../services/branches.js";
import treeService from "../../services/tree.js"; import treeService from "../../services/tree.js";
import BasicWidget from "../basic_widget.js"; import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js"; // Added import import { t } from "../../services/i18n.js";
const TPL = ` const TPL = `
<div class="move-to-dialog modal mx-auto" tabindex="-1" role="dialog"> <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-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t("move_to.dialog_title")}</h5> <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>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<form class="move-to-form"> <form class="move-to-form">
<div class="modal-body"> <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-dialog" style="max-width: 500px;" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t("note_type_chooser.modal_title")}</h5> <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>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
${t("note_type_chooser.modal_body")} ${t("note_type_chooser.modal_body")}
<div class="dropdown"> <div class="dropdown" style="display: flex;">
<button class="note-type-dropdown-trigger" type="button" style="display: none;" data-toggle="dropdown"></button> <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 class="note-type-dropdown dropdown-menu"></div>
</div> </div>
@@ -53,13 +52,14 @@ export default class NoteTypeChooserDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown"); this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
this.$noteTypeDropdownTrigger = this.$widget.find(".note-type-dropdown-trigger"); this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find(".note-type-dropdown-trigger"));
this.$noteTypeDropdownTrigger.dropdown();
this.$widget.on("hidden.bs.modal", () => { this.$widget.on("hidden.bs.modal", () => {
if (this.resolve) { if (this.resolve) {
this.resolve({success: false}); this.resolve({ success: false });
} }
if (this.$originalFocused) { if (this.$originalFocused) {
@@ -94,7 +94,7 @@ export default class NoteTypeChooserDialog extends BasicWidget {
}); });
} }
async chooseNoteTypeEvent({callback}) { async chooseNoteTypeEvent({ callback }) {
this.$originalFocused = $(':focus'); this.$originalFocused = $(':focus');
const noteTypes = await noteTypesService.getNoteTypeItems(); 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; this.$originalDialog = glob.activeDialog;
glob.activeDialog = this.$widget; glob.activeDialog = this.modal;
this.$widget.modal(); this.modal.show();
this.$noteTypeDropdown.find(".dropdown-item:first").focus(); this.$noteTypeDropdown.find(".dropdown-item:first").focus();
@@ -139,6 +139,6 @@ export default class NoteTypeChooserDialog extends BasicWidget {
}); });
this.resolve = null; 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-dialog modal-md" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t("password_not_set.title")}</h5> <h5 class="modal-title">${t("password_not_set.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
${t("password_not_set.body1")} ${t("password_not_set.body1")}
@@ -26,8 +23,10 @@ const TPL = `
export default class PasswordNoteSetDialog extends BasicWidget { export default class PasswordNoteSetDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$openPasswordOptionsButton = this.$widget.find(".open-password-options-button"); this.$openPasswordOptionsButton = this.$widget.find(".open-password-options-button");
this.$openPasswordOptionsButton.on("click", () => { this.$openPasswordOptionsButton.on("click", () => {
this.modal.hide();
this.triggerCommand("showOptions", { section: '_optionsPassword' }); this.triggerCommand("showOptions", { section: '_optionsPassword' });
}); });
} }

View File

@@ -8,13 +8,10 @@ const TPL = `
<div class="modal-content"> <div class="modal-content">
<form class="prompt-dialog-form"> <form class="prompt-dialog-form">
<div class="modal-header"> <div class="modal-header">
<h5 class="prompt-title modal-title mr-auto">${t("prompt.title")}</h5> <h5 class="prompt-title modal-title">${t("prompt.title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
</div> </div>
<div class="modal-body"></div>
<div class="modal-footer"> <div class="modal-footer">
<button class="prompt-dialog-ok-button btn btn-primary btn-sm">${t("prompt.ok")}</button> <button class="prompt-dialog-ok-button btn btn-primary btn-sm">${t("prompt.ok")}</button>
</div> </div>
@@ -33,6 +30,7 @@ export default class PromptDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$dialogBody = this.$widget.find(".modal-body"); this.$dialogBody = this.$widget.find(".modal-body");
this.$form = this.$widget.find(".prompt-dialog-form"); this.$form = this.$widget.find(".prompt-dialog-form");
this.$question = null; this.$question = null;
@@ -61,7 +59,7 @@ export default class PromptDialog extends BasicWidget {
e.preventDefault(); e.preventDefault();
this.resolve(this.$answer.val()); 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-dialog modal-md" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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 class="help-button" type="button" data-help-page="protected-notes.html" title="${t("protected_session_password.help_title")}">?</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("protected_session_password.close_label")}"></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>
</div> </div>
<form class="protected-session-password-form"> <form class="protected-session-password-form">
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <label class="col-form-label">${t("protected_session_password.form_label")}</label>
<label>
${t("protected_session_password.form_label")}
<input class="form-control protected-session-password" type="password"> <input class="form-control protected-session-password" type="password">
</label>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-primary">${t("protected_session_password.start_button")}</button> <button class="btn btn-primary">${t("protected_session_password.start_button")}</button>
@@ -36,6 +28,8 @@ const TPL = `
export default class ProtectedSessionPasswordDialog extends BasicWidget { export default class ProtectedSessionPasswordDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$passwordForm = this.$widget.find(".protected-session-password-form"); this.$passwordForm = this.$widget.find(".protected-session-password-form");
this.$passwordInput = this.$widget.find(".protected-session-password"); this.$passwordInput = this.$widget.find(".protected-session-password");
this.$passwordForm.on('submit', () => { this.$passwordForm.on('submit', () => {
@@ -55,6 +49,6 @@ export default class ProtectedSessionPasswordDialog extends BasicWidget {
} }
closeProtectedSessionPasswordDialogEvent() { 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-dialog modal-lg modal-dialog-scrollable" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t('recent_changes.title')}</h5> <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 class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
${t('recent_changes.erase_notes_button')}</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="recent-changes-content"></div> <div class="recent-changes-content"></div>
@@ -34,6 +29,8 @@ const TPL = `
export default class RecentChangesDialog extends BasicWidget { export default class RecentChangesDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$content = this.$widget.find(".recent-changes-content"); this.$content = this.$widget.find(".recent-changes-content");
this.$eraseDeletedNotesNow = this.$widget.find(".erase-deleted-notes-now-button"); this.$eraseDeletedNotesNow = this.$widget.find(".erase-deleted-notes-now-button");
this.$eraseDeletedNotesNow.on("click", () => { this.$eraseDeletedNotesNow.on("click", () => {
@@ -45,7 +42,7 @@ export default class RecentChangesDialog extends BasicWidget {
}); });
} }
async showRecentChangesEvent({ancestorNoteId}) { async showRecentChangesEvent({ ancestorNoteId }) {
this.ancestorNoteId = ancestorNoteId; this.ancestorNoteId = ancestorNoteId;
await this.refresh(); await this.refresh();
@@ -93,7 +90,7 @@ export default class RecentChangesDialog extends BasicWidget {
if (await dialogService.confirm(text)) { if (await dialogService.confirm(text)) {
await server.put(`notes/${change.noteId}/undelete`); await server.put(`notes/${change.noteId}/undelete`);
this.$widget.modal('hide'); this.modal.hide();
await ws.waitForMaxKnownEntityChangeId(); await ws.waitForMaxKnownEntityChangeId();

View File

@@ -40,21 +40,18 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document"> <div class="modal-dialog modal-xl" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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" <button class="revisions-erase-all-revisions-button btn btn-sm"
title="${t("revisions.delete_all_revisions")}" title="${t("revisions.delete_all_revisions")}"
style="padding: 0 10px 0 10px;" type="button">${t("revisions.delete_all_button")}</button> 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 class="help-button" type="button" data-help-page="note-revisions.html" title="${t("revisions.help_title")}">?</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body" style="display: flex; height: 80vh;"> <div class="modal-body" style="display: flex; height: 80vh;">
<div class="dropdown"> <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 class="revision-list dropdown-menu" style="position: static; height: 100%; overflow: auto;"></div>
</div> </div>
@@ -84,6 +81,8 @@ export default class RevisionsDialog extends BasicWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$list = this.$widget.find(".revision-list"); this.$list = this.$widget.find(".revision-list");
this.$listDropdown = this.$widget.find(".revision-list-dropdown"); this.$listDropdown = this.$widget.find(".revision-list-dropdown");
this.$content = this.$widget.find(".revision-content"); this.$content = this.$widget.find(".revision-content");
@@ -111,7 +110,7 @@ export default class RevisionsDialog extends BasicWidget {
if (await dialogService.confirm(text)) { if (await dialogService.confirm(text)) {
await server.remove(`notes/${this.note.noteId}/revisions`); await server.remove(`notes/${this.note.noteId}/revisions`);
this.$widget.modal('hide'); this.modal.hide();
toastService.showMessage(t("revisions.revisions_deleted")); 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); utils.openDialog(this.$widget);
await this.loadRevisions(noteId); await this.loadRevisions(noteId);
@@ -191,7 +190,7 @@ export default class RevisionsDialog extends BasicWidget {
if (await dialogService.confirm(text)) { if (await dialogService.confirm(text)) {
await server.post(`revisions/${revisionItem.revisionId}/restore`); await server.post(`revisions/${revisionItem.revisionId}/restore`);
this.$widget.modal('hide'); this.modal.hide();
toastService.showMessage(t("revisions.revision_restored")); 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) { if (this.$content.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX); await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement(this.$content[0], {trust: true}); renderMathInElement(this.$content[0], { trust: true });
} }
} else if (revisionItem.type === 'code') { } else if (revisionItem.type === 'code') {
this.$content.html($("<pre>").text(fullRevision.content)); this.$content.html($("<pre>").text(fullRevision.content));
@@ -274,7 +273,7 @@ export default class RevisionsDialog extends BasicWidget {
} }
this.$content.html($table); this.$content.html($table);
} else if ([ "canvas", "mindMap" ].includes(revisionItem.type)) { } else if (["canvas", "mindMap"].includes(revisionItem.type)) {
const encodedTitle = encodeURIComponent(revisionItem.title); const encodedTitle = encodeURIComponent(revisionItem.title);
this.$content.html($("<img>") 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-dialog modal-lg" style="max-width: 500px" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title mr-auto">${t("sort_child_notes.sort_children_by")}</h5> <h5 class="modal-title">${t("sort_child_notes.sort_children_by")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<form class="sort-child-notes-form"> <form class="sort-child-notes-form">
<div class="modal-body"> <div class="modal-body">
<h5>${t("sort_child_notes.sorting_criteria")}</h5> <h5>${t("sort_child_notes.sorting_criteria")}</h5>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="title" checked> <input class="form-check-input" type="radio" name="sort-by" value="title" checked>
${t("sort_child_notes.title")} <label class="form-check-label">${t("sort_child_notes.title")}</label>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="dateCreated"> <input class="form-check-input" type="radio" name="sort-by" value="dateCreated">
${t("sort_child_notes.date_created")} <label class="form-check-label">${t("sort_child_notes.date_created")}</label>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="dateModified"> <input class="form-check-input" type="radio" name="sort-by" value="dateModified">
${t("sort_child_notes.date_modified")} <label class="form-check-label">${t("sort_child_notes.date_modified")}</label>
</label>
</div> </div>
<br/> <br/>
<h5>${t("sort_child_notes.sorting_direction")}</h5> <h5>${t("sort_child_notes.sorting_direction")}</h5>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-direction" value="asc" checked> <input class="form-check-input" type="radio" name="sort-direction" value="asc" checked>
${t("sort_child_notes.ascending")} <label class="form-check-label">${t("sort_child_notes.ascending")}</label>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-direction" value="desc"> <input class="form-check-input" type="radio" name="sort-direction" value="desc">
${t("sort_child_notes.descending")} <label class="form-check-label">${t("sort_child_notes.descending")}</label>
</label>
</div> </div>
<br /> <br />
<h5>${t("sort_child_notes.folders")}</h5> <h5>${t("sort_child_notes.folders")}</h5>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="sort-folders-first" value="1"> <input class="form-check-input" type="checkbox" name="sort-folders-first" value="1">
${t("sort_child_notes.sort_folders_at_top")} <label class="form-check-label">${t("sort_child_notes.sort_folders_at_top")}</label>
</label>
</div> </div>
<br /> <br />
<h5>${t("sort_child_notes.natural_sort")}</h5> <h5>${t("sort_child_notes.natural_sort")}</h5>
<div class="form-check"> <div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="sort-natural" value="1"> <input class="form-check-input" type="checkbox" name="sort-natural" value="1">
${t("sort_child_notes.sort_with_respect_to_different_character_sorting")} <label class="form-check-label">${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}</label>
</label>
</div> </div>
<br /> <br />
<div class="form-check"> <div class="form-check">

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
* https://github.com/antoniotejada/Trilium-FindWidget * https://github.com/antoniotejada/Trilium-FindWidget
*/ */
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js";
import FindInText from "./find_in_text.js"; import FindInText from "./find_in_text.js";
import FindInCode from "./find_in_code.js"; import FindInCode from "./find_in_code.js";
@@ -47,24 +48,18 @@ const TPL = `
<div class="find-widget-box"> <div class="find-widget-box">
<div class="input-group find-widget-search-term-input-group"> <div class="input-group find-widget-search-term-input-group">
<input type="text" class="form-control find-widget-search-term-input"> <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-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> <button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
</div> </div>
</div>
<div class="form-check"> <div class="form-check">
<label tabIndex="-1" class="form-check-label">
<input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox"> <input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">
case sensitive <label tabIndex="-1" class="form-check-label">${t('find.case_sensitive')}</label>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label tabIndex="-1" class="form-check-label">
<input type="checkbox" class="form-check-input find-widget-match-words-checkbox"> <input type="checkbox" class="form-check-input find-widget-match-words-checkbox">
match words <label tabIndex="-1" class="form-check-label">${t('find.match_words')}</label>
</label>
</div> </div>
<div class="find-widget-found-wrapper"> <div class="find-widget-found-wrapper">

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 * - 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 attributeService from "../services/attributes.js";
import RightPanelWidget from "./right_panel_widget.js"; import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js"; import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js"; import OnClickButtonWidget from "./buttons/onclick_button.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
const TPL = `<div class="highlights-list-widget"> const TPL = `<div class="highlights-list-widget">
<style> <style>
@@ -28,7 +30,6 @@ const TPL = `<div class="highlights-list-widget">
cursor: pointer; cursor: pointer;
margin-bottom: 3px; margin-bottom: 3px;
text-align: justify; text-align: justify;
text-justify: distribute;
word-wrap: break-word; word-wrap: break-word;
hyphens: auto; hyphens: auto;
} }
@@ -43,16 +44,16 @@ const TPL = `<div class="highlights-list-widget">
export default class HighlightsListWidget extends RightPanelWidget { export default class HighlightsListWidget extends RightPanelWidget {
get widgetTitle() { get widgetTitle() {
return "Highlights List"; return t("highlights_list_2.title");
} }
get widgetButtons() { get widgetButtons() {
return [ return [
new OnClickButtonWidget() new OnClickButtonWidget()
.icon("bx-slider") .icon("bx-cog")
.title("Options") .title(t("highlights_list_2.options"))
.titlePlacement("left") .titlePlacement("left")
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true})) .onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', { activate: true }))
.class("icon-action"), .class("icon-action"),
new OnClickButtonWidget() new OnClickButtonWidget()
.icon("bx-x") .icon("bx-x")
@@ -97,8 +98,8 @@ export default class HighlightsListWidget extends RightPanelWidget {
let $highlightsList = "", hlLiCount = -1; let $highlightsList = "", hlLiCount = -1;
// Check for type text unconditionally in case alwaysShowWidget is set // Check for type text unconditionally in case alwaysShowWidget is set
if (this.note.type === 'text') { if (this.note.type === 'text') {
const {content} = await note.getNoteComplement(); const { content } = await note.getNoteComplement();
({$highlightsList, hlLiCount} = this.getHighlightList(content, optionsHighlightsList)); ({ $highlightsList, hlLiCount } = await this.getHighlightList(content, optionsHighlightsList));
} }
this.$highlightsList.empty().append($highlightsList); this.$highlightsList.empty().append($highlightsList);
if (hlLiCount > 0) { if (hlLiCount > 0) {
@@ -112,7 +113,79 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility"); 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 // matches a span containing background-color
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi; const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
// matches a span containing color // matches a span containing color
@@ -152,6 +225,10 @@ export default class HighlightsListWidget extends RightPanelWidget {
const combinedRegex = new RegExp(combinedRegexStr, 'gi'); const combinedRegex = new RegExp(combinedRegexStr, 'gi');
const $highlightsList = $("<ol>"); const $highlightsList = $("<ol>");
let prevEndIndex = -1, hlLiCount = 0; 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++) { for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
const subHtml = match[0]; const subHtml = match[0];
const startIndex = match.index; const startIndex = match.index;
@@ -166,11 +243,19 @@ export default class HighlightsListWidget extends RightPanelWidget {
const hasText = $(subHtml).text().trim(); const hasText = $(subHtml).text().trim();
if (hasText) { if (hasText) {
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( $highlightsList.append(
$('<li>') $('<li>')
.html(subHtml) .html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex)) .on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
); );
}
hlLiCount++; hlLiCount++;
} else { } else {
@@ -179,6 +264,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
} }
} }
prevEndIndex = endIndex; prevEndIndex = endIndex;
prevSubHtml = subHtml;
} }
return { return {
$highlightsList, $highlightsList,
@@ -232,9 +318,17 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.noteContext.viewScope.highlightsListTemporarilyHidden = true; this.noteContext.viewScope.highlightsListTemporarilyHidden = true;
await this.refresh(); await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility'); 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)) { if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh(); await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label' } 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 libraryLoader from "../services/library_loader.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js";
import server from "../services/server.js"; import server from "../services/server.js";
@@ -27,7 +28,7 @@ const TPL = `<div class="mermaid-widget">
</style> </style>
<div class="mermaid-error alert alert-warning"> <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> <p class="error-content"></p>
</div> </div>

View File

@@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js";
import attributeService from "../services/attributes.js"; import attributeService from "../services/attributes.js";
import server from "../services/server.js"; import server from "../services/server.js";
@@ -66,7 +67,7 @@ const TPL = `
} }
</style> </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="dropdown-menu" aria-labelledby="note-path-list-button" style="width: 610px;">
<div class="filter-row"> <div class="filter-row">
<span>Category:</span> <select name="icon-category" class="form-control"></select> <span>Category:</span> <select name="icon-category" class="form-control"></select>

View File

@@ -106,7 +106,7 @@ const TPL = `
title="Scroll to active note" title="Scroll to active note"
data-trigger-command="scrollToActiveNote"></button> 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> title="Tree settings"></button>
</div> </div>

View File

@@ -35,7 +35,7 @@ const TPL = `
overflow-x: hidden; overflow-x: hidden;
} }
</style> </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="note-type-desc"></span>
<span class="caret"></span> <span class="caret"></span>
</button> </button>
@@ -47,14 +47,15 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$widget.on('show.bs.dropdown', () => this.renderDropdown()); this.$widget.on('show.bs.dropdown', () => this.renderDropdown());
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown"); this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
this.$noteTypeButton = this.$widget.find(".note-type-button"); this.$noteTypeButton = this.$widget.find(".note-type-button");
this.$noteTypeDesc = this.$widget.find(".note-type-desc"); this.$noteTypeDesc = this.$widget.find(".note-type-desc");
this.$widget.on('click', '.dropdown-item', this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
} }
async refreshWithNote(note) { async refreshWithNote(note) {
@@ -63,7 +64,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime)); 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 */ /** the actual body is rendered lazily on note-type button click */
@@ -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?"); 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)) { if (loadResults.isNoteReloaded(this.noteId)) {
this.refresh(); this.refresh();
} }

View File

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

View File

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

View File

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

View File

@@ -119,7 +119,7 @@ const TPL = `
</button> </button>
<div class="dropdown" style="display: inline-block;"> <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> <span class="bx bxs-zap"></span>
${t('search_definition.action')} ${t('search_definition.action')}
</button> </button>
@@ -219,7 +219,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
}); });
this.$widget.on('click', '[data-action-add]', async event => { 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'); 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> <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="">${t('ancestor.depth_doesnt_matter')}</option>
<option value="eq1">${t('ancestor.depth_eq', {count: 1})} (${t('ancestor.direct_children')})</option> <option value="eq1">${t('ancestor.depth_eq', {count: 1})} (${t('ancestor.direct_children')})</option>
<option value="eq2">${t('ancestor.depth_eq', {count: 2})}</option> <option value="eq2">${t('ancestor.depth_eq', {count: 2})}</option>

View File

@@ -9,10 +9,10 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("debug.debug_info")}</p> <p>${t("debug.debug_info")}</p>
<p>${t("debug.access_info")}</p> ${t("debug.access_info")}
</div> </div>
</div> </div>
<span class="bx bx-x icon-action search-option-del"></span> <span class="bx bx-x icon-action search-option-del"></span>

View File

@@ -9,7 +9,7 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
${t('fast_search.description')} ${t('fast_search.description')}
</div> </div>

View File

@@ -12,7 +12,7 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
${t('limit.take_first_x_results')} ${t('limit.take_first_x_results')}
</div> </div>

View File

@@ -14,7 +14,7 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('search_script.description1')}</p> <p>${t('search_script.description1')}</p>
@@ -24,7 +24,7 @@ const TPL = `
<pre>${t('search_script.example_code')}</pre> <pre>${t('search_script.example_code')}</pre>
<p>${t('search_script.note')}</p> ${t('search_script.note')}
</div> </div>
</div> </div>

View File

@@ -13,11 +13,11 @@ const TPL = `
</td> </td>
<td class="button-column"> <td class="button-column">
<div class="dropdown help-dropdown"> <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"> <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> <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>
<p>
<ul> <ul style="marigin-bottom: 0;">
<li>${t('search_string.full_text_search')}</li> <li>${t('search_string.full_text_search')}</li>
<li><code>#abc</code> - ${t('search_string.label_abc')}</li> <li><code>#abc</code> - ${t('search_string.label_abc')}</li>
<li><code>#year = 2019</code> - ${t('search_string.label_year')}</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>#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> <li><code>note.dateCreated >= MONTH-1</code> - ${t('search_string.label_date_created')}</li>
</ul> </ul>
</p>
</div> </div>
</div> </div>
@@ -74,16 +73,16 @@ export default class SearchString extends AbstractSearchOption {
return $option; return $option;
} }
showSearchErrorEvent({error}) { showSearchErrorEvent({ error }) {
this.$searchString.tooltip({ let tooltip = new bootstrap.Tooltip(this.$searchString, {
trigger: 'manual', trigger: 'manual',
title: `${t('search_string.error', {error})}`, title: `${t('search_string.error', { error })}`,
placement: 'bottom' placement: 'bottom'
}); });
this.$searchString.tooltip("show"); tooltip.show();
setTimeout(() => this.$searchString.tooltip("dispose"), 4000); setTimeout(() => tooltip.dispose(), 4000);
} }
focusOnSearchDefinitionEvent() { focusOnSearchDefinitionEvent() {

View File

@@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import BasicWidget from "./basic_widget.js"; import BasicWidget from "./basic_widget.js";
import ws from "../services/ws.js"; import ws from "../services/ws.js";
import options from "../services/options.js"; import options from "../services/options.js";
@@ -39,36 +40,30 @@ const TPL = `
<div class="sync-status"> <div class="sync-status">
<span class="sync-status-icon sync-status-unknown bx bx-time" <span class="sync-status-icon sync-status-unknown bx bx-time"
data-toggle="tooltip" data-bs-toggle="tooltip"
data-placement="right" title="${t("sync_status.unknown")}">
title="<p>Sync status will be known once the next sync attempt starts.</p><p>Click to trigger sync now.</p>">
</span> </span>
<span class="sync-status-icon sync-status-connected-with-changes bx bx-wifi" <span class="sync-status-icon sync-status-connected-with-changes bx bx-wifi"
data-toggle="tooltip" data-bs-toggle="tooltip"
data-placement="right" title="${t("sync_status.connected_with_changes")}">
title="<p>Connected to the sync server. <br>There are some outstanding changes yet to be synced.</p><p>Click to trigger sync.</p>">
<span class="bx bxs-star sync-status-sub-icon"></span> <span class="bx bxs-star sync-status-sub-icon"></span>
</span> </span>
<span class="sync-status-icon sync-status-connected-no-changes bx bx-wifi" <span class="sync-status-icon sync-status-connected-no-changes bx bx-wifi"
data-toggle="tooltip" data-bs-toggle="tooltip"
data-placement="right" title="${t("sync_status.connected_no_changes")}">
title="<p>Connected to the sync server.<br>All changes have been already synced.</p><p>Click to trigger sync.</p>">
</span> </span>
<span class="sync-status-icon sync-status-disconnected-with-changes bx bx-wifi-off" <span class="sync-status-icon sync-status-disconnected-with-changes bx bx-wifi-off"
data-toggle="tooltip" data-bs-toggle="tooltip"
data-placement="right" title="${t("sync_status.disconnected_with_changes")}">
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>">
<span class="bx bxs-star sync-status-sub-icon"></span> <span class="bx bxs-star sync-status-sub-icon"></span>
</span> </span>
<span class="sync-status-icon sync-status-disconnected-no-changes bx bx-wifi-off" <span class="sync-status-icon sync-status-disconnected-no-changes bx bx-wifi-off"
data-toggle="tooltip" data-bs-toggle="tooltip"
data-placement="right" title="${t("sync_status.disconnected_no_changes")}">
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>">
</span> </span>
<span class="sync-status-icon sync-status-in-progress bx bx-analyse bx-spin" <span class="sync-status-icon sync-status-in-progress bx bx-analyse bx-spin"
data-toggle="tooltip" data-bs-toggle="tooltip"
data-placement="right" title="${t("sync_status.in_progress")}">
title="Sync with the server is in progress.">
</span> </span>
</div> </div>
</div> </div>
@@ -86,10 +81,6 @@ export default class SyncStatusWidget extends BasicWidget {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$widget.hide(); this.$widget.hide();
this.$widget.find('[data-toggle="tooltip"]').tooltip({
html: true
});
this.$widget.find('.sync-status-icon:not(.sync-status-in-progress)') this.$widget.find('.sync-status-icon:not(.sync-status-in-progress)')
.on('click', () => syncService.syncNow()); .on('click', () => syncService.syncNow());
@@ -102,6 +93,11 @@ export default class SyncStatusWidget extends BasicWidget {
return; return;
} }
bootstrap.Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`), {
html: true,
placement: 'right',
});
this.$widget.show(); this.$widget.show();
this.$widget.find('.sync-status-icon').hide(); this.$widget.find('.sync-status-icon').hide();
this.$widget.find(`.sync-status-${className}`).show(); 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 options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js"; import OnClickButtonWidget from "./buttons/onclick_button.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
const TPL = `<div class="toc-widget"> const TPL = `<div class="toc-widget">
<style> <style>
@@ -40,7 +41,6 @@ const TPL = `<div class="toc-widget">
.toc li { .toc li {
cursor: pointer; cursor: pointer;
text-align: justify; text-align: justify;
text-justify: distribute;
word-wrap: break-word; word-wrap: break-word;
hyphens: auto; hyphens: auto;
} }
@@ -61,7 +61,7 @@ export default class TocWidget extends RightPanelWidget {
get widgetButtons() { get widgetButtons() {
return [ return [
new OnClickButtonWidget() new OnClickButtonWidget()
.icon("bx-slider") .icon("bx-cog")
.title("Options") .title("Options")
.titlePlacement("left") .titlePlacement("left")
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true})) .onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true}))
@@ -120,6 +120,52 @@ export default class TocWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility"); 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. * 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 * with an onclick event that will cause the document to scroll to
* the desired position. * the desired position.
*/ */
getToc(html) { async getToc(html) {
// Regular expression for headings <h1>...</h1> using non-greedy // Regular expression for headings <h1>...</h1> using non-greedy
// matching and backreferences // matching and backreferences
const headingTagsRegex = /<h(\d+)[^>]*>(.*?)<\/h\1>/gi; 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 // Create the list item and set up the click callback
// //
const headingText = $("<div>").html(m[2]).text(); const headingText = await this.replaceMathTextWithKatax(m[2])
const $li = $('<li>').text(headingText); const $li = $('<li>').html(headingText);
$li.on("click", () => this.jumpToHeading(headingIndex)); $li.on("click", () => this.jumpToHeading(headingIndex));
$ols[$ols.length - 1].append($li); $ols[$ols.length - 1].append($li);
headingCount = headingIndex; headingCount = headingIndex;
@@ -228,9 +274,17 @@ export default class TocWidget extends RightPanelWidget {
this.noteContext.viewScope.tocTemporarilyHidden = true; this.noteContext.viewScope.tocTemporarilyHidden = true;
await this.refresh(); await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility'); 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)) { if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh(); await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label' } 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") { if (e.key === "F1") {
e.stopPropagation(); 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(); super.doRender();

View File

@@ -37,7 +37,7 @@ const TPL = `
<div class="form-group row"> <div class="form-group row">
<div class="col-6"> <div class="col-6">
<label>${t("fonts.font_family")}</label> <label>${t("fonts.font_family")}</label>
<select class="main-font-family form-control"></select> <select class="main-font-family form-select"></select>
</div> </div>
<div class="col-6"> <div class="col-6">
@@ -45,19 +45,17 @@ const TPL = `
<div class="input-group"> <div class="input-group">
<input type="number" class="main-font-size form-control options-number-input" min="50" max="200" step="10"/> <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> <span class="input-group-text">%</span>
</div> </div>
</div> </div>
</div> </div>
</div>
<h5>${t("fonts.note_tree_font")}</h5> <h5>${t("fonts.note_tree_font")}</h5>
<div class="form-group row"> <div class="form-group row">
<div class="col-4"> <div class="col-4">
<label>${t("fonts.font_family")}</label> <label>${t("fonts.font_family")}</label>
<select class="tree-font-family form-control"></select> <select class="tree-font-family form-select"></select>
</div> </div>
<div class="col-6"> <div class="col-6">
@@ -65,19 +63,17 @@ const TPL = `
<div class="input-group"> <div class="input-group">
<input type="number" class="tree-font-size form-control options-number-input" min="50" max="200" step="10"/> <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> <span class="input-group-text">%</span>
</div> </div>
</div> </div>
</div> </div>
</div>
<h5>${t("fonts.note_detail_font")}</h5> <h5>${t("fonts.note_detail_font")}</h5>
<div class="form-group row"> <div class="form-group row">
<div class="col-4"> <div class="col-4">
<label>${t("fonts.font_family")}</label> <label>${t("fonts.font_family")}</label>
<select class="detail-font-family form-control"></select> <select class="detail-font-family form-select"></select>
</div> </div>
<div class="col-6"> <div class="col-6">
@@ -85,19 +81,17 @@ const TPL = `
<div class="input-group"> <div class="input-group">
<input type="number" class="detail-font-size form-control options-number-input" min="50" max="200" step="10"/> <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> <span class="input-group-text">%</span>
</div> </div>
</div> </div>
</div> </div>
</div>
<h5>${t("fonts.monospace_font")}</h5> <h5>${t("fonts.monospace_font")}</h5>
<div class="form-group row"> <div class="form-group row">
<div class="col-4"> <div class="col-4">
<label>${t("fonts.font_family")}</label> <label>${t("fonts.font_family")}</label>
<select class="monospace-font-family form-control"></select> <select class="monospace-font-family form-select"></select>
</div> </div>
<div class="col-6"> <div class="col-6">
@@ -105,12 +99,10 @@ const TPL = `
<div class="input-group"> <div class="input-group">
<input type="number" class="monospace-font-size form-control options-number-input" min="50" max="200" step="10"/> <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> <span class="input-group-text">%</span>
</div> </div>
</div> </div>
</div> </div>
</div>
<p>${t("fonts.note_tree_and_detail_font_sizing")}</p> <p>${t("fonts.note_tree_and_detail_font_sizing")}</p>

View File

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

View File

@@ -5,12 +5,12 @@ const TPL = `
<div class="options-section"> <div class="options-section">
<h4>${t('ribbon.widgets')}</h4> <h4>${t('ribbon.widgets')}</h4>
<label> <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')} ${t('ribbon.promoted_attributes_message')}
</label> </label>
<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')} ${t('ribbon.edited_notes_message')}
</label> </label>
</div>`; </div>`;

View File

@@ -10,12 +10,14 @@ const TPL = `
<div class="form-group row"> <div class="form-group row">
<div class="col-6"> <div class="col-6">
<label>${t("theme.theme_label")}</label> <label>${t("theme.theme_label")}</label>
<select class="theme-select form-control"></select> <select class="theme-select form-select"></select>
</div> </div>
<div class="col-6"> <div class="col-6">
<label>${t("theme.override_theme_fonts_label")}</label> <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> </div>
</div>`; </div>`;

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ const TPL = `
<div class="options-section"> <div class="options-section">
<h4>${t('vim_key_bindings.use_vim_keybindings_in_code_notes')}</h4> <h4>${t('vim_key_bindings.use_vim_keybindings_in_code_notes')}</h4>
<label> <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')} ${t('vim_key_bindings.enable_vim_keybindings')}
</label> </label>
</div>`; </div>`;

View File

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

View File

@@ -6,7 +6,7 @@ const TPL = `
<h4>${t("network_connections.network_connections_title")}</h4> <h4>${t("network_connections.network_connections_title")}</h4>
<label> <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")} ${t("network_connections.check_for_updates")}
</label> </label>
</div>`; </div>`;

View File

@@ -5,7 +5,7 @@ const TPL = `
<div class="options-section"> <div class="options-section">
<h4>${t("heading_style.title")}</h4> <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="plain">${t("heading_style.plain")}</option>
<option value="underline">${t("heading_style.underline")}</option> <option value="underline">${t("heading_style.underline")}</option>
<option value="markdown">${t("heading_style.markdown")}</option> <option value="markdown">${t("heading_style.markdown")}</option>

View File

@@ -8,11 +8,11 @@ const TPL = `
<p>${t("highlights_list.description")}</p> <p>${t("highlights_list.description")}</p>
</div> </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 form-check-input" 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 form-check-input" 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 form-check-input" 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 form-check-input" 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="bgColor"> ${t("highlights_list.bg_color")} &nbsp;</label>
</div> </div>
<br/><br/> <br/><br/>

View File

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

View File

@@ -36,22 +36,78 @@ a, a:visited, a:hover {
color: var(--link-color); color: var(--link-color);
} }
input, select, textarea { .note-list-widget a {
color: var(--input-text-color) !important; text-decoration: none;
background: var(--input-background-color) !important;
} }
#left-pane input, select, textarea { .note-list-widget a:hover {
color: var(--left-pane-text-color) !important; text-decoration: underline;
background: var(--left-pane-background-color) !important;
} }
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); 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); color: var(--main-text-color);
background: transparent;
} }
.ck .todo-list__checkmark { .ck .todo-list__checkmark {
@@ -184,10 +240,15 @@ div.ui-tooltip {
display: none; display: none;
} }
.dropdown-divider {
background-color: var(--menu-text-color);
}
.dropdown-menu { .dropdown-menu {
border: 1px solid var(--dropdown-border-color); border: 1px solid var(--dropdown-border-color);
color: var(--menu-text-color) !important; color: var(--menu-text-color) !important;
background-color: var(--menu-background-color) !important; background-color: var(--menu-background-color) !important;
font-size: inherit;
} }
.dropdown-menu .disabled { .dropdown-menu .disabled {
@@ -281,6 +342,10 @@ body .CodeMirror {
border-color: var(--button-border-color); border-color: var(--button-border-color);
} }
.btn:hover {
border-color: var(--hover-item-border-color);
}
button.btn, button.btn-sm { button.btn, button.btn-sm {
font-size: inherit; 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; font-size: var(--main-font-size) !important;
} }
.bs-tooltip-bottom .arrow::before { border-bottom-color: var(--main-border-color) !important; } .tooltip-trigger {
.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 {
position: absolute; position: absolute;
content: ''; top: 0;
border-color: transparent; left: 0;
border-style: solid; width: 100%;
height: 100%;
background: transparent;
pointer-events: none;
} }
.bs-tooltip-auto[x-placement^='left'] .arrow::after, .bs-tooltip-bottom .tooltip-arrow::before { border-bottom-color: var(--main-border-color) !important; }
.bs-tooltip-left .arrow::after { .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; left: -1px;
border-width: 0.4rem 0 0.4rem 0.4rem; 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-auto[data-popper-placement^='bottom'] .tooltip-arrow::before,
.bs-tooltip-bottom .arrow::after { .bs-tooltip-bottom .tooltip-arrow::before {
bottom: -1px; bottom: -1px;
border-width: 0 0.4rem 0.4rem; 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-auto[data-popper-placement^='right'] .tooltip-arrow::before,
.bs-tooltip-right .arrow::after { .bs-tooltip-right .tooltip-arrow::before {
right: -1px; right: -1px;
border-width: 0.4rem 0.4rem 0.4rem 0; 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-auto[data-popper-placement^='top'] .tooltip-arrow::before,
.bs-tooltip-top .arrow::after { .bs-tooltip-top .tooltip-arrow::before {
top: -1px; top: -1px;
border-width: 0.4rem 0.4rem 0; 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; display: none;
} }
@@ -651,6 +751,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
} }
.card { .card {
color: inherit !important;
background-color: inherit !important; background-color: inherit !important;
border-color: var(--main-border-color) !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 { .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 { #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; color: var(--main-text-color) !important;
z-index: 9999999999 !important; z-index: 9999999999 !important;
pointer-events: all; pointer-events: all;
flex-basis: 0; /* otherwise toast is always at least 350px tall (happens since 4.5.1, perhaps a bug) */
} }
.toast-header { .toast-header {
@@ -789,10 +889,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
cursor: row-resize; cursor: row-resize;
} }
.dropdown-menu {
font-size: inherit;
}
#context-menu-container { #context-menu-container {
max-height: 100vh; max-height: 100vh;
/* !!! Cannot set overflow: auto, submenus will break !!! */ /* !!! Cannot set overflow: auto, submenus will break !!! */
@@ -846,7 +942,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
background-color: inherit; background-color: inherit;
} }
[data-toggle="tooltip"]:not(.button-widget) span { [data-bs-toggle="tooltip"]:not(.button-widget) span {
padding-bottom: 0; padding-bottom: 0;
border-bottom: 1px dotted; border-bottom: 1px dotted;
} }
@@ -954,11 +1050,6 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
background-color: var(--launcher-pane-background-color); background-color: var(--launcher-pane-background-color);
} }
input {
background-color: transparent !important;
}
#right-pane { #right-pane {
overflow: auto; overflow: auto;
} }
@@ -1059,7 +1150,6 @@ button.close:hover {
} }
.options-number-input { .options-number-input {
text-align: right;
/* overriding settings from .form-control */ /* overriding settings from .form-control */
width: 10em !important; width: 10em !important;
flex-grow: 0 !important; flex-grow: 0 !important;
@@ -1069,12 +1159,6 @@ textarea {
cursor: auto; cursor: auto;
} }
.table thead th,
.table td, .table th {
/* Fix center vertical alignment of table cells */
vertical-align: middle;
}
.ck-powered-by-balloon { .ck-powered-by-balloon {
display: none !important; display: none !important;
} }
@@ -1092,7 +1176,6 @@ textarea {
.jump-to-note-dialog .modal-header { .jump-to-note-dialog .modal-header {
align-items: center; align-items: center;
padding-bottom: 1rem !important;
} }
.jump-to-note-dialog .modal-body { .jump-to-note-dialog .modal-body {

View File

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

View File

@@ -132,7 +132,7 @@
"pasteNotes": "将笔记粘贴为活动笔记的子笔记(根据是复制还是剪切到剪贴板来决定是移动还是克隆)", "pasteNotes": "将笔记粘贴为活动笔记的子笔记(根据是复制还是剪切到剪贴板来决定是移动还是克隆)",
"deleteNotes": "删除笔记/子树", "deleteNotes": "删除笔记/子树",
"editingNotes": "编辑笔记", "editingNotes": "编辑笔记",
"editNoteTitle": "", "editNoteTitle": "在树形笔记树中,焦点会从笔记树切换到笔记标题。按下 Enter 键会将焦点从笔记标题切换到文本编辑器。按下 <kbd>Ctrl+.</kbd> 会将焦点从编辑器切换回笔记树。",
"createEditLink": "创建/编辑外部链接", "createEditLink": "创建/编辑外部链接",
"createInternalLink": "创建内部链接", "createInternalLink": "创建内部链接",
"followLink": "跟随光标下的链接", "followLink": "跟随光标下的链接",
@@ -187,6 +187,12 @@
"search_placeholder": "按笔记名称搜索", "search_placeholder": "按笔记名称搜索",
"search_button": "全文搜索 <kbd>Ctrl+回车</kbd>" "search_button": "全文搜索 <kbd>Ctrl+回车</kbd>"
}, },
"markdown_import": {
"dialog_title": "Markdown 导入",
"modal_body_text": "由于浏览器沙箱的限制,无法直接从 JavaScript 读取剪贴板内容。请将要导入的 Markdown 文本粘贴到下面的文本框中,然后点击导入按钮",
"import_button": "导入 Ctrl+回车",
"import_success": "Markdown 内容已成功导入文档。"
},
"move_to": { "move_to": {
"dialog_title": "移动笔记到...", "dialog_title": "移动笔记到...",
"notes_to_move": "需要移动的笔记", "notes_to_move": "需要移动的笔记",
@@ -196,12 +202,6 @@
"error_no_path": "没有可以移动到的路径。", "error_no_path": "没有可以移动到的路径。",
"move_success_message": "所选笔记已移动到 " "move_success_message": "所选笔记已移动到 "
}, },
"markdown_import": {
"dialog_title": "Markdown 导入",
"modal_body_text": "由于浏览器沙箱的限制,无法直接从 JavaScript 读取剪贴板内容。请将要导入的 Markdown 文本粘贴到下面的文本框中,然后点击导入按钮",
"import_button": "导入 Ctrl+回车",
"import_success": "Markdown 内容已成功导入文档。"
},
"note_type_chooser": { "note_type_chooser": {
"modal_title": "选择笔记类型", "modal_title": "选择笔记类型",
"modal_body": "选择新笔记的类型或模板:", "modal_body": "选择新笔记的类型或模板:",
@@ -468,8 +468,8 @@
"to": "到", "to": "到",
"target_parent_note": "目标父笔记", "target_parent_note": "目标父笔记",
"on_all_matched_notes": "对于所有匹配的笔记", "on_all_matched_notes": "对于所有匹配的笔记",
"move_note_new_parent": "", "move_note_new_parent": "如果笔记只有一个父级(即旧分支被移除并创建新分支到新父级),则将笔记移动到新父级",
"clone_note_new_parent": "", "clone_note_new_parent": "如果笔记有多个克隆/分支(不清楚应该移除哪个分支),则将笔记克隆到新父级",
"nothing_will_happen": "如果笔记无法移动到目标笔记(即这会创建一个树循环),则不会发生任何事情" "nothing_will_happen": "如果笔记无法移动到目标笔记(即这会创建一个树循环),则不会发生任何事情"
}, },
"rename_note": { "rename_note": {
@@ -477,7 +477,7 @@
"rename_note_title_to": "重命名笔记标题为", "rename_note_title_to": "重命名笔记标题为",
"new_note_title": "新笔记标题", "new_note_title": "新笔记标题",
"click_help_icon": "点击右侧的帮助图标查看所有选项", "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_note": "<code>Note</code> - 所有匹配的笔记都被重命名为“Note”",
"example_new_title": "<code>NEW: ${note.title}</code> - 匹配的笔记标题以“NEW: ”为前缀", "example_new_title": "<code>NEW: ${note.title}</code> - 匹配的笔记标题以“NEW: ”为前缀",
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - 匹配的笔记以笔记的创建月份-日期为前缀", "example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - 匹配的笔记以笔记的创建月份-日期为前缀",
@@ -567,6 +567,12 @@
"edit_button": { "edit_button": {
"edit_this_note": "编辑此笔记" "edit_this_note": "编辑此笔记"
}, },
"show_toc_widget_button": {
"show_toc": "显示目录"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "显示高亮列表"
},
"global_menu": { "global_menu": {
"menu": "菜单", "menu": "菜单",
"options": "选项", "options": "选项",
@@ -593,9 +599,17 @@
"about": "关于 TriliumNext 笔记", "about": "关于 TriliumNext 笔记",
"logout": "登出" "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": { "left_pane_toggle": {
"hide_panel": "隐藏面板", "show_panel": "显示面板",
"show_panel": "显示面板" "hide_panel": "隐藏面板"
}, },
"move_pane_button": { "move_pane_button": {
"move_left": "向左移动", "move_left": "向左移动",
@@ -645,6 +659,9 @@
"hide_floating_buttons_button": { "hide_floating_buttons_button": {
"button_title": "隐藏按钮" "button_title": "隐藏按钮"
}, },
"svg_export_button": {
"button_title": "导出SVG格式图片"
},
"relation_map_buttons": { "relation_map_buttons": {
"create_child_note_title": "创建新的子笔记并添加到关系图", "create_child_note_title": "创建新的子笔记并添加到关系图",
"reset_pan_zoom_title": "重置平移和缩放到初始坐标和放大倍率", "reset_pan_zoom_title": "重置平移和缩放到初始坐标和放大倍率",
@@ -662,6 +679,9 @@
"error_cannot_get_branch_id": "无法获取 notePath '{{notePath}}' 的 branchId", "error_cannot_get_branch_id": "无法获取 notePath '{{notePath}}' 的 branchId",
"error_unrecognized_command": "无法识别的命令 {{command}}" "error_unrecognized_command": "无法识别的命令 {{command}}"
}, },
"note_icon": {
"change_note_icon": "更改笔记图标"
},
"basic_properties": { "basic_properties": {
"note_type": "笔记类型", "note_type": "笔记类型",
"editable": "可编辑", "editable": "可编辑",
@@ -1023,8 +1043,8 @@
"title": "下拉菜单可用的MIME文件类型" "title": "下拉菜单可用的MIME文件类型"
}, },
"vim_key_bindings": { "vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "", "use_vim_keybindings_in_code_notes": "Vim 快捷键",
"enable_vim_keybindings": "" "enable_vim_keybindings": "在代码笔记中启用 Vim 快捷键(不包含 ex 模式)"
}, },
"wrap_lines": { "wrap_lines": {
"wrap_lines_in_code_notes": "代码笔记自动换行", "wrap_lines_in_code_notes": "代码笔记自动换行",
@@ -1090,11 +1110,11 @@
"highlights_list": { "highlights_list": {
"title": "高亮列表", "title": "高亮列表",
"description": "您可以自定义右侧面板中显示的高亮列表:", "description": "您可以自定义右侧面板中显示的高亮列表:",
"bold": "", "bold": "粗体",
"italic": "", "italic": "斜体",
"underline": "", "underline": "下划线",
"color": "", "color": "字体颜色",
"bg_color": "", "bg_color": "背景颜色",
"visibility_title": "高亮列表可见性", "visibility_title": "高亮列表可见性",
"visibility_description": "您可以通过添加 #hideHighlightWidget 标签来隐藏每个笔记的高亮小部件。", "visibility_description": "您可以通过添加 #hideHighlightWidget 标签来隐藏每个笔记的高亮小部件。",
"shortcut_info": "您可以在选项 -> 快捷键中为快速切换右侧面板(包括高亮列表)配置键盘快捷键(名称为 'toggleRightPane')。" "shortcut_info": "您可以在选项 -> 快捷键中为快速切换右侧面板(包括高亮列表)配置键盘快捷键(名称为 'toggleRightPane')。"
@@ -1112,7 +1132,10 @@
}, },
"i18n": { "i18n": {
"title": "本地化", "title": "本地化",
"language": "语言" "language": "语言",
"first-day-of-the-week": "一周的第一天",
"sunday": "星期日",
"monday": "星期一"
}, },
"backup": { "backup": {
"automatic_backup": "自动备份", "automatic_backup": "自动备份",
@@ -1238,5 +1261,90 @@
"note_is_editable": "笔记如果不太长则可编辑。", "note_is_editable": "笔记如果不太长则可编辑。",
"note_is_read_only": "笔记为只读,但可以通过点击按钮进行编辑。", "note_is_read_only": "笔记为只读,但可以通过点击按钮进行编辑。",
"note_is_always_editable": "无论笔记长度如何,始终可编辑。" "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_button": {
"edit_this_note": "Edit this note" "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": { "global_menu": {
"menu": "Menu", "menu": "Menu",
"options": "Options", "options": "Options",
@@ -593,6 +599,14 @@
"about": "About TriliumNext Notes", "about": "About TriliumNext Notes",
"logout": "Logout" "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": { "left_pane_toggle": {
"show_panel": "Show panel", "show_panel": "Show panel",
"hide_panel": "Hide panel" "hide_panel": "Hide panel"
@@ -665,6 +679,9 @@
"error_cannot_get_branch_id": "Cannot get branchId for notePath '{{notePath}}'", "error_cannot_get_branch_id": "Cannot get branchId for notePath '{{notePath}}'",
"error_unrecognized_command": "Unrecognized command {{command}}" "error_unrecognized_command": "Unrecognized command {{command}}"
}, },
"note_icon": {
"change_note_icon": "Change note icon"
},
"basic_properties": { "basic_properties": {
"note_type": "Note type", "note_type": "Note type",
"editable": "Editable", "editable": "Editable",
@@ -1325,5 +1342,16 @@
"toggle-on-hint": "Make the note a template", "toggle-on-hint": "Make the note a template",
"toggle-off-hint": "Remove the note as 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', id: '_hidden',
title: 'Hidden Notes', title: 'Hidden Notes',
type: 'doc', 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 // 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 // over tree when it's in the middle
notePosition: 999_999_999, 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: [ { id: '_lbJumpTo', title: 'Jump to Note', type: 'launcher', command: 'jumpToNote', icon: 'bx bx-send', attributes: [
{ type: 'label', name: 'desktopOnly' } { 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: '_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: [ { id: '_lbRecentChanges', title: 'Recent Changes', type: 'launcher', command: 'showRecentChanges', icon: 'bx bx-history', attributes: [
{ type: 'label', name: 'desktopOnly' } { type: 'label', name: 'desktopOnly' }
@@ -241,6 +241,7 @@ const HIDDEN_SUBTREE_DEFINITION: Item = {
id: '_options', id: '_options',
title: 'Options', title: 'Options',
type: 'book', type: 'book',
icon: 'bx-cog',
children: [ children: [
{ id: '_optionsAppearance', title: 'Appearance', type: 'contentWidget', icon: 'bx-layout' }, { id: '_optionsAppearance', title: 'Appearance', type: 'contentWidget', icon: 'bx-layout' },
{ id: '_optionsShortcuts', title: 'Shortcuts', type: 'contentWidget', icon: 'bxs-keyboard' }, { 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_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_GID}" ] && groupmod -og ${USER_GID} node || echo "No USER_GID specified, leaving 1000"
chown -R node:node /home/node chown -R node:node /home/node
exec su -c "node ./src/www" node exec su -c "node ./src/www" node