Compare commits
208 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4faf27364f | |||
| 52a6d0b48a | |||
| cddc9a7b6a | |||
| 75d019863f | |||
| 849a6a3aef | |||
| e7378306a2 | |||
| 1277dfc5d5 | |||
| ae680847dc | |||
| a5fd57308a | |||
| fa769df7b0 | |||
| 8136a2972e | |||
| 8c8c3974f3 | |||
| e81bfa3693 | |||
| a857f4816f | |||
| ade34f9745 | |||
| fd66cb930d | |||
| 182d9afac1 | |||
| 4e6ef0be95 | |||
| 88961ea93f | |||
| a3f2946a17 | |||
| 8ae5f9ea9b | |||
| 9ec2508f09 | |||
| bae63b08a2 | |||
| 729a188528 | |||
| 2c5a5acffa | |||
| e45c5f429d | |||
| 5c44ac5ad8 | |||
| b44c2f5ebf | |||
| 6e64cab2d4 | |||
| 9c8cf0bc09 | |||
| bafc556b00 | |||
| 0ec3232c81 | |||
| 469c1ceb07 | |||
| 5e80f120c9 | |||
| 8faa26b663 | |||
| 201e2fcfdb | |||
| 5678487a16 | |||
| 6646e8c311 | |||
| 96fc4d3280 | |||
| 0a69189b9b | |||
| cb3ea3fb75 | |||
| 787a2d1957 | |||
| 89e1275dda | |||
| ddc927d617 | |||
| d46963e496 | |||
| c37a51c6d0 | |||
| 91095e8d4e | |||
| d4fa7e3fd2 | |||
| 9204f0735c | |||
| cfc32a14e0 | |||
| 270aa52591 | |||
| 99a5913391 | |||
| c3d6165aff | |||
| 9220616bb5 | |||
| eb9b4aee21 | |||
| d9a7671830 | |||
| 353c410f0e | |||
| 7ca4cddc58 | |||
| 12543f762b | |||
| ec7fabcb58 | |||
| 5d08f2bc5a | |||
| a557b82c83 | |||
| 62a884cb5a | |||
| 88875a3375 | |||
| cd547ebdaf | |||
| e511d6aecf | |||
| 9b6148dec3 | |||
| 29a1aad45a | |||
| a9fce727d4 | |||
| 9e85d2cdbd | |||
| c4f430276a | |||
| fdf57b6cfa | |||
| 0a9e76abb7 | |||
| 3e085e5cae | |||
| 73cd54e75c | |||
| 28b8e59b4d | |||
| 51b6725b91 | |||
| 93ca30fda0 | |||
| 8c222368de | |||
| a3aa6b0628 | |||
| d0061794dd | |||
| 17c8708b54 | |||
| da0ddbb80d | |||
| 138b4b1a95 | |||
| 1299072ea9 | |||
| bb5893f989 | |||
| 7b764fd7d8 | |||
| ac2fc49a48 | |||
| c0abf6e2c5 | |||
| 85ff0bac55 | |||
| 3671b83a9c | |||
| 6970bf4fc1 | |||
| e5cce1b1bc | |||
| faa8eca810 | |||
| 84bf0cbae5 | |||
| a77264208e | |||
| ee06db8c8f | |||
| e330b43750 | |||
| f5b21498bf | |||
| 83388ecf1c | |||
| 87de631af4 | |||
| e4271d3945 | |||
| 65b8a2f97c | |||
| 0656154c35 | |||
| b3f682144b | |||
| d3c3e157c7 | |||
| 2534402157 | |||
| ec80ba5caf | |||
| 26621b6336 | |||
| d961e3cdf1 | |||
| 7ef399912b | |||
| 9fc789676f | |||
| f01dc3d102 | |||
| 501bf624cc | |||
| f8605688ab | |||
| 590a9bef2d | |||
| 419756d19e | |||
| 93ce81b355 | |||
| 8bfa4461b0 | |||
| 648feb82f8 | |||
| 74645e12fc | |||
| 71de1a0b55 | |||
| 694f3cb174 | |||
| a2bc5073d0 | |||
| 3134ef7c03 | |||
| 8e665e27e7 | |||
| e6fbf62cf9 | |||
| ce8b5e33da | |||
| b63b603d64 | |||
| 31aa6feb0c | |||
| 98c9e25124 | |||
| bb97e1a661 | |||
| a4341a5cac | |||
| fe844d4f8c | |||
| 447cf60afb | |||
| 7a11f9aaff | |||
| 527591f651 | |||
| fca6f99870 | |||
| 8c97f0bec7 | |||
| 58ee801e57 | |||
| c1ce578018 | |||
| 464d8417f5 | |||
| 44bd008829 | |||
| f3b7261748 | |||
| 5d579fee68 | |||
| d7ab99013c | |||
| a3a5339048 | |||
| d95a23de28 | |||
| 744d953822 | |||
| f8e5717b80 | |||
| 3ad2d1a309 | |||
| 7186222393 | |||
| d66c07717c | |||
| 132dd7514a | |||
| aca9d0f1ef | |||
| eabceae6f1 | |||
| 3a5b05e5c6 | |||
| 11e5f6a1c1 | |||
| bec9acdc3f | |||
| 37079b7388 | |||
| 22b7bf826b | |||
| 7cc71a4fdb | |||
| fb28b9d36d | |||
| 720087f082 | |||
| 3bdb87d3aa | |||
| e4066ba164 | |||
| a0fdaabb1f | |||
| 38ac4318b9 | |||
| e382a32ebd | |||
| 8074245758 | |||
| f26a04216a | |||
| e565d29490 | |||
| 947c50353d | |||
| 83851a61d5 | |||
| 893a1e3723 | |||
| 6bf18b6837 | |||
| 94857d8802 | |||
| 58d1e77ac1 | |||
| 1fbc65b007 | |||
| c25c8c753d | |||
| 317b7b4c59 | |||
| 68042994e0 | |||
| d00c028a0c | |||
| a9e6c887f2 | |||
| 69f9457bda | |||
| 0fed54f1c3 | |||
| b45fde2e5f | |||
| c1010a79f9 | |||
| c33154d128 | |||
| c0d613c46d | |||
| 4f7bbee769 | |||
| 7a567583f2 | |||
| 0ac1f071cd | |||
| 114e7809fe | |||
| 4106cc61b5 | |||
| 7c0d6930fa | |||
| 89e073bf28 | |||
| 490ae1c9fd | |||
| 2a27383682 | |||
| 00f8a43b0f | |||
| 759ac4a4c8 | |||
| e66b865cd6 | |||
| 78bfc3341b | |||
| 898afb7ed7 | |||
| bc4444d132 | |||
| 56259616cb | |||
| a50e5cc733 | |||
| b790dabc8c |
@@ -1,153 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "feature/update**"
|
||||
- "feature/server_esm**"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "bin/**"
|
||||
tags:
|
||||
- "v*"
|
||||
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
|
||||
@@ -9,22 +9,35 @@ on:
|
||||
- "bin/**"
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GHCR_REGISTRY: ghcr.io
|
||||
DOCKERHUB_REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
TEST_TAG: triliumnext/notes:test
|
||||
PLATFORMS: linux/arm64,linux/arm/v7
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/notes
|
||||
TEST_TAG: ${{ github.repository_owner }}/notes:test
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
test_docker:
|
||||
name: Check Docker build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: Dockerfile.alpine
|
||||
- dockerfile: Dockerfile
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -47,14 +60,17 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
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: Validate container run output
|
||||
run: |
|
||||
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --name trilium_local ${{ env.TEST_TAG }})
|
||||
echo "Container ID: $CONTAINER_ID"
|
||||
|
||||
- name: Wait for the healthchecks to pass
|
||||
uses: stringbean/docker-healthcheck-action@v1
|
||||
with:
|
||||
@@ -63,7 +79,13 @@ jobs:
|
||||
require-status: running
|
||||
require-healthy: true
|
||||
|
||||
build_docker:
|
||||
# Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
|
||||
- name: Print entire log
|
||||
if: always()
|
||||
run: |
|
||||
journalctl -u docker CONTAINER_NAME=trilium_local --no-pager
|
||||
|
||||
build:
|
||||
name: Build Docker images
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
@@ -73,26 +95,48 @@ jobs:
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Extract metadata (tags, labels) for GHCR image
|
||||
id: ghcr-meta
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: Dockerfile.alpine
|
||||
platform: linux/amd64
|
||||
- dockerfile: Dockerfile
|
||||
platform: linux/arm64
|
||||
- dockerfile: Dockerfile
|
||||
platform: linux/arm/v7
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
- name: Extract metadata (tags, labels) for DockerHub image
|
||||
id: dh-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -103,50 +147,103 @@ jobs:
|
||||
run: npx tsc
|
||||
- name: Create server-package.json
|
||||
run: cat package.json | grep -v electron > server-package.json
|
||||
- name: Log in to the GHCR container registry
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push container image to GHCR
|
||||
uses: docker/build-push-action@v6
|
||||
id: ghcr-push
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.ghcr-meta.outputs.tags }}
|
||||
labels: ${{ steps.ghcr-meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Generate and push artifact attestation to GHCR
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
- name: Log in to the DockerHub container registry
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push image to DockerHub
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
id: dh-push
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.dh-meta.outputs.tags }}
|
||||
labels: ${{ steps.dh-meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Generate and push artifact attestation to DockerHub
|
||||
uses: actions/attest-build-provenance@v1
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.dh-push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Merge manifest lists
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
# Extract the branch or tag name from the ref
|
||||
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
|
||||
|
||||
# Create and push the manifest list with both the branch/tag name and the commit SHA
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
|
||||
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
|
||||
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
name: Nightly Release
|
||||
on:
|
||||
# This can be used to automatically publish nightlies at UTC nighttime
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
# This can be used to allow manually triggering nightlies from the web interface
|
||||
workflow_dispatch:
|
||||
env:
|
||||
GITHUB_UPLOAD_URL: https://uploads.github.com/repos/TriliumNext/Notes/releases/179589950/assets{?name,label}
|
||||
GITHUB_RELEASE_ID: 179589950
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
nightly-electron:
|
||||
name: Deploy nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
os:
|
||||
- name: macos
|
||||
image: macos-latest
|
||||
extension: dmg
|
||||
- name: linux
|
||||
image: ubuntu-latest
|
||||
extension: deb
|
||||
- name: windows
|
||||
image: windows-latest
|
||||
extension: exe
|
||||
runs-on: ${{ matrix.os.image }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Set up Python for appdmg to be installed
|
||||
if: ${{ matrix.os.name == 'macos' }}
|
||||
run: brew install python-setuptools
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Update build info
|
||||
run: npm run update-build-info
|
||||
- name: Run electron-forge
|
||||
run: npm run make-electron -- --arch=${{ matrix.arch }}
|
||||
- name: Prepare artifacts (Unix)
|
||||
if: runner.os != 'windows'
|
||||
run: |
|
||||
mkdir -p upload
|
||||
file=$(find out/make -name '*.zip' -print -quit)
|
||||
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
|
||||
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
|
||||
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
|
||||
- name: Prepare artifacts (Windows)
|
||||
if: runner.os == 'windows'
|
||||
run: |
|
||||
mkdir upload
|
||||
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
|
||||
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
|
||||
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
|
||||
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
|
||||
- name: Publish artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
||||
path: upload/*.zip
|
||||
overwrite: true
|
||||
- name: Publish installer artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
||||
path: upload/*.${{ matrix.os.extension }}
|
||||
overwrite: true
|
||||
|
||||
- name: Deploy release
|
||||
uses: WebFreak001/deploy-nightly@v3.1.0
|
||||
with:
|
||||
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
|
||||
release_id: ${{ env.GITHUB_RELEASE_ID }}
|
||||
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip # path to archive to upload
|
||||
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip # required by GitHub API
|
||||
- name: Deploy installer release
|
||||
uses: WebFreak001/deploy-nightly@v3.1.0
|
||||
with:
|
||||
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
|
||||
release_id: ${{ env.GITHUB_RELEASE_ID }}
|
||||
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }} # path to archive to upload
|
||||
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.${{ matrix.os.extension }} # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip # required by GitHub API
|
||||
nightly-server:
|
||||
name: Deploy server nightly
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run Linux server build (x86_64)
|
||||
run: |
|
||||
npm run update-build-info
|
||||
./bin/build-server.sh
|
||||
- name: Prepare artifacts
|
||||
if: runner.os != 'windows'
|
||||
run: |
|
||||
mkdir -p upload
|
||||
file=$(find dist -name '*.tar.xz' -print -quit)
|
||||
cp "$file" "upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: TriliumNextNotes linux server x64
|
||||
path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz
|
||||
overwrite: true
|
||||
|
||||
- name: Deploy release
|
||||
uses: WebFreak001/deploy-nightly@v3.1.0
|
||||
with:
|
||||
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
|
||||
release_id: ${{ env.GITHUB_RELEASE_ID }}
|
||||
asset_path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz # path to archive to upload
|
||||
asset_name: TriliumNextNotes-linux-x64-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip # required by GitHub API
|
||||
+2
@@ -3,6 +3,7 @@
|
||||
languageIds:
|
||||
- javascript
|
||||
- typescript
|
||||
- html
|
||||
|
||||
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
@@ -25,6 +26,7 @@ scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
|
||||
refactorTemplates:
|
||||
- t("$1")
|
||||
- ${t("$1")}
|
||||
- <%= t("$1") %>
|
||||
|
||||
|
||||
# If set to true, only enables this custom framework (will disable all built-in frameworks)
|
||||
|
||||
Vendored
+5
-1
@@ -6,7 +6,8 @@
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.localesPaths": [
|
||||
"./src/public/translations"
|
||||
"./src/public/translations",
|
||||
"./translations"
|
||||
],
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
@@ -17,4 +18,7 @@
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
},
|
||||
"github-actions.workflows.pinned.workflows": [
|
||||
".github/workflows/nightly.yml"
|
||||
],
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://triliumnext.github.io/Docs/Wiki/screenshot-tour)以快速了解:
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes es una aplicación de toma de notas jerárquicas multi-plataforma y de código libre con un enfoque en la construcción de grandes bases de conocimiento personal.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes è un'applicazione per appunti ad organizzazione gerarchica, studiata per la costruzione di archivi di conoscenza personali di grandi dimensioni.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
Trilium Notes は、大規模な個人知識ベースの構築に焦点を当てた、階層型ノートアプリケーションです。概要は[スクリーンショット](https://triliumnext.github.io/Docs/Wiki/screenshot-tour)をご覧ください:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
TriliumNext Notes is an open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
|
||||
|
||||
@@ -82,13 +82,19 @@ You can also read [Patterns of personal knowledge base](https://triliumnext.gith
|
||||
|
||||
## 💻 Contribute
|
||||
|
||||
Clone locally and run
|
||||
### Code
|
||||
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Head on over to our [Docs repo](https://github.com/TriliumNext/Docs)
|
||||
|
||||
## 👏 Shoutouts
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# TriliumNext Notes
|
||||
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
|
||||
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
|
||||
|
||||
Trilium Notes – это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://triliumnext.github.io/Docs/Wiki/screenshot-tour):
|
||||
|
||||
|
||||
+3
-2
@@ -22,13 +22,14 @@ rm -r $PKG_DIR/node/lib/node_modules/npm
|
||||
rm -r $PKG_DIR/node/include/node
|
||||
|
||||
rm -r $PKG_DIR/node_modules/electron*
|
||||
rm -r $PKG_DIR/electron.js
|
||||
rm -r $PKG_DIR/electron*.js
|
||||
|
||||
printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh
|
||||
printf "#!/bin/sh\n./node/bin/node src/main" > $PKG_DIR/trilium.sh
|
||||
chmod 755 $PKG_DIR/trilium.sh
|
||||
|
||||
cp bin/tpl/anonymize-database.sql $PKG_DIR/
|
||||
|
||||
cp -r translations $PKG_DIR/
|
||||
cp -r dump-db $PKG_DIR/
|
||||
rm -rf $PKG_DIR/dump-db/node_modules
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ const copy = async () => {
|
||||
await fs.copy(file, path.join(DEST_DIR, file));
|
||||
}
|
||||
|
||||
const dirsToCopy = ["images", "libraries", "db"];
|
||||
const dirsToCopy = ["images", "libraries", "translations", "db"];
|
||||
for (const dir of dirsToCopy) {
|
||||
log(`Copying ${dir}`);
|
||||
await fs.copy(dir, path.join(DEST_DIR, dir));
|
||||
|
||||
+2
-2
@@ -37,11 +37,11 @@ for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample
|
||||
done
|
||||
|
||||
# Patch package.json main
|
||||
sed -i 's/.\/dist\/electron.js/electron.js/g' "$DIR/package.json"
|
||||
sed -i 's/.\/dist\/electron-main.js/electron-main.js/g' "$DIR/package.json"
|
||||
|
||||
script_dir=$(realpath $(dirname $0))
|
||||
cp -R "$script_dir/../build/src" "$DIR"
|
||||
cp "$script_dir/../build/electron.js" "$DIR"
|
||||
cp "$script_dir/../build/electron-main.js" "$DIR"
|
||||
|
||||
# run in subshell (so we return to original dir)
|
||||
(cd $DIR && npm install --omit=dev)
|
||||
|
||||
@@ -42,9 +42,9 @@ const NOTE_TYPE_ICONS = {
|
||||
"code": "bx bx-code",
|
||||
"render": "bx bx-extension",
|
||||
"search": "bx bx-file-find",
|
||||
"relationMap": "bx bx-map-alt",
|
||||
"relationMap": "bx bxs-network-chart",
|
||||
"book": "bx bx-book",
|
||||
"noteMap": "bx bx-map-alt",
|
||||
"noteMap": "bx bxs-network-chart",
|
||||
"mermaid": "bx bx-selection",
|
||||
"canvas": "bx bx-pen",
|
||||
"webView": "bx bx-globe-alt",
|
||||
@@ -570,7 +570,7 @@ class FNote {
|
||||
return workspaceIconClass;
|
||||
}
|
||||
else if (this.noteId === 'root') {
|
||||
return "bx bx-chevrons-right";
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === '_share') {
|
||||
return "bx bx-share-alt";
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { initializeTranslations } from "./src/services/i18n.js";
|
||||
|
||||
await initializeTranslations();
|
||||
await import("./electron.js")
|
||||
+1
-1
@@ -70,4 +70,4 @@ electron.app.on("will-quit", () => {
|
||||
// this is to disable electron warning spam in the dev console (local development only)
|
||||
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
|
||||
|
||||
await import('./src/www.js');
|
||||
await import('./src/main.js');
|
||||
|
||||
+11
-2
@@ -10,7 +10,13 @@ module.exports = {
|
||||
overwrite: true,
|
||||
asar: true,
|
||||
icon: "./images/app-icons/icon",
|
||||
extraResource: getExtraResourcesForPlatform(),
|
||||
extraResource: [
|
||||
// Moved to root
|
||||
...getExtraResourcesForPlatform(),
|
||||
|
||||
// Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS)
|
||||
"translations/"
|
||||
],
|
||||
afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => {
|
||||
const extraResources = getExtraResourcesForPlatform();
|
||||
for (const resource of extraResources) {
|
||||
@@ -75,7 +81,10 @@ module.exports = {
|
||||
|
||||
|
||||
function getExtraResourcesForPlatform() {
|
||||
let resources = ['dump-db/', './bin/tpl/anonymize-database.sql']
|
||||
let resources = [
|
||||
'dump-db/',
|
||||
'./bin/tpl/anonymize-database.sql'
|
||||
];
|
||||
const scripts = ['trilium-portable', 'trilium-safe-mode', 'trilium-no-cert-check']
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -3,7 +3,10 @@
|
||||
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
|
||||
"verbose": false,
|
||||
"exec": "tsx",
|
||||
"watch": ["src/"],
|
||||
"watch": [
|
||||
"src/",
|
||||
"translations/"
|
||||
],
|
||||
"signal": "SIGTERM",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
|
||||
Generated
+1197
-1162
File diff suppressed because it is too large
Load Diff
+105
-104
@@ -2,9 +2,9 @@
|
||||
"name": "trilium",
|
||||
"productName": "TriliumNext Notes",
|
||||
"description": "Build your personal knowledge base with TriliumNext Notes",
|
||||
"version": "0.90.5-beta",
|
||||
"version": "0.90.7-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "./dist/electron.js",
|
||||
"main": "./dist/electron-main.js",
|
||||
"author": {
|
||||
"name": "TriliumNext Notes Team",
|
||||
"email": "contact@eliandoran.me",
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"copyright": "",
|
||||
"bin": {
|
||||
"trilium": "src/www.js"
|
||||
"trilium": "src/main.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -20,12 +20,12 @@
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/www.ts",
|
||||
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
|
||||
"start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
|
||||
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
|
||||
"start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/main.ts",
|
||||
"qstart-server": "npm run switch-server && npm run start-server",
|
||||
"start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron.js --inspect=5858 .",
|
||||
"start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.js --inspect=5858 .",
|
||||
"start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
|
||||
"qstart-electron": "npm run switch-electron && npm run start-electron",
|
||||
"switch-server": "rimraf ./node_modules/better-sqlite3 && npm install",
|
||||
@@ -34,7 +34,7 @@
|
||||
"build-frontend-docs": "rimraf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
|
||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
||||
"webpack": "cross-env node --import ./loader-register.js node_modules/webpack/bin/webpack.js -c webpack.config.ts",
|
||||
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/.bin/jasmine",
|
||||
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/jasmine/bin/jasmine.js",
|
||||
"test-es6": "tsx -r esm spec-es6/attribute_parser.spec.ts",
|
||||
"test": "npm run test-jasmine && npm run test-es6",
|
||||
"start-electron-forge": "npm run prepare-dist && electron-forge start",
|
||||
@@ -43,42 +43,41 @@
|
||||
"prepare-dist": "rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
|
||||
"update-build-info": "tsx bin/update-build-info.ts",
|
||||
"errors": "tsc --watch --noEmit",
|
||||
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
|
||||
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
|
||||
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
|
||||
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"generate-document": "cross-env nodemon src/tools/generate_document.ts 1000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^7.1.0",
|
||||
"@braintree/sanitize-url": "7.1.0",
|
||||
"@electron/remote": "2.1.2",
|
||||
"@excalidraw/excalidraw": "^0.17.6",
|
||||
"@types/electron-squirrel-startup": "^1.0.2",
|
||||
"archiver": "^7.0.1",
|
||||
"async-mutex": "^0.5.0",
|
||||
"autocomplete.js": "^0.38.1",
|
||||
"axios": "^1.7.2",
|
||||
"better-sqlite3": "^11.1.2",
|
||||
"bootstrap": "^4.6.2",
|
||||
"@excalidraw/excalidraw": "0.17.6",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"axios": "1.7.7",
|
||||
"better-sqlite3": "11.3.0",
|
||||
"bootstrap": "5.3.3",
|
||||
"boxicons": "2.1.4",
|
||||
"chokidar": "3.6.0",
|
||||
"cls-hooked": "4.2.2",
|
||||
"codemirror": "^5.65.17",
|
||||
"codemirror": "5.65.18",
|
||||
"compression": "1.7.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
"csurf": "1.11.0",
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "^2.1.0",
|
||||
"ejs": "^3.1.10",
|
||||
"electron-debug": "3.2.0",
|
||||
"electron-dl": "3.5.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"debounce": "2.1.1",
|
||||
"ejs": "3.1.10",
|
||||
"electron-debug": "4.0.1",
|
||||
"electron-dl": "4.0.0",
|
||||
"electron-squirrel-startup": "1.0.1",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"eslint": "^9.9.0",
|
||||
"express": "^4.19.2",
|
||||
"eslint": "9.10.0",
|
||||
"express": "4.21.0",
|
||||
"express-partial-content": "1.0.2",
|
||||
"express-rate-limit": "^7.3.1",
|
||||
"express-rate-limit": "7.4.0",
|
||||
"express-session": "1.18.0",
|
||||
"force-graph": "1.43.5",
|
||||
"fs-extra": "11.2.0",
|
||||
@@ -86,112 +85,114 @@
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "^7.0.5",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"https-proxy-agent": "7.0.5",
|
||||
"i18next": "23.15.2",
|
||||
"i18next-fs-backend": "2.3.2",
|
||||
"i18next-http-backend": "2.6.2",
|
||||
"image-type": "4.1.0",
|
||||
"ini": "^4.1.3",
|
||||
"ini": "5.0.0",
|
||||
"is-animated": "2.0.2",
|
||||
"is-svg": "4.3.2",
|
||||
"jimp": "0.22.12",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
"jquery.fancytree": "^2.38.3",
|
||||
"jsdom": "^24.1.0",
|
||||
"jsplumb": "^2.15.6",
|
||||
"katex": "^0.16.11",
|
||||
"knockout": "^3.5.1",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "^13.0.2",
|
||||
"mermaid": "^10.9.1",
|
||||
"jquery.fancytree": "2.38.3",
|
||||
"jsdom": "25.0.0",
|
||||
"jsplumb": "2.15.6",
|
||||
"katex": "0.16.11",
|
||||
"knockout": "3.5.1",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "14.1.2",
|
||||
"mermaid": "11.3.0",
|
||||
"mime-types": "2.1.35",
|
||||
"mind-elixir": "^4.0.5",
|
||||
"mind-elixir": "4.1.5",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "^3.65.0",
|
||||
"node-abi": "3.67.0",
|
||||
"normalize-strings": "1.1.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"print-this": "2.0.0",
|
||||
"rand-token": "1.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"request": "2.88.2",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "^2.13.0",
|
||||
"sax": "^1.4.1",
|
||||
"semver": "^7.6.3",
|
||||
"sanitize-html": "2.13.0",
|
||||
"sax": "1.4.1",
|
||||
"semver": "7.6.3",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.5.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"source-map-support": "0.5.21",
|
||||
"split.js": "1.6.5",
|
||||
"stream-throttle": "0.1.3",
|
||||
"striptags": "3.2.0",
|
||||
"tmp": "0.2.3",
|
||||
"tree-kill": "1.2.2",
|
||||
"turndown": "^7.2.0",
|
||||
"turndown": "7.2.0",
|
||||
"unescape": "1.0.1",
|
||||
"vanilla-js-wheel-zoom": "^9.0.2",
|
||||
"ws": "^8.18.0",
|
||||
"vanilla-js-wheel-zoom": "9.0.2",
|
||||
"ws": "8.18.0",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "^3.1.3"
|
||||
"yauzl": "3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.4.0",
|
||||
"@electron-forge/maker-deb": "^7.4.0",
|
||||
"@electron-forge/maker-dmg": "^7.4.0",
|
||||
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||
"@electron-forge/maker-zip": "^7.4.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||
"@playwright/test": "^1.46.0",
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/better-sqlite3": "^7.6.9",
|
||||
"@types/cls-hooked": "^4.3.8",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/csurf": "^1.11.5",
|
||||
"@types/debounce": "^1.2.4",
|
||||
"@types/ejs": "^3.1.5",
|
||||
"@types/escape-html": "^1.0.4",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/html": "^1.0.4",
|
||||
"@types/ini": "^4.1.0",
|
||||
"@types/jasmine": "^5.1.4",
|
||||
"@types/jsdom": "^21.1.6",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^22.5.2",
|
||||
"@types/safe-compare": "^1.1.2",
|
||||
"@types/sanitize-html": "^2.13.0",
|
||||
"@types/sax": "^1.2.7",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/serve-favicon": "^2.5.7",
|
||||
"@types/session-file-store": "^1.2.5",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/stream-throttle": "^0.1.4",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@electron-forge/cli": "7.5.0",
|
||||
"@electron-forge/maker-deb": "7.5.0",
|
||||
"@electron-forge/maker-dmg": "7.5.0",
|
||||
"@electron-forge/maker-squirrel": "7.5.0",
|
||||
"@electron-forge/maker-zip": "7.5.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.5.0",
|
||||
"@playwright/test": "1.47.1",
|
||||
"@types/archiver": "6.0.2",
|
||||
"@types/better-sqlite3": "7.6.11",
|
||||
"@types/cls-hooked": "4.3.8",
|
||||
"@types/compression": "1.7.5",
|
||||
"@types/cookie-parser": "1.4.7",
|
||||
"@types/csurf": "1.11.5",
|
||||
"@types/debounce": "1.2.4",
|
||||
"@types/ejs": "3.1.5",
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@types/escape-html": "1.0.4",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/express-session": "1.18.0",
|
||||
"@types/html": "1.0.4",
|
||||
"@types/ini": "4.1.1",
|
||||
"@types/jasmine": "5.1.4",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/multer": "1.4.12",
|
||||
"@types/node": "22.5.4",
|
||||
"@types/safe-compare": "1.1.2",
|
||||
"@types/sanitize-html": "2.13.0",
|
||||
"@types/sax": "1.2.7",
|
||||
"@types/semver": "7.5.8",
|
||||
"@types/serve-favicon": "2.5.7",
|
||||
"@types/session-file-store": "1.2.5",
|
||||
"@types/source-map-support": "0.5.10",
|
||||
"@types/stream-throttle": "0.1.4",
|
||||
"@types/tmp": "0.2.6",
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/ws": "8.5.12",
|
||||
"@types/xml2js": "0.4.14",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "^31.2.1",
|
||||
"electron": "31.3.1",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"esm": "3.2.25",
|
||||
"iconsur": "^1.7.0",
|
||||
"jasmine": "5.1.0",
|
||||
"jsdoc": "^4.0.3",
|
||||
"iconsur": "1.7.0",
|
||||
"jasmine": "5.3.1",
|
||||
"jsdoc": "4.0.3",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"nodemon": "^3.1.4",
|
||||
"nodemon": "3.1.7",
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.7.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"rimraf": "6.0.1",
|
||||
"ts-node": "10.9.2",
|
||||
"tslib": "2.7.0",
|
||||
"tsx": "4.19.1",
|
||||
"typescript": "5.6.3",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-cli": "5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import custom from "./routes/custom.js";
|
||||
import error_handlers from "./routes/error_handlers.js";
|
||||
import { startScheduledCleanup } from "./services/erase.js";
|
||||
import sql_init from "./services/sql_init.js";
|
||||
import { t } from "i18next";
|
||||
|
||||
await import('./services/handlers.js');
|
||||
await import('./becca/becca_loader.js');
|
||||
@@ -29,6 +30,11 @@ sql_init.initializeDb();
|
||||
app.set('views', path.join(scriptDir, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.locals.t = t;
|
||||
return next();
|
||||
});
|
||||
|
||||
if (!utils.isElectron()) {
|
||||
app.use(compression()); // HTTP compression
|
||||
}
|
||||
@@ -47,6 +53,7 @@ app.use(cookieParser());
|
||||
app.use(express.static(path.join(scriptDir, 'public/root')));
|
||||
app.use(`/manifest.webmanifest`, express.static(path.join(scriptDir, 'public/manifest.webmanifest')));
|
||||
app.use(`/robots.txt`, express.static(path.join(scriptDir, 'public/robots.txt')));
|
||||
app.use(`/icon.png`, express.static(path.join(scriptDir, 'public/icon.png')));
|
||||
app.use(sessionParser);
|
||||
app.use(favicon(`${scriptDir}/../images/app-icons/icon.ico`));
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ import cls from "../services/cls.js";
|
||||
import entityConstructor from "../becca/entity_constructor.js";
|
||||
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows.js';
|
||||
import AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
|
||||
import options_init from "../services/options_init.js";
|
||||
import ws from "../services/ws.js";
|
||||
|
||||
const beccaLoaded = new Promise<void>(async (res, rej) => {
|
||||
const sqlInit = (await import("../services/sql_init.js")).default;
|
||||
// We have to import async since options init requires keyboard actions which require translations.
|
||||
const options_init = (await import("../services/options_init.js")).default;
|
||||
|
||||
sqlInit.dbReady.then(() => {
|
||||
cls.init(() => {
|
||||
load();
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import protectedSessionService from "../../services/protected_session.js";
|
||||
import log from "../../services/log.js";
|
||||
import sql from "../../services/sql.js";
|
||||
import optionService from "../../services/options.js";
|
||||
import eraseService from "../../services/erase.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import dateUtils from "../../services/date_utils.js";
|
||||
import AbstractBeccaEntity from "./abstract_becca_entity.js";
|
||||
@@ -68,7 +70,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
/** set during the deletion operation, before it is completed (removed from becca completely). */
|
||||
isBeingDeleted!: boolean;
|
||||
isDecrypted!: boolean;
|
||||
|
||||
|
||||
ownedAttributes!: BAttribute[];
|
||||
parentBranches!: BBranch[];
|
||||
parents!: BNote[];
|
||||
@@ -455,8 +457,8 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
return this.getAttributes().find(
|
||||
attr => attr.name.toLowerCase() === name
|
||||
&& (!value || attr.value.toLowerCase() === value)
|
||||
&& attr.type === type);
|
||||
&& (!value || attr.value.toLowerCase() === value)
|
||||
&& attr.type === type);
|
||||
}
|
||||
|
||||
getRelationTarget(name: string) {
|
||||
@@ -1107,7 +1109,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
}
|
||||
|
||||
getRevisions(): BRevision[] {
|
||||
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
|
||||
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ? ORDER BY revisions.utcDateCreated ASC", [this.noteId])
|
||||
.map(row => new BRevision(row));
|
||||
}
|
||||
|
||||
@@ -1612,10 +1614,31 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
revision.setContent(noteContent);
|
||||
|
||||
this.eraseExcessRevisionSnapshots()
|
||||
return revision;
|
||||
});
|
||||
}
|
||||
|
||||
// Limit the number of Snapshots to revisionSnapshotNumberLimit
|
||||
// Delete older Snapshots that exceed the limit
|
||||
eraseExcessRevisionSnapshots() {
|
||||
// lable has a higher priority
|
||||
let revisionSnapshotNumberLimit = parseInt(this.getLabelValue("versioningLimit") ?? "");
|
||||
if (!Number.isInteger(revisionSnapshotNumberLimit)) {
|
||||
revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit'));
|
||||
}
|
||||
if (revisionSnapshotNumberLimit >= 0) {
|
||||
const revisions = this.getRevisions();
|
||||
if (revisions.length - revisionSnapshotNumberLimit > 0) {
|
||||
const revisionIds = revisions
|
||||
.slice(0, revisions.length - revisionSnapshotNumberLimit)
|
||||
.map(revision => revision.revisionId)
|
||||
.filter((id): id is string => id !== undefined);
|
||||
eraseService.eraseRevisions(revisionIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param matchBy - choose by which property we detect if to update an existing attachment.
|
||||
* Supported values are either 'attachmentId' (default) or 'title'
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Make sure not to import any modules that depend on localized messages via i18next here, as the initializations
|
||||
* are loaded later and will result in an empty string.
|
||||
*/
|
||||
|
||||
import { initializeTranslations } from "./services/i18n.js";
|
||||
|
||||
async function startApplication() {
|
||||
await import("./www.js");
|
||||
}
|
||||
|
||||
await initializeTranslations();
|
||||
await startApplication();
|
||||
@@ -14,9 +14,9 @@ const NOTE_TYPE_ICONS = {
|
||||
"code": "bx bx-code",
|
||||
"render": "bx bx-extension",
|
||||
"search": "bx bx-file-find",
|
||||
"relationMap": "bx bx-map-alt",
|
||||
"relationMap": "bx bxs-network-chart",
|
||||
"book": "bx bx-book",
|
||||
"noteMap": "bx bx-map-alt",
|
||||
"noteMap": "bx bxs-network-chart",
|
||||
"mermaid": "bx bx-selection",
|
||||
"canvas": "bx bx-pen",
|
||||
"webView": "bx bx-globe-alt",
|
||||
@@ -543,7 +543,7 @@ class FNote {
|
||||
return workspaceIconClass;
|
||||
}
|
||||
else if (this.noteId === 'root') {
|
||||
return "bx bx-chevrons-right";
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === '_share') {
|
||||
return "bx bx-share-alt";
|
||||
|
||||
@@ -38,6 +38,8 @@ import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
|
||||
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
|
||||
import EditButton from "../widgets/buttons/edit_button.js";
|
||||
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
|
||||
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
|
||||
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
|
||||
import MermaidWidget from "../widgets/mermaid.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
|
||||
@@ -160,6 +162,8 @@ export default class DesktopLayout {
|
||||
.child(new WatchedFileUpdateStatusWidget())
|
||||
.child(new FloatingButtons()
|
||||
.child(new EditButton())
|
||||
.child(new ShowTocWidgetButton())
|
||||
.child(new ShowHighlightsListWidgetButton())
|
||||
.child(new CodeButtonsWidget())
|
||||
.child(new RelationMapButtons())
|
||||
.child(new CopyImageReferenceButton())
|
||||
|
||||
@@ -22,15 +22,31 @@ function setupContextMenu($image) {
|
||||
command: "copyImageReferenceToClipboard",
|
||||
uiIcon: "bx bx-empty"
|
||||
},
|
||||
{title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty"},
|
||||
{ title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty" },
|
||||
],
|
||||
selectMenuItemHandler: ({command}) => {
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === 'copyImageReferenceToClipboard') {
|
||||
imageService.copyImageReferenceToClipboard($image);
|
||||
} else if (command === 'copyImageToClipboard') {
|
||||
const webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
|
||||
utils.dynamicRequire('electron');
|
||||
webContents.copyImageAt(e.pageX, e.pageY);
|
||||
try {
|
||||
const nativeImage = utils.dynamicRequire('electron').nativeImage;
|
||||
const clipboard = utils.dynamicRequire('electron').clipboard;
|
||||
|
||||
const response = await fetch(
|
||||
$image.attr('src')
|
||||
);
|
||||
const blob = await response.blob();
|
||||
|
||||
clipboard.writeImage(
|
||||
nativeImage.createFromBuffer(
|
||||
Buffer.from(
|
||||
await blob.arrayBuffer()
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to copy image to clipboard:', error);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unrecognized command '${command}'`);
|
||||
}
|
||||
@@ -41,4 +57,4 @@ function setupContextMenu($image) {
|
||||
|
||||
export default {
|
||||
setupContextMenu
|
||||
};
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import noteTypesService from "../services/note_types.js";
|
||||
import server from "../services/server.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
|
||||
export default class TreeContextMenu {
|
||||
/**
|
||||
@@ -48,55 +49,55 @@ export default class TreeContextMenu {
|
||||
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
|
||||
|
||||
return [
|
||||
{ title: 'Open in a new tab <kbd>Ctrl+Click</kbd>', command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
|
||||
{ title: 'Open in a new split', command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
||||
{ title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "bx bx-plus",
|
||||
{ title: `${t("tree-context-menu.open-in-a-new-tab")} <kbd>Ctrl+Click</kbd>`, command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
|
||||
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
||||
{ title: `${t("tree-context-menu.insert-note-after")} <kbd data-command="createNoteAfter"></kbd>`, command: "insertNoteAfter", uiIcon: "bx bx-plus",
|
||||
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
|
||||
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions },
|
||||
{ title: 'Insert child note <kbd data-command="createNoteInto"></kbd>', command: "insertChildNote", uiIcon: "bx bx-plus",
|
||||
{ title: `${t("tree-context-menu.insert-child-note")} <kbd data-command="createNoteInto"></kbd>`, command: "insertChildNote", uiIcon: "bx bx-plus",
|
||||
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
|
||||
enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash",
|
||||
{ title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions },
|
||||
{ title: "----" },
|
||||
{ title: 'Search in subtree <kbd data-command="searchInSubtree"></kbd>', command: "searchInSubtree", uiIcon: "bx bx-search",
|
||||
{ title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`, command: "searchInSubtree", uiIcon: "bx bx-search",
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
isHoisted ? null : { title: 'Hoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
|
||||
!isHoisted || !isNotRoot ? null : { title: 'Unhoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
|
||||
{ title: 'Edit branch prefix <kbd data-command="editBranchPrefix"></kbd>', command: "editBranchPrefix", uiIcon: "bx bx-empty",
|
||||
{ title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`, command: "editBranchPrefix", uiIcon: "bx bx-empty",
|
||||
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions },
|
||||
{ title: "Advanced", uiIcon: "bx bx-empty", enabled: true, items: [
|
||||
{ title: 'Expand subtree <kbd data-command="expandSubtree"></kbd>', command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
|
||||
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
|
||||
{ title: 'Sort by ... <kbd data-command="sortChildNotes"></kbd>', command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
|
||||
{ title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
|
||||
{ title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
|
||||
{ title: 'Copy note path to clipboard', command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
|
||||
{ title: t("tree-context-menu.advanced"), uiIcon: "bx bx-empty", enabled: true, items: [
|
||||
{ title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
|
||||
{ title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
|
||||
{ title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`, command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
|
||||
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
|
||||
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
|
||||
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
|
||||
] },
|
||||
{ title: "----" },
|
||||
{ title: "Protect subtree", command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
|
||||
{ title: "Unprotect subtree", command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
|
||||
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
|
||||
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
|
||||
{ title: "----" },
|
||||
{ title: 'Copy / clone <kbd data-command="copyNotesToClipboard"></kbd>', command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
|
||||
{ title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
|
||||
enabled: isNotRoot && !isHoisted },
|
||||
{ title: 'Clone to ... <kbd data-command="cloneNotesTo"></kbd>', command: "cloneNotesTo", uiIcon: "bx bx-empty",
|
||||
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-empty",
|
||||
enabled: isNotRoot && !isHoisted },
|
||||
{ title: 'Cut <kbd data-command="cutNotesToClipboard"></kbd>', command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
|
||||
{ title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`, command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
{ title: 'Move to ... <kbd data-command="moveNotesTo"></kbd>', command: "moveNotesTo", uiIcon: "bx bx-empty",
|
||||
{ title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`, command: "moveNotesTo", uiIcon: "bx bx-empty",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
{ title: 'Paste into <kbd data-command="pasteNotesFromClipboard"></kbd>', command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
|
||||
{ title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`, command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
|
||||
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
|
||||
{ title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
|
||||
{ title: t("tree-context-menu.paste-after"), command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
|
||||
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
|
||||
{ title: `Duplicate subtree <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
|
||||
{ title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
|
||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions },
|
||||
{ title: "----" },
|
||||
{ title: "Export", command: "exportNote", uiIcon: "bx bx-empty",
|
||||
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-empty",
|
||||
enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: "Import into note", command: "importIntoNote", uiIcon: "bx bx-empty",
|
||||
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-empty",
|
||||
enabled: notSearch && noSelectedNotes && notOptions },
|
||||
{ title: "Apply bulk actions", command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
|
||||
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
|
||||
enabled: true }
|
||||
].filter(row => row !== null);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ export async function initLocale() {
|
||||
lng: locale,
|
||||
fallbackLng: "en",
|
||||
backend: {
|
||||
loadPath: `/${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
|
||||
}
|
||||
loadPath: `${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
|
||||
},
|
||||
returnEmptyString: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
|
||||
|
||||
const CODE_MIRROR = {
|
||||
js: [
|
||||
"libraries/codemirror/codemirror.js",
|
||||
// "node_modules/codemirror/lib/codemirror.js",
|
||||
"node_modules/codemirror/lib/codemirror.js",
|
||||
"node_modules/codemirror/addon/display/placeholder.js",
|
||||
"node_modules/codemirror/addon/edit/matchbrackets.js",
|
||||
"node_modules/codemirror/addon/edit/matchtags.js",
|
||||
|
||||
@@ -105,28 +105,23 @@ function initNoteAutocomplete($el, options) {
|
||||
|
||||
$el.addClass("note-autocomplete-input");
|
||||
|
||||
const $clearTextButton = $("<a>")
|
||||
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
|
||||
.prop("title", "Clear text field");
|
||||
const $clearTextButton = $("<button>")
|
||||
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
|
||||
.prop("title", "Clear text field");
|
||||
|
||||
const $showRecentNotesButton = $("<a>")
|
||||
.addClass("input-group-text show-recent-notes-button bx bx-time")
|
||||
.prop("title", "Show recent notes");
|
||||
const $showRecentNotesButton = $("<button>")
|
||||
.addClass("input-group-text show-recent-notes-button bx bx-time")
|
||||
.prop("title", "Show recent notes");
|
||||
|
||||
const $goToSelectedNoteButton = $("<a>")
|
||||
const $goToSelectedNoteButton = $("<button>")
|
||||
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
||||
|
||||
const $sideButtons = $("<div>")
|
||||
.addClass("input-group-append")
|
||||
.append($clearTextButton)
|
||||
.append($showRecentNotesButton);
|
||||
$el.after($clearTextButton).after($showRecentNotesButton);
|
||||
|
||||
if (!options.hideGoToSelectedNoteButton) {
|
||||
$sideButtons.append($goToSelectedNoteButton);
|
||||
$el.after($goToSelectedNoteButton);
|
||||
}
|
||||
|
||||
$el.after($sideButtons);
|
||||
|
||||
$clearTextButton.on('click', () => clearText($el));
|
||||
|
||||
$showRecentNotesButton.on('click', e => {
|
||||
@@ -180,13 +175,13 @@ function initNoteAutocomplete($el, options) {
|
||||
}
|
||||
|
||||
if (suggestion.action === 'create-note') {
|
||||
const {success, noteType, templateNoteId} = await noteCreateService.chooseNoteType();
|
||||
const { success, noteType, templateNoteId } = await noteCreateService.chooseNoteType();
|
||||
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note} = await noteCreateService.createNote(suggestion.parentNoteId, {
|
||||
const { note } = await noteCreateService.createNote(suggestion.parentNoteId, {
|
||||
title: suggestion.noteTitle,
|
||||
activate: false,
|
||||
type: noteType,
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import server from "./server.js";
|
||||
import froca from "./froca.js";
|
||||
import { t } from "./i18n.js";
|
||||
|
||||
async function getNoteTypeItems(command) {
|
||||
const items = [
|
||||
{ title: "Text", command: command, type: "text", uiIcon: "bx bx-note" },
|
||||
{ title: "Code", command: command, type: "code", uiIcon: "bx bx-code" },
|
||||
{ title: "Saved Search", command: command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: "Note Map", command: command, type: "noteMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: "Render Note", command: command, type: "render", uiIcon: "bx bx-extension" },
|
||||
{ title: "Book", command: command, type: "book", uiIcon: "bx bx-book" },
|
||||
{ title: "Mermaid Diagram", command: command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
{ title: "Canvas", command: command, type: "canvas", uiIcon: "bx bx-pen" },
|
||||
{ title: "Web View", command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
{ title: "Mind Map", command, type: "mindMap", uiIcon: "bx bx-sitemap" }
|
||||
{ title: t("note_types.text"), command: command, type: "text", uiIcon: "bx bx-note" },
|
||||
{ title: t("note_types.code"), command: command, type: "code", uiIcon: "bx bx-code" },
|
||||
{ title: t("note_types.saved-search"), command: command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: t("note_types.relation-map"), command: command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.render-note"), command: command, type: "render", uiIcon: "bx bx-extension" },
|
||||
{ title: t("note_types.book"), command: command, type: "book", uiIcon: "bx bx-book" },
|
||||
{ title: t("note_types.mermaid-diagram"), command: command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
{ title: t("note_types.canvas"), command: command, type: "canvas", uiIcon: "bx bx-pen" },
|
||||
{ title: t("note_types.web-view"), command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" }
|
||||
];
|
||||
|
||||
const templateNoteIds = await server.get("search-templates");
|
||||
|
||||
@@ -166,6 +166,23 @@ function getHost() {
|
||||
return `${url.protocol}//${url.hostname}:${url.port}`;
|
||||
}
|
||||
|
||||
async function openDirectory(directory) {
|
||||
try {
|
||||
if (utils.isElectron()) {
|
||||
const electron = utils.dynamicRequire('electron');
|
||||
const res = await electron.shell.openPath(directory);
|
||||
if (res) {
|
||||
console.error('Failed to open directory:', res);
|
||||
}
|
||||
} else {
|
||||
console.error('Not running in an Electron environment.');
|
||||
}
|
||||
} catch (err) {
|
||||
// Handle file system errors (e.g. path does not exist or is inaccessible)
|
||||
console.error('Error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
download,
|
||||
downloadFileNote,
|
||||
@@ -176,4 +193,5 @@ export default {
|
||||
openAttachmentExternally,
|
||||
openNoteCustom,
|
||||
openAttachmentCustom,
|
||||
openDirectory
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@ import ws from "./ws.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
function toast(options) {
|
||||
const $toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="mr-auto"><span class="bx bx-${options.icon}"></span> <span class="toast-title"></span></strong>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
</div>`);
|
||||
const $toast = $(
|
||||
`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">
|
||||
<span class="bx bx-${options.icon}"></span>
|
||||
<span class="toast-title"></span>
|
||||
</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
</div>`
|
||||
);
|
||||
|
||||
$toast.find('.toast-title').text(options.title);
|
||||
$toast.find('.toast-body').text(options.message);
|
||||
|
||||
@@ -201,7 +201,7 @@ function getMimeTypeClass(mime) {
|
||||
|
||||
function closeActiveDialog() {
|
||||
if (glob.activeDialog) {
|
||||
glob.activeDialog.modal('hide');
|
||||
bootstrap.Modal.getOrCreateInstance(glob.activeDialog).hide();
|
||||
glob.activeDialog = null;
|
||||
}
|
||||
}
|
||||
@@ -245,8 +245,7 @@ async function openDialog($dialog, closeActDialog = true) {
|
||||
}
|
||||
|
||||
saveFocusedElement();
|
||||
|
||||
$dialog.modal();
|
||||
bootstrap.Modal.getOrCreateInstance($dialog).show();
|
||||
|
||||
$dialog.on('hidden.bs.modal', () => {
|
||||
$(".aa-input").autocomplete("close");
|
||||
|
||||
@@ -97,7 +97,7 @@ const TPL = `
|
||||
<tr class="attr-row-promoted"
|
||||
title="${t('attribute_detail.promoted_title')}">
|
||||
<th>${t('attribute_detail.promoted')}</th>
|
||||
<td><input type="checkbox" class="attr-input-promoted form-control form-control-sm" /></td>
|
||||
<td><input type="checkbox" class="attr-input-promoted form-check" /></td>
|
||||
</tr>
|
||||
<tr class="attr-row-promoted-alias">
|
||||
<th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th>
|
||||
@@ -134,9 +134,7 @@ const TPL = `
|
||||
<td>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control attr-input-number-precision" style="text-align: right">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">${t('attribute_detail.digits')}</span>
|
||||
</div>
|
||||
<span class="input-group-text">${t('attribute_detail.digits')}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -150,7 +148,7 @@ const TPL = `
|
||||
</tr>
|
||||
<tr title="${t('attribute_detail.inheritable_title')}">
|
||||
<th>${t('attribute_detail.inheritable')}</th>
|
||||
<td><input type="checkbox" class="attr-input-inheritable form-control form-control-sm" /></td>
|
||||
<td><input type="checkbox" class="attr-input-inheritable form-check" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -349,7 +347,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
|
||||
this.$inputTargetNote = this.$widget.find('.attr-input-target-note');
|
||||
|
||||
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, {allowCreatingNotes: true})
|
||||
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, { allowCreatingNotes: true })
|
||||
.on('autocomplete:noteselected', (event, suggestion, dataset) => {
|
||||
if (!suggestion.notePath) {
|
||||
return false;
|
||||
@@ -403,7 +401,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async showAttributeDetail({allAttributes, attribute, isOwned, x, y, focus}) {
|
||||
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus }) {
|
||||
if (!attribute) {
|
||||
this.hide();
|
||||
|
||||
@@ -545,7 +543,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
|
||||
return {left, right};
|
||||
return { left, right };
|
||||
}
|
||||
|
||||
async saveAndClose() {
|
||||
@@ -589,19 +587,19 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async updateRelatedNotes() {
|
||||
let {results, count} = await server.post('search-related', this.attribute);
|
||||
let { results, count } = await server.post('search-related', this.attribute);
|
||||
|
||||
for (const res of results) {
|
||||
res.noteId = res.notePathArray[res.notePathArray.length - 1];
|
||||
}
|
||||
|
||||
results = results.filter(({noteId}) => noteId !== this.noteId);
|
||||
results = results.filter(({ noteId }) => noteId !== this.noteId);
|
||||
|
||||
if (results.length === 0) {
|
||||
this.$relatedNotesContainer.hide();
|
||||
} else {
|
||||
this.$relatedNotesContainer.show();
|
||||
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", {attributeType: this.attribute.type, attributeName: this.attribute.name}));
|
||||
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", { attributeType: this.attribute.type, attributeName: this.attribute.name }));
|
||||
|
||||
this.$relatedNotesList.empty();
|
||||
|
||||
@@ -611,7 +609,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
for (const note of displayedNotes) {
|
||||
const notePath = note.getBestNotePathString(hoistedNoteId);
|
||||
const $noteLink = await linkService.createLink(notePath, {showNotePath: true});
|
||||
const $noteLink = await linkService.createLink(notePath, { showNotePath: true });
|
||||
|
||||
this.$relatedNotesList.append(
|
||||
$("<li>").append($noteLink)
|
||||
@@ -619,7 +617,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
if (results.length > DISPLAYED_NOTES) {
|
||||
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", {count: count - DISPLAYED_NOTES}));
|
||||
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", { count: count - DISPLAYED_NOTES }));
|
||||
} else {
|
||||
this.$relatedNotesMoreNotes.hide();
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ const editorConfig = {
|
||||
toolbar: {
|
||||
items: []
|
||||
},
|
||||
placeholder: "Type the labels and relations here",
|
||||
placeholder: t("attribute_editor.placeholder"),
|
||||
mention: mentionSetup
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const TPL = `
|
||||
<td class="button-column">
|
||||
<div style="display: flex">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
${t('execute_script.help_text')}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t("add_label.help_text")}</p>
|
||||
|
||||
@@ -30,7 +30,7 @@ const TPL = `
|
||||
<li>${t("add_label.help_text_item2")}</li>
|
||||
</ul>
|
||||
|
||||
<p>${t("add_label.help_text_note")}</p>
|
||||
${t("add_label.help_text_note")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t("update_label_value.help_text")}</p>
|
||||
|
||||
<p>${t("update_label_value.help_text_note")}</p>
|
||||
${t("update_label_value.help_text_note")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t("delete_note.delete_matched_notes_description")}</p>
|
||||
|
||||
<p>${t("delete_note.undelete_notes_instruction")}</p>
|
||||
|
||||
<p>${t("delete_note.erase_notes_instruction")}</p>
|
||||
${t("delete_note.erase_notes_instruction")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
${t('delete_revisions.all_past_note_revisions')}
|
||||
</div>
|
||||
|
||||
@@ -18,11 +18,11 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('move_note.on_all_matched_notes')}:</p>
|
||||
|
||||
<ul>
|
||||
<ul style="margin-bottom: 0;">
|
||||
<li>${t('move_note.move_note_new_parent')}</li>
|
||||
<li>${t('move_note.clone_note_new_parent')}</li>
|
||||
<li>${t('move_note.nothing_will_happen')}</li>
|
||||
|
||||
@@ -16,7 +16,7 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('rename_note.evaluated_as_js_string')}</p>
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('add_relation.create_relation_on_all_matched_notes')}</p>
|
||||
${t('add_relation.create_relation_on_all_matched_notes')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t('update_relation_target.on_all_matched_notes')}:</p>
|
||||
|
||||
<ul>
|
||||
<ul style="margin-bottom: 0;">
|
||||
<li>${t('update_relation_target.create_given_relation')}</li>
|
||||
<li>${t('update_relation_target.change_target_note')}</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = `<button class="button-widget bx"
|
||||
data-toggle="tooltip"
|
||||
data-bs-toggle="tooltip"
|
||||
title=""></button>`;
|
||||
|
||||
export default class AbstractButtonWidget extends NoteContextAwareWidget {
|
||||
@@ -22,10 +22,13 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.tooltip = new bootstrap.Tooltip(this.$widget, {
|
||||
html: true, title: () => this.getTitle(), trigger: 'hover'
|
||||
})
|
||||
|
||||
if (this.settings.onContextMenu) {
|
||||
this.$widget.on("contextmenu", e => {
|
||||
this.$widget.tooltip("hide");
|
||||
this.tooltip.hide();
|
||||
|
||||
this.settings.onContextMenu(e);
|
||||
|
||||
@@ -35,12 +38,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$widget.attr("data-placement", this.settings.titlePlacement);
|
||||
|
||||
this.$widget.tooltip({
|
||||
html: true,
|
||||
title: () => this.getTitle(),
|
||||
trigger: "hover"
|
||||
});
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"
|
||||
style="position: relative; top: 3px;"></button>
|
||||
|
||||
@@ -61,7 +61,8 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
|
||||
|
||||
this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input");
|
||||
this.$uploadNewRevisionInput.on('change', async () => {
|
||||
@@ -84,7 +85,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
.addClass("disabled")
|
||||
.append($('<span class="disabled-tooltip"> (?)</span>')
|
||||
.attr("title", t('attachments_actions.open_externally_detail_page'))
|
||||
);
|
||||
);
|
||||
if (isElectron) {
|
||||
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
|
||||
$openAttachmentCustomButton
|
||||
@@ -94,7 +95,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!isElectron){
|
||||
if (!isElectron) {
|
||||
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
|
||||
$openAttachmentCustomButton
|
||||
.addClass("disabled")
|
||||
@@ -138,7 +139,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
const {note: newNote} = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
|
||||
const { note: newNote } = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
|
||||
toastService.showMessage(t('attachments_actions.convert_success', { title: this.attachment.title }));
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
|
||||
@@ -155,6 +156,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
await server.put(`attachments/${this.attachmentId}/rename`, {title: attachmentTitle});
|
||||
await server.put(`attachments/${this.attachmentId}/rename`, { title: attachmentTitle });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +74,12 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
|
||||
this.$month = this.$dropdownContent.find('[data-calendar-area="month"]');
|
||||
this.$weekHeader = this.$dropdownContent.find(".calendar-week");
|
||||
|
||||
|
||||
this.manageFirstDayOfWeek();
|
||||
|
||||
|
||||
// Month navigation
|
||||
this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]');
|
||||
this.$monthSelect.on("input", (e) => {
|
||||
@@ -88,10 +88,10 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
});
|
||||
this.$next = this.$dropdownContent.find('[data-calendar-toggle="next"]');
|
||||
this.$next.on('click', () => {
|
||||
this.date.setMonth(this.date.getMonth() + 1);
|
||||
this.date.setMonth(this.date.getMonth() + 1);
|
||||
this.createMonth();
|
||||
});
|
||||
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
|
||||
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
|
||||
this.$previous.on('click', e => {
|
||||
this.date.setMonth(this.date.getMonth() - 1);
|
||||
this.createMonth();
|
||||
@@ -108,7 +108,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
this.date.setFullYear(this.date.getFullYear() + 1);
|
||||
this.createMonth();
|
||||
});
|
||||
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
|
||||
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
|
||||
this.$previousYear.on('click', e => {
|
||||
this.date.setFullYear(this.date.getFullYear() - 1);
|
||||
this.createMonth();
|
||||
@@ -123,11 +123,13 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
|
||||
if (note) {
|
||||
appContext.tabManager.getActiveContext().setNote(note.noteId);
|
||||
this.hideDropdown();
|
||||
this.dropdown.hide();
|
||||
}
|
||||
else {
|
||||
toastService.showError(t("calendar.cannot_find_day_note"));
|
||||
}
|
||||
|
||||
ev.stopPropagation();
|
||||
});
|
||||
|
||||
// Prevent dismissing the calendar popup by clicking on an empty space inside it.
|
||||
@@ -138,9 +140,9 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
this.firstDayOfWeek = options.getInt("firstDayOfWeek");
|
||||
|
||||
// Generate the list of days of the week taking into consideration the user's selected first day of week.
|
||||
let localeDaysOfWeek = [ ...DAYS_OF_WEEK ];
|
||||
let localeDaysOfWeek = [...DAYS_OF_WEEK];
|
||||
const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
|
||||
localeDaysOfWeek = [ ...localeDaysOfWeek, ...daysToBeAddedAtEnd ];
|
||||
localeDaysOfWeek = [...localeDaysOfWeek, ...daysToBeAddedAtEnd];
|
||||
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`));
|
||||
}
|
||||
|
||||
@@ -184,7 +186,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
|
||||
if (dateNoteId) {
|
||||
$newDay.addClass('calendar-date-exists');
|
||||
$newDay.attr("href", `#root/${dateNoteId}`);
|
||||
$newDay.attr("data-href", `#root/${dateNoteId}`);
|
||||
}
|
||||
|
||||
if (this.isEqual(this.date, this.activeDate)) {
|
||||
@@ -236,11 +238,11 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
this.$yearSelect.val(this.date.getFullYear());
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (!loadResults.getOptionNames().includes("firstDayOfWeek")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.manageFirstDayOfWeek();
|
||||
this.createMonth();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget {
|
||||
|
||||
if (this.settings.command) {
|
||||
this.$widget.on("click", () => {
|
||||
this.$widget.tooltip("hide");
|
||||
this.tooltip.hide();
|
||||
|
||||
this.triggerCommand(this._command);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import UpdateAvailableWidget from "./update_available.js";
|
||||
import options from "../../services/options.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown global-menu dropright">
|
||||
<div class="dropdown global-menu dropend">
|
||||
<style>
|
||||
.global-menu {
|
||||
width: 53px;
|
||||
@@ -70,6 +70,7 @@ const TPL = `
|
||||
background-color: var(--button-background-color);
|
||||
padding: 3px;
|
||||
margin-left: 3px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a:hover {
|
||||
@@ -102,10 +103,9 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" data-placement="right"
|
||||
aria-haspopup="true" aria-expanded="false"
|
||||
class="icon-action global-menu-button" title="${t('global_menu.menu')}">
|
||||
<svg viewBox="0 0 256 256">
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action global-menu-button">
|
||||
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}">
|
||||
<g>
|
||||
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
|
||||
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
|
||||
@@ -126,7 +126,7 @@ const TPL = `
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li class="dropdown-item" data-trigger-command="showOptions">
|
||||
<span class="bx bx-slider"></span>
|
||||
<span class="bx bx-cog"></span>
|
||||
${t('global_menu.options')}
|
||||
</li>
|
||||
|
||||
@@ -177,7 +177,7 @@ const TPL = `
|
||||
|
||||
<li class="dropdown-item dropdown-submenu">
|
||||
<span class="dropdown-toggle">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-chip"></span>
|
||||
${t('global_menu.advanced')}
|
||||
</span>
|
||||
|
||||
@@ -195,43 +195,43 @@ const TPL = `
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-data"></span>
|
||||
${t('global_menu.open_sql_console_history')}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showSearchHistory">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-search-alt"></span>
|
||||
${t('global_menu.open_search_history')}
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showBackendLog">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-detail"></span>
|
||||
${t('global_menu.show_backend_log')}
|
||||
<kbd data-command="showBackendLog"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
|
||||
title="${t('global_menu.reload_hint')}">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-refresh"></span>
|
||||
${t('global_menu.reload_frontend')}
|
||||
<kbd data-command="reloadFrontendApp"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item" data-trigger-command="showHiddenSubtree">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-hide"></span>
|
||||
${t('global_menu.show_hidden_subtree')}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
|
||||
<span class="bx bx-info-circle"></span>
|
||||
<span class="bx bx-help-circle"></span>
|
||||
${t('global_menu.show_help')}
|
||||
<kbd data-command="showHelp"></kbd>
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item show-about-dialog-button">
|
||||
<span class="bx bx-empty"></span>
|
||||
<span class="bx bx-info-circle"></span>
|
||||
${t('global_menu.about')}
|
||||
</li>
|
||||
|
||||
@@ -259,10 +259,9 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$dropdown = this.$widget.find("[data-toggle='dropdown']");
|
||||
const $button = this.$widget.find(".global-menu-button");
|
||||
$button.tooltip({ trigger: "hover" });
|
||||
$button.on("click", () => $button.tooltip("hide"));
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
|
||||
this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" });
|
||||
|
||||
this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog"));
|
||||
|
||||
@@ -278,8 +277,13 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$dropdown.dropdown('toggle');
|
||||
this.dropdown.toggle();
|
||||
});
|
||||
this.$widget.on('click', '.dropdown-submenu', e => {
|
||||
if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass('dropdown-toggle')) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
})
|
||||
|
||||
this.$widget.find(".global-menu-button-update-available").append(
|
||||
this.updateAvailableWidget.render()
|
||||
@@ -292,7 +296,12 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
this.$zoomState = this.$widget.find(".zoom-state");
|
||||
this.$widget.on('show.bs.dropdown', () => this.updateZoomState());
|
||||
this.$widget.on('show.bs.dropdown', () => {
|
||||
this.updateZoomState();
|
||||
this.tooltip.hide();
|
||||
this.tooltip.disable();
|
||||
});
|
||||
this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable());
|
||||
|
||||
this.$widget.find(".zoom-buttons").on("click",
|
||||
// delay to wait for the actual zoom change
|
||||
@@ -342,10 +351,10 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
activeContextChangedEvent() {
|
||||
this.$dropdown.dropdown('hide');
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
noteSwitchedEvent() {
|
||||
this.$dropdown.dropdown('hide');
|
||||
this.dropdown.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const TPL = `
|
||||
}
|
||||
|
||||
.note-actions .dropdown-menu {
|
||||
width: 15em;
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
|
||||
@@ -27,7 +27,7 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
@@ -82,7 +82,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
this.$importNoteButton = this.$widget.find('.import-files-button');
|
||||
this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId}));
|
||||
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown('toggle'));
|
||||
|
||||
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
|
||||
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown right-dropdown-widget dropright">
|
||||
<div class="dropdown right-dropdown-widget dropend">
|
||||
<style>
|
||||
.right-dropdown-widget {
|
||||
height: 53px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" data-placement="right"
|
||||
<button type="button" data-bs-toggle="dropdown" data-placement="right"
|
||||
aria-haspopup="true" aria-expanded="false"
|
||||
class="bx right-dropdown-button launcher-button"></button>
|
||||
|
||||
<div class="tooltip-trigger"></div>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right"></div>
|
||||
</div>
|
||||
`;
|
||||
@@ -28,12 +30,16 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$dropdownMenu = this.$widget.find(".dropdown-menu");
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
|
||||
const $button = this.$widget.find(".right-dropdown-button")
|
||||
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
|
||||
this.tooltip = new bootstrap.Tooltip(this.$tooltip);
|
||||
|
||||
this.$widget.find(".right-dropdown-button")
|
||||
.addClass(this.iconClass)
|
||||
.attr("title", this.title)
|
||||
.tooltip({ trigger: "hover" })
|
||||
.on("click", () => $button.tooltip("hide"));
|
||||
.on("click", () => this.tooltip.hide())
|
||||
.on('mouseenter', () => this.tooltip.show())
|
||||
.on('mouseleave', () => this.tooltip.hide());
|
||||
|
||||
this.$widget.on('show.bs.dropdown', async () => {
|
||||
await this.dropdownShown();
|
||||
@@ -51,10 +57,5 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
// to be overridden
|
||||
async dropdownShow() {}
|
||||
|
||||
hideDropdown() {
|
||||
this.$widget.dropdown("hide");
|
||||
this.$dropdownMenu.removeClass("show");
|
||||
}
|
||||
async dropdownShow() { }
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,16 @@ import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import openService from "../../services/open.js";
|
||||
|
||||
|
||||
const TPL = `
|
||||
<div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("about.title")}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t("about.title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table table-borderless text-nowrap">
|
||||
@@ -72,7 +71,18 @@ export default class AboutDialog extends BasicWidget {
|
||||
this.$buildDate.text(appInfo.buildDate);
|
||||
this.$buildRevision.text(appInfo.buildRevision);
|
||||
this.$buildRevision.attr('href', `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`);
|
||||
this.$dataDirectory.text(appInfo.dataDirectory);
|
||||
if (utils.isElectron()) {
|
||||
this.$dataDirectory.html($('<a></a>', {
|
||||
href: '#',
|
||||
text: appInfo.dataDirectory,
|
||||
}));
|
||||
this.$dataDirectory.find("a").on('click', (event) => {
|
||||
event.preventDefault();
|
||||
openService.openDirectory(appInfo.dataDirectory);
|
||||
})
|
||||
} else {
|
||||
this.$dataDirectory.text(appInfo.dataDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
async openAboutDialogEvent() {
|
||||
|
||||
@@ -9,13 +9,9 @@ const TPL = `
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('add_link.add_link')}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t('add_link.add_link')}</h5>
|
||||
<button type="button" class="help-button" title="${t('add_link.help_on_links')}" data-help-page="links.html">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('add_link.close')}" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('add_link.close')}"></button>
|
||||
</div>
|
||||
<form class="add-link-form">
|
||||
<div class="modal-body">
|
||||
|
||||
@@ -14,13 +14,9 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
<form class="branch-prefix-form">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('branch_prefix.edit_branch_prefix')}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t('branch_prefix.edit_branch_prefix')}</h5>
|
||||
<button class="help-button" type="button" data-help-page="tree-concepts.html#prefix" title="${t('branch_prefix.help_on_tree_prefix')}">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('branch_prefix.close')}" style="margin-left: 0;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('branch_prefix.close')}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
@@ -28,10 +24,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
|
||||
<div class="input-group">
|
||||
<input class="branch-prefix-input form-control">
|
||||
|
||||
<div class="input-group-append">
|
||||
<div class="branch-prefix-note-title input-group-text"></div>
|
||||
</div>
|
||||
<div class="branch-prefix-note-title input-group-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,6 +39,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
export default class BranchPrefixDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$form = this.$widget.find(".branch-prefix-form");
|
||||
this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
|
||||
this.$noteTitle = this.$widget.find('.branch-prefix-note-title');
|
||||
@@ -60,7 +54,7 @@ export default class BranchPrefixDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
async refresh(notePath) {
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
|
||||
if (!noteId || !parentNoteId) {
|
||||
return;
|
||||
@@ -97,9 +91,9 @@ export default class BranchPrefixDialog extends BasicWidget {
|
||||
async savePrefix() {
|
||||
const prefix = this.$treePrefixInput.val();
|
||||
|
||||
await server.put(`branches/${branchId}/set-prefix`, {prefix: prefix});
|
||||
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
toastService.showMessage(t('branch_prefix.branch_prefix_saved'));
|
||||
}
|
||||
|
||||
@@ -38,20 +38,15 @@ const TPL = `
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('bulk_actions.bulk_actions')}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('bulk_actions.close')}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t('bulk_actions.bulk_actions')}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('bulk_actions.close')}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h4>${t('bulk_actions.affected_notes')}: <span class="affected-note-count">0</span></h4>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="include-descendants form-check-input" type="checkbox" value="">
|
||||
${t('bulk_actions.include_descendants')}
|
||||
</label>
|
||||
<input class="include-descendants form-check-input" type="checkbox" value="">
|
||||
<label class="form-check-label">${t('bulk_actions.include_descendants')}</label>
|
||||
</div>
|
||||
|
||||
<h4>${t('bulk_actions.available_actions')}</h4>
|
||||
@@ -72,7 +67,6 @@ const TPL = `
|
||||
export default class BulkActionsDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$includeDescendants = this.$widget.find(".include-descendants");
|
||||
this.$includeDescendants.on("change", () => this.refresh());
|
||||
|
||||
|
||||
@@ -13,13 +13,9 @@ const TPL = `
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('clone_to.clone_notes_to')}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t('clone_to.clone_notes_to')}</h5>
|
||||
<button type="button" class="help-button" title="${t('clone_to.help_on_links')}" data-help-page="cloning-notes.html">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="clone-to-form">
|
||||
<div class="modal-body">
|
||||
@@ -81,9 +77,9 @@ export default class CloneToDialog extends BasicWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async cloneNoteIdsToEvent({noteIds}) {
|
||||
async cloneNoteIdsToEvent({ noteIds }) {
|
||||
if (!noteIds || noteIds.length === 0) {
|
||||
noteIds = [ appContext.tabManager.getActiveContextNoteId() ];
|
||||
noteIds = [appContext.tabManager.getActiveContextNoteId()];
|
||||
}
|
||||
|
||||
this.clonedNoteIds = [];
|
||||
@@ -111,7 +107,7 @@ export default class CloneToDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
async cloneNotesTo(notePath) {
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
|
||||
for (const cloneNoteId of this.clonedNoteIds) {
|
||||
@@ -120,7 +116,7 @@ export default class CloneToDialog extends BasicWidget {
|
||||
const clonedNote = await froca.getNote(cloneNoteId);
|
||||
const targetNote = await froca.getBranch(targetBranchId).getNote();
|
||||
|
||||
toastService.showMessage(t('clone_to.note_cloned', {clonedTitle: clonedNote.title, targetTitle: targetNote.title}));
|
||||
toastService.showMessage(t('clone_to.note_cloned', { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,8 @@ const TPL = `
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('confirm.confirmation')}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t('confirm.confirmation')}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="confirm-dialog-content"></div>
|
||||
@@ -40,6 +37,7 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$confirmContent = this.$widget.find(".confirm-dialog-content");
|
||||
this.$okButton = this.$widget.find(".confirm-dialog-ok-button");
|
||||
this.$cancelButton = this.$widget.find(".confirm-dialog-cancel-button");
|
||||
@@ -62,7 +60,7 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
this.$okButton.on('click', () => this.doResolve(true));
|
||||
}
|
||||
|
||||
showConfirmDialogEvent({message, callback}) {
|
||||
showConfirmDialogEvent({ message, callback }) {
|
||||
this.$originallyFocused = $(':focus');
|
||||
|
||||
this.$custom.hide();
|
||||
@@ -75,15 +73,15 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
this.$confirmContent.empty().append(message);
|
||||
|
||||
this.$widget.modal();
|
||||
this.modal.show();
|
||||
|
||||
this.resolve = callback;
|
||||
}
|
||||
|
||||
showConfirmDeleteNoteBoxWithNoteDialogEvent({title, callback}) {
|
||||
showConfirmDeleteNoteBoxWithNoteDialogEvent({ title, callback }) {
|
||||
glob.activeDialog = this.$widget;
|
||||
|
||||
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', {title: title})}`);
|
||||
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', { title: title })}`);
|
||||
|
||||
this.$custom.empty()
|
||||
.append("<br/>")
|
||||
@@ -104,7 +102,7 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
this.$custom.show();
|
||||
|
||||
this.$widget.modal();
|
||||
this.modal.show();
|
||||
|
||||
this.resolve = callback;
|
||||
}
|
||||
@@ -117,6 +115,6 @@ export default class ConfirmDialog extends BasicWidget {
|
||||
|
||||
this.resolve = null;
|
||||
|
||||
this.$widget.modal("hide");
|
||||
this.modal.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,27 +10,18 @@ const TPL = `
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title mr-auto">${t('delete_notes.delete_notes_preview')}</h4>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">${t('delete_notes.delete_notes_preview')}</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input class="delete-all-clones" value="1" type="checkbox">
|
||||
|
||||
${t('delete_notes.delete_all_clones_description')}
|
||||
</label>
|
||||
<div class="form-checkbox">
|
||||
<input class="delete-all-clones form-check-input" value="1" type="checkbox">
|
||||
<label class="form-check-label">${t('delete_notes.delete_all_clones_description')}</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label title="${t('delete_notes.erase_notes_description')}">
|
||||
<input class="erase-notes" value="1" type="checkbox">
|
||||
|
||||
${t('delete_notes.erase_notes_warning')}
|
||||
</label>
|
||||
<div class="form-checkbox" style="margin-bottom: 1rem">
|
||||
<input class="erase-notes form-check-input" value="1" type="checkbox">
|
||||
<label class="form-check-label">${t('delete_notes.erase_notes_warning')}</label>
|
||||
</div>
|
||||
|
||||
<div class="delete-notes-list-wrapper">
|
||||
|
||||
@@ -34,9 +34,7 @@ const TPL = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${t('export.export_note_title')} <span class="export-note-title"></span></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('export.close')}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('export.close')}"></button>
|
||||
</div>
|
||||
<form class="export-form">
|
||||
<div class="modal-body">
|
||||
@@ -127,6 +125,7 @@ export default class ExportDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$form = this.$widget.find(".export-form");
|
||||
this.$noteTitle = this.$widget.find(".export-note-title");
|
||||
this.$subtreeFormats = this.$widget.find(".export-subtree-formats");
|
||||
@@ -137,7 +136,7 @@ export default class ExportDialog extends BasicWidget {
|
||||
this.$opmlVersions = this.$widget.find(".opml-versions");
|
||||
|
||||
this.$form.on('submit', () => {
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
const exportType = this.$widget.find("input[name='export-type']:checked").val();
|
||||
|
||||
@@ -188,7 +187,7 @@ export default class ExportDialog extends BasicWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async showExportDialogEvent({notePath, defaultType}) {
|
||||
async showExportDialogEvent({ notePath, defaultType }) {
|
||||
this.taskId = '';
|
||||
this.$exportButton.removeAttr("disabled");
|
||||
|
||||
@@ -208,7 +207,7 @@ export default class ExportDialog extends BasicWidget {
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
|
||||
this.branchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
this.$noteTitle.text(await treeService.getNoteTitle(noteId));
|
||||
|
||||
@@ -7,14 +7,11 @@ const TPL = `
|
||||
<div class="modal-dialog" role="document" style="min-width: 100%; height: 100%; margin: 0;">
|
||||
<div class="modal-content" style="height: auto;">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('help.fullDocumentation')}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('help.close')}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t('help.fullDocumentation')}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('help.close')}"></button>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow: auto; height: calc(100vh - 70px);">
|
||||
<div class="card-columns help-cards">
|
||||
<div class="help-cards row row-cols-3 g-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">${t('help.noteNavigation')}</h5>
|
||||
|
||||
@@ -11,9 +11,7 @@ const TPL = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${t('import.importIntoNote')}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t('import.close')}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('import.close')}"></button>
|
||||
</div>
|
||||
<form class="import-form">
|
||||
<div class="modal-body">
|
||||
@@ -29,21 +27,21 @@ const TPL = `
|
||||
<strong>${t('import.options')}:</strong>
|
||||
|
||||
<div class="checkbox">
|
||||
<label data-toggle="tooltip" title="${t('import.safeImportTooltip')}">
|
||||
<label data-bs-toggle="tooltip" title="${t('import.safeImportTooltip')}">
|
||||
<input class="safe-import-checkbox" value="1" type="checkbox" checked>
|
||||
<span>${t('import.safeImport')}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label data-toggle="tooltip" title="${t('import.explodeArchivesTooltip')}">
|
||||
<label data-bs-toggle="tooltip" title="${t('import.explodeArchivesTooltip')}">
|
||||
<input class="explode-archives-checkbox" value="1" type="checkbox" checked>
|
||||
<span>${t('import.explodeArchives')}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label data-toggle="tooltip" title="${t('import.shrinkImagesTooltip')}">
|
||||
<label data-bs-toggle="tooltip" title="${t('import.shrinkImagesTooltip')}">
|
||||
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t('import.shrinkImages')}</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -86,6 +84,8 @@ export default class ImportDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
|
||||
this.$form = this.$widget.find(".import-form");
|
||||
this.$noteTitle = this.$widget.find(".import-note-title");
|
||||
this.$fileUploadInput = this.$widget.find(".import-file-upload-input");
|
||||
@@ -115,12 +115,14 @@ export default class ImportDialog extends BasicWidget {
|
||||
}
|
||||
});
|
||||
|
||||
this.$widget.find('[data-toggle="tooltip"]').tooltip({
|
||||
html: true
|
||||
let _ = [...this.$widget.find('[data-bs-toggle="tooltip"]')].forEach(element => {
|
||||
bootstrap.Tooltip.getOrCreateInstance(element, {
|
||||
html: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async showImportDialogEvent({noteId}) {
|
||||
async showImportDialogEvent({ noteId }) {
|
||||
this.parentNoteId = noteId;
|
||||
|
||||
this.$fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below
|
||||
|
||||
@@ -11,9 +11,7 @@ const TPL = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${t('include_note.dialog_title')}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="include-note-form">
|
||||
<div class="modal-body">
|
||||
@@ -27,22 +25,16 @@ const TPL = `
|
||||
${t('include_note.box_size_prompt')}
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
|
||||
${t('include_note.box_size_small')}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
|
||||
<label class="form-check-label">${t('include_note.box_size_small')}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
|
||||
${t('include_note.box_size_medium')}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
|
||||
<label class="form-check-label">${t('include_note.box_size_medium')}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
|
||||
${t('include_note.box_size_full')}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
|
||||
<label class="form-check-label">${t('include_note.box_size_full')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -56,13 +48,14 @@ const TPL = `
|
||||
export default class IncludeNoteDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$form = this.$widget.find(".include-note-form");
|
||||
this.$autoComplete = this.$widget.find(".include-note-autocomplete");
|
||||
this.$form.on('submit', () => {
|
||||
const notePath = this.$autoComplete.getSelectedNotePath();
|
||||
|
||||
if (notePath) {
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
this.includeNote(notePath);
|
||||
} else {
|
||||
logError("No noteId to include.");
|
||||
@@ -72,7 +65,7 @@ export default class IncludeNoteDialog extends BasicWidget {
|
||||
})
|
||||
}
|
||||
|
||||
async showIncludeNoteDialogEvent({textTypeWidget}) {
|
||||
async showIncludeNoteDialogEvent({ textTypeWidget }) {
|
||||
this.textTypeWidget = textTypeWidget;
|
||||
await this.refresh();
|
||||
utils.openDialog(this.$widget);
|
||||
|
||||
@@ -7,11 +7,8 @@ const TPL = `
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("info.modalTitle")}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t("info.closeButton")}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t("info.modalTitle")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("info.closeButton")}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="info-dialog-content"></div>
|
||||
@@ -33,6 +30,7 @@ export default class InfoDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$infoContent = this.$widget.find(".info-dialog-content");
|
||||
this.$okButton = this.$widget.find(".info-dialog-ok-button");
|
||||
|
||||
@@ -49,10 +47,10 @@ export default class InfoDialog extends BasicWidget {
|
||||
}
|
||||
});
|
||||
|
||||
this.$okButton.on('click', () => this.$widget.modal("hide"));
|
||||
this.$okButton.on('click', () => this.modal.hide());
|
||||
}
|
||||
|
||||
showInfoDialogEvent({message, callback}) {
|
||||
showInfoDialogEvent({ message, callback }) {
|
||||
this.$originallyFocused = $(':focus');
|
||||
|
||||
this.$infoContent.text(message);
|
||||
|
||||
@@ -12,10 +12,7 @@ const TPL = `<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="
|
||||
<div class="input-group">
|
||||
<input class="jump-to-note-autocomplete form-control" placeholder="${t('jump_to_note.search_placeholder')}">
|
||||
</div>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="algolia-autocomplete-container jump-to-note-results"></div>
|
||||
@@ -38,6 +35,8 @@ export default class JumpToNoteDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
|
||||
this.$autoComplete = this.$widget.find(".jump-to-note-autocomplete");
|
||||
this.$results = this.$widget.find(".jump-to-note-results");
|
||||
this.$showInFullTextButton = this.$widget.find(".show-in-full-text-button");
|
||||
@@ -94,8 +93,8 @@ export default class JumpToNoteDialog extends BasicWidget {
|
||||
|
||||
const searchString = this.$autoComplete.val();
|
||||
|
||||
this.triggerCommand('searchNotes', {searchString});
|
||||
this.triggerCommand('searchNotes', { searchString });
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ const TPL = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${t("markdown_import.dialog_title")}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>${t("markdown_import.modal_body_text")}</p>
|
||||
@@ -37,6 +35,7 @@ export default class MarkdownImportDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$importTextarea = this.$widget.find('.markdown-import-textarea');
|
||||
this.$importButton = this.$widget.find('.markdown-import-button');
|
||||
|
||||
@@ -48,7 +47,7 @@ export default class MarkdownImportDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
async convertMarkdownToHtml(markdownContent) {
|
||||
const {htmlContent} = await server.post('other/render-markdown', { markdownContent });
|
||||
const { htmlContent } = await server.post('other/render-markdown', { markdownContent });
|
||||
|
||||
const textEditor = await appContext.tabManager.getActiveContext().getTextEditor();
|
||||
|
||||
@@ -70,7 +69,7 @@ export default class MarkdownImportDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const {clipboard} = utils.dynamicRequire('electron');
|
||||
const { clipboard } = utils.dynamicRequire('electron');
|
||||
const text = clipboard.readText();
|
||||
|
||||
this.convertMarkdownToHtml(text);
|
||||
@@ -83,7 +82,7 @@ export default class MarkdownImportDialog extends BasicWidget {
|
||||
async sendForm() {
|
||||
const text = this.$importTextarea.val();
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
await this.convertMarkdownToHtml(text);
|
||||
|
||||
|
||||
@@ -5,18 +5,15 @@ import froca from "../../services/froca.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js"; // Added import
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="move-to-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("move_to.dialog_title")}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title me-auto">${t("move_to.dialog_title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="move-to-form">
|
||||
<div class="modal-body">
|
||||
|
||||
@@ -23,17 +23,16 @@ const TPL = `
|
||||
<div class="modal-dialog" style="max-width: 500px;" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("note_type_chooser.modal_title")}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t("note_type_chooser.modal_title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${t("note_type_chooser.modal_body")}
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="note-type-dropdown-trigger" type="button" style="display: none;" data-toggle="dropdown"></button>
|
||||
<div class="dropdown" style="display: flex;">
|
||||
<button class="note-type-dropdown-trigger" type="button" style="display: none;"
|
||||
data-bs-toggle="dropdown" data-bs-display="static">
|
||||
</button>
|
||||
|
||||
<div class="note-type-dropdown dropdown-menu"></div>
|
||||
</div>
|
||||
@@ -53,13 +52,14 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
|
||||
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
|
||||
this.$noteTypeDropdownTrigger = this.$widget.find(".note-type-dropdown-trigger");
|
||||
this.$noteTypeDropdownTrigger.dropdown();
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find(".note-type-dropdown-trigger"));
|
||||
|
||||
this.$widget.on("hidden.bs.modal", () => {
|
||||
if (this.resolve) {
|
||||
this.resolve({success: false});
|
||||
this.resolve({ success: false });
|
||||
}
|
||||
|
||||
if (this.$originalFocused) {
|
||||
@@ -94,7 +94,7 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async chooseNoteTypeEvent({callback}) {
|
||||
async chooseNoteTypeEvent({ callback }) {
|
||||
this.$originalFocused = $(':focus');
|
||||
|
||||
const noteTypes = await noteTypesService.getNoteTypeItems();
|
||||
@@ -116,11 +116,11 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
this.$noteTypeDropdownTrigger.dropdown('show');
|
||||
this.dropdown.show();
|
||||
|
||||
this.$originalDialog = glob.activeDialog;
|
||||
glob.activeDialog = this.$widget;
|
||||
this.$widget.modal();
|
||||
glob.activeDialog = this.modal;
|
||||
this.modal.show();
|
||||
|
||||
this.$noteTypeDropdown.find(".dropdown-item:first").focus();
|
||||
|
||||
@@ -139,6 +139,6 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
});
|
||||
this.resolve = null;
|
||||
|
||||
this.$widget.modal("hide");
|
||||
this.modal.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,8 @@ const TPL = `
|
||||
<div class="modal-dialog modal-md" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("password_not_set.title")}</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t("password_not_set.title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
${t("password_not_set.body1")}
|
||||
@@ -26,8 +23,10 @@ const TPL = `
|
||||
export default class PasswordNoteSetDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$openPasswordOptionsButton = this.$widget.find(".open-password-options-button");
|
||||
this.$openPasswordOptionsButton.on("click", () => {
|
||||
this.modal.hide();
|
||||
this.triggerCommand("showOptions", { section: '_optionsPassword' });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,13 +8,10 @@ const TPL = `
|
||||
<div class="modal-content">
|
||||
<form class="prompt-dialog-form">
|
||||
<div class="modal-header">
|
||||
<h5 class="prompt-title modal-title mr-auto">${t("prompt.title")}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5 class="prompt-title modal-title">${t("prompt.title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body"></div>
|
||||
<div class="modal-footer">
|
||||
<button class="prompt-dialog-ok-button btn btn-primary btn-sm">${t("prompt.ok")}</button>
|
||||
</div>
|
||||
@@ -33,6 +30,7 @@ export default class PromptDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
this.$dialogBody = this.$widget.find(".modal-body");
|
||||
this.$form = this.$widget.find(".prompt-dialog-form");
|
||||
this.$question = null;
|
||||
@@ -61,7 +59,7 @@ export default class PromptDialog extends BasicWidget {
|
||||
e.preventDefault();
|
||||
this.resolve(this.$answer.val());
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,14 @@ const TPL = `
|
||||
<div class="modal-dialog modal-md" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("protected_session_password.modal_title")}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t("protected_session_password.modal_title")}</h5>
|
||||
<button class="help-button" type="button" data-help-page="protected-notes.html" title="${t("protected_session_password.help_title")}">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="${t("protected_session_password.close_label")}" style="margin-left: 0;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("protected_session_password.close_label")}"></button>
|
||||
</div>
|
||||
<form class="protected-session-password-form">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
${t("protected_session_password.form_label")}
|
||||
<input class="form-control protected-session-password" type="password">
|
||||
</label>
|
||||
</div>
|
||||
<label class="col-form-label">${t("protected_session_password.form_label")}</label>
|
||||
<input class="form-control protected-session-password" type="password">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary">${t("protected_session_password.start_button")}</button>
|
||||
@@ -36,6 +28,8 @@ const TPL = `
|
||||
export default class ProtectedSessionPasswordDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
|
||||
this.$passwordForm = this.$widget.find(".protected-session-password-form");
|
||||
this.$passwordInput = this.$widget.find(".protected-session-password");
|
||||
this.$passwordForm.on('submit', () => {
|
||||
@@ -55,6 +49,6 @@ export default class ProtectedSessionPasswordDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
closeProtectedSessionPasswordDialogEvent() {
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,9 @@ const TPL = `
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t('recent_changes.title')}</h5>
|
||||
|
||||
<button class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px">
|
||||
${t('recent_changes.erase_notes_button')}</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title flex-grow-1">${t('recent_changes.title')}</h5>
|
||||
<button class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px">${t('recent_changes.erase_notes_button')}</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="recent-changes-content"></div>
|
||||
@@ -34,6 +29,8 @@ const TPL = `
|
||||
export default class RecentChangesDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
|
||||
this.$content = this.$widget.find(".recent-changes-content");
|
||||
this.$eraseDeletedNotesNow = this.$widget.find(".erase-deleted-notes-now-button");
|
||||
this.$eraseDeletedNotesNow.on("click", () => {
|
||||
@@ -45,7 +42,7 @@ export default class RecentChangesDialog extends BasicWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async showRecentChangesEvent({ancestorNoteId}) {
|
||||
async showRecentChangesEvent({ ancestorNoteId }) {
|
||||
this.ancestorNoteId = ancestorNoteId;
|
||||
|
||||
await this.refresh();
|
||||
@@ -93,7 +90,7 @@ export default class RecentChangesDialog extends BasicWidget {
|
||||
if (await dialogService.confirm(text)) {
|
||||
await server.put(`notes/${change.noteId}/undelete`);
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import openService from "../../services/open.js";
|
||||
import protectedSessionHolder from "../../services/protected_session_holder.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
import options from "../../services/options.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="revisions-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
@@ -40,21 +41,18 @@ const TPL = `
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("revisions.note_revisions")}</h5>
|
||||
|
||||
<h5 class="modal-title flex-grow-1">${t("revisions.note_revisions")}</h5>
|
||||
<button class="revisions-erase-all-revisions-button btn btn-sm"
|
||||
title="${t("revisions.delete_all_revisions")}"
|
||||
style="padding: 0 10px 0 10px;" type="button">${t("revisions.delete_all_button")}</button>
|
||||
|
||||
<button class="help-button" type="button" data-help-page="note-revisions.html" title="${t("revisions.help_title")}">?</button>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" style="display: flex; height: 80vh;">
|
||||
<div class="dropdown">
|
||||
<button class="revision-list-dropdown" type="button" style="display: none;" data-toggle="dropdown"></button>
|
||||
<button class="revision-list-dropdown" type="button" style="display: none;"
|
||||
data-bs-toggle="dropdown" data-bs-display="static">
|
||||
</button>
|
||||
|
||||
<div class="revision-list dropdown-menu" style="position: static; height: 100%; overflow: auto;"></div>
|
||||
</div>
|
||||
@@ -69,6 +67,11 @@ const TPL = `
|
||||
<div class="revision-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer py-0">
|
||||
<span class="revisions-snapshot-interval flex-grow-1 my-0 py-0"></span>
|
||||
<span class="maximum-revisions-for-current-note flex-grow-1 my-0 py-0"></span>
|
||||
<button class="revision-settings-button icon-action bx bx-cog my-0 py-0" title="${t("revisions.settings")}"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -84,22 +87,33 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
|
||||
this.$list = this.$widget.find(".revision-list");
|
||||
this.$listDropdown = this.$widget.find(".revision-list-dropdown");
|
||||
this.listDropdown = bootstrap.Dropdown.getOrCreateInstance(this.$listDropdown);
|
||||
this.$content = this.$widget.find(".revision-content");
|
||||
this.$title = this.$widget.find(".revision-title");
|
||||
this.$titleButtons = this.$widget.find(".revision-title-buttons");
|
||||
this.$eraseAllRevisionsButton = this.$widget.find(".revisions-erase-all-revisions-button");
|
||||
|
||||
this.$listDropdown.dropdown();
|
||||
this.$snapshotInterval = this.$widget.find(".revisions-snapshot-interval");
|
||||
this.$maximumRevisions = this.$widget.find(".maximum-revisions-for-current-note");
|
||||
this.$revisionSettingsButton = this.$widget.find(".revision-settings-button")
|
||||
this.listDropdown.show();
|
||||
|
||||
this.$listDropdown.parent().on('hide.bs.dropdown', e => {
|
||||
// prevent closing dropdown by clicking outside
|
||||
if (e.clickEvent) {
|
||||
e.preventDefault();
|
||||
}
|
||||
// Prevent closing dropdown by pressing ESC and clicking outside
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
// Close the revision dialog when revision element is focused and ESC is pressed
|
||||
if (e.key === 'Escape' ||
|
||||
e.target.classList.contains(['dropdown-item', 'active'])) {
|
||||
this.modal.hide();
|
||||
}
|
||||
}, true)
|
||||
|
||||
this.$widget.on('shown.bs.modal', () => {
|
||||
this.$list.find(`[data-revision-id="${this.revisionId}"]`)
|
||||
.trigger('focus');
|
||||
@@ -111,17 +125,12 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
if (await dialogService.confirm(text)) {
|
||||
await server.remove(`notes/${this.note.noteId}/revisions`);
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
toastService.showMessage(t("revisions.revisions_deleted"));
|
||||
}
|
||||
});
|
||||
|
||||
this.$list.on('click', '.dropdown-item', e => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$list.on('focus', '.dropdown-item', e => {
|
||||
this.$list.find('.dropdown-item').each((i, el) => {
|
||||
$(el).toggleClass('active', el === e.target);
|
||||
@@ -129,9 +138,13 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
|
||||
this.setContentPane();
|
||||
});
|
||||
|
||||
this.$revisionSettingsButton.on('click', async () => {
|
||||
appContext.tabManager.openContextWithNote('_optionsOther', { activate: true });
|
||||
});
|
||||
}
|
||||
|
||||
async showRevisionsEvent({noteId = appContext.tabManager.getActiveContextNoteId()}) {
|
||||
async showRevisionsEvent({ noteId = appContext.tabManager.getActiveContextNoteId() }) {
|
||||
utils.openDialog(this.$widget);
|
||||
|
||||
await this.loadRevisions(noteId);
|
||||
@@ -154,7 +167,7 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
);
|
||||
}
|
||||
|
||||
this.$listDropdown.dropdown('show');
|
||||
this.listDropdown.show();
|
||||
|
||||
if (this.revisionItems.length > 0) {
|
||||
if (!this.revisionId) {
|
||||
@@ -166,6 +179,17 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
this.$eraseAllRevisionsButton.toggle(this.revisionItems.length > 0);
|
||||
|
||||
// Show the footer of the revisions dialog
|
||||
this.$snapshotInterval.text(t("revisions.snapshot_interval", { seconds: options.getInt('revisionSnapshotTimeInterval') }))
|
||||
let revisionsNumberLimit = parseInt(this.note.getLabelValue("versioningLimit") ?? "");
|
||||
if (!Number.isInteger(revisionsNumberLimit)) {
|
||||
revisionsNumberLimit = parseInt(options.getInt('revisionSnapshotNumberLimit'));
|
||||
}
|
||||
if (revisionsNumberLimit === -1) {
|
||||
revisionsNumberLimit = "∞"
|
||||
}
|
||||
this.$maximumRevisions.text(t("revisions.maximum_revisions", { number: revisionsNumberLimit }))
|
||||
}
|
||||
|
||||
async setContentPane() {
|
||||
@@ -191,7 +215,7 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
if (await dialogService.confirm(text)) {
|
||||
await server.post(`revisions/${revisionItem.revisionId}/restore`);
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
toastService.showMessage(t("revisions.revision_restored"));
|
||||
}
|
||||
@@ -241,17 +265,25 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
if (this.$content.find('span.math-tex').length > 0) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
||||
|
||||
renderMathInElement(this.$content[0], {trust: true});
|
||||
renderMathInElement(this.$content[0], { trust: true });
|
||||
}
|
||||
} else if (revisionItem.type === 'code') {
|
||||
this.$content.html($("<pre>").text(fullRevision.content));
|
||||
} else if (revisionItem.type === 'image') {
|
||||
this.$content.html($("<img>")
|
||||
// the reason why we put this inline as base64 is that we do not want to let user copy this
|
||||
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
|
||||
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
|
||||
.css("max-width", "100%")
|
||||
.css("max-height", "100%"));
|
||||
if (fullRevision.mime === "image/svg+xml") {
|
||||
let encodedSVG = encodeURIComponent(fullRevision.content); //Base64 of other format images may be embedded in svg
|
||||
this.$content.html($("<img>")
|
||||
.attr("src", `data:${fullRevision.mime};utf8,${encodedSVG}`)
|
||||
.css("max-width", "100%")
|
||||
.css("max-height", "100%"));
|
||||
} else {
|
||||
this.$content.html($("<img>")
|
||||
// the reason why we put this inline as base64 is that we do not want to let user copy this
|
||||
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
|
||||
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
|
||||
.css("max-width", "100%")
|
||||
.css("max-height", "100%"));
|
||||
}
|
||||
} else if (revisionItem.type === 'file') {
|
||||
const $table = $("<table cellpadding='10'>")
|
||||
.append($("<tr>").append(
|
||||
@@ -274,7 +306,7 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
this.$content.html($table);
|
||||
} else if ([ "canvas", "mindMap" ].includes(revisionItem.type)) {
|
||||
} else if (["canvas", "mindMap"].includes(revisionItem.type)) {
|
||||
const encodedTitle = encodeURIComponent(revisionItem.title);
|
||||
|
||||
this.$content.html($("<img>")
|
||||
|
||||
@@ -7,61 +7,45 @@ const TPL = `<div class="sort-child-notes-dialog modal mx-auto" tabindex="-1" ro
|
||||
<div class="modal-dialog modal-lg" style="max-width: 500px" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">${t("sort_child_notes.sort_children_by")}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">${t("sort_child_notes.sort_children_by")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="sort-child-notes-form">
|
||||
<div class="modal-body">
|
||||
<h5>${t("sort_child_notes.sorting_criteria")}</h5>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="sort-by" value="title" checked>
|
||||
${t("sort_child_notes.title")}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="sort-by" value="title" checked>
|
||||
<label class="form-check-label">${t("sort_child_notes.title")}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="sort-by" value="dateCreated">
|
||||
${t("sort_child_notes.date_created")}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="sort-by" value="dateCreated">
|
||||
<label class="form-check-label">${t("sort_child_notes.date_created")}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="sort-by" value="dateModified">
|
||||
${t("sort_child_notes.date_modified")}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="sort-by" value="dateModified">
|
||||
<label class="form-check-label">${t("sort_child_notes.date_modified")}</label>
|
||||
</div>
|
||||
<br/>
|
||||
<h5>${t("sort_child_notes.sorting_direction")}</h5>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="sort-direction" value="asc" checked>
|
||||
${t("sort_child_notes.ascending")}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="sort-direction" value="asc" checked>
|
||||
<label class="form-check-label">${t("sort_child_notes.ascending")}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="radio" name="sort-direction" value="desc">
|
||||
${t("sort_child_notes.descending")}
|
||||
</label>
|
||||
<input class="form-check-input" type="radio" name="sort-direction" value="desc">
|
||||
<label class="form-check-label">${t("sort_child_notes.descending")}</label>
|
||||
</div>
|
||||
<br />
|
||||
<h5>${t("sort_child_notes.folders")}</h5>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="checkbox" name="sort-folders-first" value="1">
|
||||
${t("sort_child_notes.sort_folders_at_top")}
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="sort-folders-first" value="1">
|
||||
<label class="form-check-label">${t("sort_child_notes.sort_folders_at_top")}</label>
|
||||
</div>
|
||||
<br />
|
||||
<h5>${t("sort_child_notes.natural_sort")}</h5>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="checkbox" name="sort-natural" value="1">
|
||||
${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" name="sort-natural" value="1">
|
||||
<label class="form-check-label">${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}</label>
|
||||
</div>
|
||||
<br />
|
||||
<div class="form-check">
|
||||
|
||||
@@ -11,9 +11,7 @@ const TPL = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${t("upload_attachments.upload_attachments_to_note")}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="upload-attachment-form">
|
||||
<div class="modal-body">
|
||||
@@ -26,8 +24,8 @@ const TPL = `
|
||||
<div class="form-group">
|
||||
<strong>${t("upload_attachments.options")}:</strong>
|
||||
<div class="checkbox">
|
||||
<label data-toggle="tooltip" title="${t("upload_attachments.tooltip")}">
|
||||
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t("upload_attachments.shrink_images")}</span>
|
||||
<label data-bs-toggle="tooltip" title="${t("upload_attachments.tooltip")}">
|
||||
<input class="shrink-images-checkbox form-check-input" value="1" type="checkbox" checked> <span>${t("upload_attachments.shrink_images")}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,6 +47,8 @@ export default class UploadAttachmentsDialog extends BasicWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
|
||||
|
||||
this.$form = this.$widget.find(".upload-attachment-form");
|
||||
this.$noteTitle = this.$widget.find(".upload-attachment-note-title");
|
||||
this.$fileUploadInput = this.$widget.find(".upload-attachment-file-upload-input");
|
||||
@@ -71,12 +71,12 @@ export default class UploadAttachmentsDialog extends BasicWidget {
|
||||
}
|
||||
});
|
||||
|
||||
this.$widget.find('[data-toggle="tooltip"]').tooltip({
|
||||
bootstrap.Tooltip.getOrCreateInstance(this.$widget.find('[data-bs-toggle="tooltip"]'), {
|
||||
html: true
|
||||
});
|
||||
}
|
||||
|
||||
async showUploadAttachmentsDialogEvent({noteId}) {
|
||||
async showUploadAttachmentsDialogEvent({ noteId }) {
|
||||
this.parentNoteId = noteId;
|
||||
|
||||
this.$fileUploadInput.val('').trigger('change'); // to trigger upload button disabling listener below
|
||||
@@ -96,7 +96,7 @@ export default class UploadAttachmentsDialog extends BasicWidget {
|
||||
shrinkImages: boolToString(this.$shrinkImagesCheckbox),
|
||||
};
|
||||
|
||||
this.$widget.modal('hide');
|
||||
this.modal.hide();
|
||||
|
||||
await importService.uploadFiles('attachments', parentNoteId, files, options);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const TPL = `
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle editability-button">
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle editability-button">
|
||||
<span class="editability-active-desc">${t("editability_select.auto")}</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
@@ -43,11 +43,13 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
|
||||
this.$editabilityActiveDesc = this.$widget.find(".editability-active-desc");
|
||||
|
||||
this.$widget.on('click', '.dropdown-item',
|
||||
async e => {
|
||||
this.$widget.find('.dropdown-toggle').dropdown('toggle');
|
||||
this.dropdown.toggle();
|
||||
|
||||
const editability = $(e.target).closest("[data-editability]").attr("data-editability");
|
||||
|
||||
@@ -85,7 +87,7 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget {
|
||||
this.$editabilityActiveDesc.text(labels[editability]);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.getAttributeRows().find(attr => attr.noteId === this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* https://github.com/antoniotejada/Trilium-FindWidget
|
||||
*/
|
||||
|
||||
import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import FindInText from "./find_in_text.js";
|
||||
import FindInCode from "./find_in_code.js";
|
||||
@@ -47,24 +48,18 @@ const TPL = `
|
||||
<div class="find-widget-box">
|
||||
<div class="input-group find-widget-search-term-input-group">
|
||||
<input type="text" class="form-control find-widget-search-term-input">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button>
|
||||
<button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button>
|
||||
<button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label tabIndex="-1" class="form-check-label">
|
||||
<input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">
|
||||
case sensitive
|
||||
</label>
|
||||
<input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">
|
||||
<label tabIndex="-1" class="form-check-label">${t('find.case_sensitive')}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label tabIndex="-1" class="form-check-label">
|
||||
<input type="checkbox" class="form-check-input find-widget-match-words-checkbox">
|
||||
match words
|
||||
</label>
|
||||
<input type="checkbox" class="form-check-input find-widget-match-words-checkbox">
|
||||
<label tabIndex="-1" class="form-check-label">${t('find.match_words')}</label>
|
||||
</div>
|
||||
|
||||
<div class="find-widget-found-wrapper">
|
||||
@@ -279,7 +274,7 @@ export default class FindWidget extends NoteContextAwareWidget {
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
this.$totalFound.text("?")
|
||||
}
|
||||
this.$totalFound.text("?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="floating-buttons no-print">
|
||||
@@ -7,7 +8,7 @@ const TPL = `
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floating-buttons-children {
|
||||
.floating-buttons-children,.show-floating-buttons {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
@@ -42,12 +43,44 @@ const TPL = `
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.floating-buttons.temporarily-hidden {
|
||||
.floating-buttons .floating-buttons-children.temporarily-hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="floating-buttons-children"></div>
|
||||
|
||||
<!-- Show button that displays floating button after click on close button -->
|
||||
<div class="show-floating-buttons">
|
||||
<style>
|
||||
.floating-buttons-children.temporarily-hidden+.show-floating-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.floating-buttons-children:not(.temporarily-hidden)+.show-floating-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show-floating-buttons {
|
||||
/* display: none;*/
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
.show-floating-buttons-button {
|
||||
border: 1px solid transparent;
|
||||
color: var(--button-text-color);
|
||||
padding: 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.show-floating-buttons-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" class="show-floating-buttons-button btn bx bx-chevrons-left"
|
||||
title="${t('show_floating_buttons_button.button_title')}"></button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class FloatingButtons extends NoteContextAwareWidget {
|
||||
@@ -62,10 +95,11 @@ export default class FloatingButtons extends NoteContextAwareWidget {
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.toggle(true);
|
||||
this.$widget.find(".show-floating-buttons-button").on('click', () => this.toggle(true));
|
||||
}
|
||||
|
||||
toggle(show) {
|
||||
this.$widget.toggleClass("temporarily-hidden", !show);
|
||||
this.$widget.find(".floating-buttons-children").toggleClass("temporarily-hidden", !show);
|
||||
}
|
||||
|
||||
hideFloatingButtonsCommand() {
|
||||
|
||||
@@ -27,7 +27,7 @@ const TPL = `
|
||||
</style>
|
||||
|
||||
<button type="button"
|
||||
class="close-floating-buttons-button btn bx bx-x"
|
||||
class="close-floating-buttons-button btn bx bx-chevrons-right"
|
||||
title="${t('hide_floating_buttons_button.button_title')}"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
* - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries
|
||||
*/
|
||||
|
||||
import { t } from "../services/i18n.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import RightPanelWidget from "./right_panel_widget.js";
|
||||
import options from "../services/options.js";
|
||||
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
|
||||
const TPL = `<div class="highlights-list-widget">
|
||||
<style>
|
||||
@@ -28,7 +30,6 @@ const TPL = `<div class="highlights-list-widget">
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
text-align: justify;
|
||||
text-justify: distribute;
|
||||
word-wrap: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
@@ -43,16 +44,16 @@ const TPL = `<div class="highlights-list-widget">
|
||||
|
||||
export default class HighlightsListWidget extends RightPanelWidget {
|
||||
get widgetTitle() {
|
||||
return "Highlights List";
|
||||
return t("highlights_list_2.title");
|
||||
}
|
||||
|
||||
get widgetButtons() {
|
||||
return [
|
||||
new OnClickButtonWidget()
|
||||
.icon("bx-slider")
|
||||
.title("Options")
|
||||
.icon("bx-cog")
|
||||
.title(t("highlights_list_2.options"))
|
||||
.titlePlacement("left")
|
||||
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true}))
|
||||
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', { activate: true }))
|
||||
.class("icon-action"),
|
||||
new OnClickButtonWidget()
|
||||
.icon("bx-x")
|
||||
@@ -97,8 +98,8 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
let $highlightsList = "", hlLiCount = -1;
|
||||
// Check for type text unconditionally in case alwaysShowWidget is set
|
||||
if (this.note.type === 'text') {
|
||||
const {content} = await note.getNoteComplement();
|
||||
({$highlightsList, hlLiCount} = this.getHighlightList(content, optionsHighlightsList));
|
||||
const { content } = await note.getNoteComplement();
|
||||
({ $highlightsList, hlLiCount } = await this.getHighlightList(content, optionsHighlightsList));
|
||||
}
|
||||
this.$highlightsList.empty().append($highlightsList);
|
||||
if (hlLiCount > 0) {
|
||||
@@ -112,7 +113,79 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
this.triggerCommand("reEvaluateRightPaneVisibility");
|
||||
}
|
||||
|
||||
getHighlightList(content, optionsHighlightsList) {
|
||||
extractOuterTag(htmlStr) {
|
||||
if (htmlStr === null) {
|
||||
return null
|
||||
}
|
||||
// Regular expressions that match only the outermost tag
|
||||
const regex = /^<([a-zA-Z]+)([^>]*)>/;
|
||||
const match = htmlStr.match(regex);
|
||||
if (match) {
|
||||
const tagName = match[1].toLowerCase(); // Extract tag name
|
||||
const attributes = match[2].trim(); // Extract label attributes
|
||||
return { tagName, attributes };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
areOuterTagsConsistent(str1, str2) {
|
||||
const tag1 = this.extractOuterTag(str1);
|
||||
const tag2 = this.extractOuterTag(str2);
|
||||
// If one of them has no label, returns false
|
||||
if (!tag1 || !tag2) {
|
||||
return false;
|
||||
}
|
||||
// Compare tag names and attributes to see if they are the same
|
||||
return tag1.tagName === tag2.tagName && tag1.attributes === tag2.attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendering formulas in strings using katex
|
||||
*
|
||||
* @param {string} html Note's html content
|
||||
* @returns {string} The HTML content with mathematical formulas rendered by KaTeX.
|
||||
*/
|
||||
async replaceMathTextWithKatax(html) {
|
||||
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
|
||||
var matches = [...html.matchAll(mathTextRegex)];
|
||||
let modifiedText = html;
|
||||
|
||||
if (matches.length > 0) {
|
||||
// Process all matches asynchronously
|
||||
for (const match of matches) {
|
||||
let latexCode = match[1];
|
||||
let rendered;
|
||||
|
||||
try {
|
||||
rendered = katex.renderToString(latexCode, {
|
||||
throwOnError: false
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof ReferenceError && e.message.includes('katex is not defined')) {
|
||||
// Load KaTeX if it is not already loaded
|
||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
||||
try {
|
||||
rendered = katex.renderToString(latexCode, {
|
||||
throwOnError: false
|
||||
});
|
||||
} catch (renderError) {
|
||||
console.error("KaTeX rendering error after loading library:", renderError);
|
||||
rendered = match[0]; // Fall back to original if error persists
|
||||
}
|
||||
} else {
|
||||
console.error("KaTeX rendering error:", e);
|
||||
rendered = match[0]; // Fall back to original on error
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the matched formula in the modified text
|
||||
modifiedText = modifiedText.replace(match[0], rendered);
|
||||
}
|
||||
}
|
||||
return modifiedText;
|
||||
}
|
||||
|
||||
async getHighlightList(content, optionsHighlightsList) {
|
||||
// matches a span containing background-color
|
||||
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
|
||||
// matches a span containing color
|
||||
@@ -152,6 +225,10 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
const combinedRegex = new RegExp(combinedRegexStr, 'gi');
|
||||
const $highlightsList = $("<ol>");
|
||||
let prevEndIndex = -1, hlLiCount = 0;
|
||||
let prevSubHtml = null;
|
||||
// Used to determine if a string is only a formula
|
||||
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;
|
||||
|
||||
for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
|
||||
const subHtml = match[0];
|
||||
const startIndex = match.index;
|
||||
@@ -166,11 +243,19 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
const hasText = $(subHtml).text().trim();
|
||||
|
||||
if (hasText) {
|
||||
$highlightsList.append(
|
||||
$('<li>')
|
||||
.html(subHtml)
|
||||
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
|
||||
);
|
||||
const substring = content.substring(prevEndIndex, startIndex);
|
||||
//If the two elements have the same style and there are only formulas in between, append the formulas and the current element to the end of the previous element.
|
||||
if (this.areOuterTagsConsistent(prevSubHtml, subHtml) && onlyMathRegex.test(substring)) {
|
||||
const $lastLi = $highlightsList.children('li').last();
|
||||
$lastLi.append(await this.replaceMathTextWithKatax(substring));
|
||||
$lastLi.append(subHtml);
|
||||
} else {
|
||||
$highlightsList.append(
|
||||
$('<li>')
|
||||
.html(subHtml)
|
||||
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
|
||||
);
|
||||
}
|
||||
|
||||
hlLiCount++;
|
||||
} else {
|
||||
@@ -179,6 +264,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
}
|
||||
}
|
||||
prevEndIndex = endIndex;
|
||||
prevSubHtml = subHtml;
|
||||
}
|
||||
return {
|
||||
$highlightsList,
|
||||
@@ -232,9 +318,17 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
||||
this.noteContext.viewScope.highlightsListTemporarilyHidden = true;
|
||||
await this.refresh();
|
||||
this.triggerCommand('reEvaluateRightPaneVisibility');
|
||||
appContext.triggerEvent("reEvaluateHighlightsListWidgetVisibility", { noteId: this.noteId });
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
async showHighlightsListWidgetEvent({ noteId }) {
|
||||
if (this.noteId === noteId) {
|
||||
await this.refresh();
|
||||
this.triggerCommand('reEvaluateRightPaneVisibility');
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
await this.refresh();
|
||||
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import libraryLoader from "../services/library_loader.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import server from "../services/server.js";
|
||||
@@ -27,7 +28,7 @@ const TPL = `<div class="mermaid-widget">
|
||||
</style>
|
||||
|
||||
<div class="mermaid-error alert alert-warning">
|
||||
<p><strong>The diagram could not be displayed. See <a href="https://mermaid-js.github.io/mermaid/#/flowchart?id=graph">help and examples</a>.</strong></p>
|
||||
<p><strong>${t('mermaid.diagram_error')}</strong></p>
|
||||
<p class="error-content"></p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import SpacedUpdate from "../services/spaced_update.js";
|
||||
@@ -155,7 +156,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
getTypeWidget() {
|
||||
if (!this.typeWidgets[this.type]) {
|
||||
throw new Error(`Could not find typeWidget for type '${this.type}'`);
|
||||
throw new Error(t(`note_detail.could_not_find_typewidget`, { type: this.type }));
|
||||
}
|
||||
|
||||
return this.typeWidgets[this.type];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import server from "../services/server.js";
|
||||
@@ -66,12 +67,12 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button class="btn dropdown-toggle note-icon" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Change note icon"></button>
|
||||
<button class="btn dropdown-toggle note-icon" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="${t("note_icon.change_note_icon")}"></button>
|
||||
<div class="dropdown-menu" aria-labelledby="note-path-list-button" style="width: 610px;">
|
||||
<div class="filter-row">
|
||||
<span>Category:</span> <select name="icon-category" class="form-control"></select>
|
||||
<span>${t("note_icon.category")}</span> <select name="icon-category" class="form-control"></select>
|
||||
|
||||
<span>Search:</span> <input type="text" name="icon-search" class="form-control" />
|
||||
<span>${t("note_icon.search")}</span> <input type="text" name="icon-search" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="icon-list"></div>
|
||||
@@ -150,7 +151,7 @@ export default class NoteIconWidget extends NoteContextAwareWidget {
|
||||
this.$iconList.append(
|
||||
$(`<div style="text-align: center">`)
|
||||
.append(
|
||||
$('<button class="btn btn-sm">Reset to default icon</button>')
|
||||
$(`<button class="btn btn-sm">${t("note_icon.reset-default")}</button>`)
|
||||
.on('click', () => this.getIconLabels()
|
||||
.forEach(label => attributeService.removeAttributeById(this.noteId, label.attributeId))
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import appContext from "../components/app_context.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import linkContextMenuService from "../menus/link_context_menu.js";
|
||||
import utils from "../services/utils.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
|
||||
const esc = utils.escapeHtml;
|
||||
|
||||
@@ -30,8 +31,8 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
|
||||
</style>
|
||||
|
||||
<div class="btn-group btn-group-sm map-type-switcher" role="group">
|
||||
<button type="button" class="btn bx bx-network-chart" title="Link Map" data-type="link"></button>
|
||||
<button type="button" class="btn bx bx-sitemap" title="Tree map" data-type="tree"></button>
|
||||
<button type="button" class="btn bx bx-network-chart" title="${t("note-map.button-link-map")}" data-type="link"></button>
|
||||
<button type="button" class="btn bx bx-sitemap" title="${t("note-map.button-tree-map")}" data-type="tree"></button>
|
||||
</div>
|
||||
|
||||
<div class="style-resolver"></div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import server from "../services/server.js";
|
||||
@@ -27,7 +28,7 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<input autocomplete="off" value="" placeholder="type note's title here..." class="note-title" tabindex="100">
|
||||
<input autocomplete="off" value="" placeholder="${t('note_title.placeholder')}" class="note-title" tabindex="100">
|
||||
</div>`;
|
||||
|
||||
export default class NoteTitleWidget extends NoteContextAwareWidget {
|
||||
|
||||
@@ -18,6 +18,7 @@ import options from "../services/options.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import shortcutService from "../services/shortcuts.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="tree-wrapper">
|
||||
@@ -99,15 +100,15 @@ const TPL = `
|
||||
|
||||
<div class="tree-actions">
|
||||
<button class="tree-floating-button bx bx-layer-minus collapse-tree-button"
|
||||
title="Collapse note tree"
|
||||
title="${t("note_tree.collapse-title")}"
|
||||
data-trigger-command="collapseTree"></button>
|
||||
|
||||
<button class="tree-floating-button bx bx-crosshair scroll-to-active-note-button"
|
||||
title="Scroll to active note"
|
||||
title="${t("note_tree.scroll-active-title")}"
|
||||
data-trigger-command="scrollToActiveNote"></button>
|
||||
|
||||
<button class="tree-floating-button bx bx-cog tree-settings-button"
|
||||
title="Tree settings"></button>
|
||||
<button class="tree-floating-button bx bxs-tree tree-settings-button"
|
||||
title="${t("note_tree.tree-settings-title")}"></button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -116,22 +117,22 @@ const TPL = `
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input hide-archived-notes" type="checkbox" value="">
|
||||
|
||||
Hide archived notes
|
||||
${t("note_tree.hide-archived-notes")}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input auto-collapse-note-tree" type="checkbox" value="">
|
||||
|
||||
Automatically collapse notes
|
||||
${t("note_tree.automatically-collapse-notes")}
|
||||
<span class="bx bx-info-circle"
|
||||
title="Notes will be collapsed after period of inactivity to declutter the tree."></span>
|
||||
title="${t("note_tree.automatically-collapse-notes-title")}"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<button class="btn btn-sm btn-primary save-tree-settings-button" type="submit">Save & apply changes</button>
|
||||
<button class="btn btn-sm btn-primary save-tree-settings-button" type="submit">${t("note_tree.save-changes")}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -2,25 +2,26 @@ import server from '../services/server.js';
|
||||
import mimeTypesService from '../services/mime_types.js';
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import { t } from '../services/i18n.js';
|
||||
|
||||
const NOTE_TYPES = [
|
||||
{ type: "file", title: "File", selectable: false },
|
||||
{ type: "image", title: "Image", selectable: false },
|
||||
{ type: "search", title: "Saved Search", selectable: false },
|
||||
{ type: "noteMap", mime: '', title: "Note Map", selectable: false },
|
||||
{ type: "launcher", mime: '', title: "Launcher", selectable: false },
|
||||
{ type: "doc", mime: '', title: "Doc", selectable: false },
|
||||
{ type: "contentWidget", mime: '', title: "Widget", selectable: false },
|
||||
{ type: "file", title: t("note_types.file"), selectable: false },
|
||||
{ type: "image", title: t("note_types.image"), selectable: false },
|
||||
{ type: "search", title: t("note_types.saved-search"), selectable: false },
|
||||
{ type: "noteMap", mime: '', title: t("note_types.note-map"), selectable: false },
|
||||
{ type: "launcher", mime: '', title: t("note_types.launcher"), selectable: false },
|
||||
{ type: "doc", mime: '', title: t("note_types.doc"), selectable: false },
|
||||
{ type: "contentWidget", mime: '', title: t("note_types.widget"), selectable: false },
|
||||
|
||||
{ type: "text", mime: "text/html", title: "Text", selectable: true },
|
||||
{ type: "relationMap", mime: "application/json", title: "Relation Map", selectable: true },
|
||||
{ type: "mindMap", mime: "application/json", "title": "Mind Map", selectable: true },
|
||||
{ type: "render", mime: '', title: "Render Note", selectable: true },
|
||||
{ type: "canvas", mime: 'application/json', title: "Canvas", selectable: true },
|
||||
{ type: "mermaid", mime: 'text/mermaid', title: "Mermaid Diagram", selectable: true },
|
||||
{ type: "book", mime: '', title: "Book", selectable: true },
|
||||
{ type: "webView", mime: '', title: "Web View", selectable: true },
|
||||
{ type: "code", mime: 'text/plain', title: "Code", selectable: true }
|
||||
{ type: "text", mime: "text/html", title: t("note_types.text"), selectable: true },
|
||||
{ type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), selectable: true },
|
||||
{ type: "mindMap", mime: "application/json", "title": t("note_types.mind-map"), selectable: true },
|
||||
{ type: "render", mime: '', title: t("note_types.render-note"), selectable: true },
|
||||
{ type: "canvas", mime: 'application/json', title: t("note_types.canvas"), selectable: true },
|
||||
{ type: "mermaid", mime: 'text/mermaid', title: t("note_types.mermaid-diagram"), selectable: true },
|
||||
{ type: "book", mime: '', title: t("note_types.book"), selectable: true },
|
||||
{ type: "webView", mime: '', title: t("note_types.web-view"), selectable: true },
|
||||
{ type: "code", mime: 'text/plain', title: t("note_types.code"), selectable: true }
|
||||
];
|
||||
|
||||
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter(nt => !nt.selectable).map(nt => nt.type);
|
||||
@@ -34,7 +35,7 @@ const TPL = `
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle note-type-button">
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle note-type-button">
|
||||
<span class="note-type-desc"></span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
@@ -46,14 +47,15 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
|
||||
this.$widget.on('show.bs.dropdown', () => this.renderDropdown());
|
||||
|
||||
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
|
||||
this.$noteTypeButton = this.$widget.find(".note-type-button");
|
||||
this.$noteTypeDesc = this.$widget.find(".note-type-desc");
|
||||
|
||||
this.$widget.on('click', '.dropdown-item',
|
||||
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
|
||||
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
@@ -62,7 +64,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime));
|
||||
|
||||
this.$noteTypeButton.dropdown('hide');
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
/** the actual body is rendered lazily on note-type button click */
|
||||
@@ -91,7 +93,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
|
||||
.append('<span class="check">✓</span> ')
|
||||
.append($('<strong>').text(noteType.title));
|
||||
}
|
||||
|
||||
|
||||
if (this.note.type === noteType.type) {
|
||||
$typeLink.addClass("selected");
|
||||
}
|
||||
@@ -160,7 +162,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
|
||||
return await dialogService.confirm("It is not recommended to change note type when note content is not empty. Do you want to continue anyway?");
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.isNoteReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t } from "../services/i18n.js";
|
||||
import protectedSessionService from "../services/protected_session.js";
|
||||
import SwitchWidget from "./switch.js";
|
||||
|
||||
@@ -5,11 +6,11 @@ export default class ProtectedNoteSwitchWidget extends SwitchWidget {
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$switchOnName.text("Protect the note");
|
||||
this.$switchOnButton.attr("title", "Note is not protected, click to make it protected");
|
||||
this.$switchOnName.text(t("protect_note.toggle-on"));
|
||||
this.$switchOnButton.attr("title", t("protect_note.toggle-on-hint"));
|
||||
|
||||
this.$switchOffName.text("Unprotect the note");
|
||||
this.$switchOffButton.attr("title", "Note is protected, click to make it unprotected");
|
||||
this.$switchOffName.text(t("protect_note.toggle-off"));
|
||||
this.$switchOffButton.attr("title", t("protect_note.toggle-off-hint"));
|
||||
}
|
||||
|
||||
switchOn() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import froca from "../services/froca.js";
|
||||
import utils from "../services/utils.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import shortcutService from "../services/shortcuts.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="quick-search input-group input-group-sm">
|
||||
@@ -30,13 +31,12 @@ const TPL = `
|
||||
</style>
|
||||
|
||||
<div class="input-group-prepend">
|
||||
<button class="btn btn-outline-secondary search-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button class="btn btn-outline-secondary search-button" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="bx bx-search"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-left"></div>
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-sm search-string" placeholder="Quick search">
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-sm search-string" placeholder="${t("quick-search.placeholder")}">
|
||||
</div>`;
|
||||
|
||||
const MAX_DISPLAYED_NOTES = 15;
|
||||
@@ -44,21 +44,20 @@ const MAX_DISPLAYED_NOTES = 15;
|
||||
export default class QuickSearchWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
|
||||
|
||||
this.$searchString = this.$widget.find('.search-string');
|
||||
this.$dropdownMenu = this.$widget.find('.dropdown-menu');
|
||||
this.$dropdownToggle = this.$widget.find('.search-button');
|
||||
this.$dropdownToggle.dropdown();
|
||||
|
||||
this.$widget.find('.input-group-prepend').on('shown.bs.dropdown', () => this.search());
|
||||
|
||||
if(utils.isMobile()) {
|
||||
this.$searchString.keydown(e =>{
|
||||
if(e.which === 13) {
|
||||
if (utils.isMobile()) {
|
||||
this.$searchString.keydown(e => {
|
||||
if (e.which === 13) {
|
||||
if (this.$dropdownMenu.is(":visible")) {
|
||||
this.search(); // just update already visible dropdown
|
||||
} else {
|
||||
this.$dropdownToggle.dropdown('show');
|
||||
this.dropdown.show();
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -70,7 +69,7 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
if (this.$dropdownMenu.is(":visible")) {
|
||||
this.search(); // just update already visible dropdown
|
||||
} else {
|
||||
this.$dropdownToggle.dropdown('show');
|
||||
this.dropdown.show();
|
||||
}
|
||||
|
||||
this.$searchString.focus();
|
||||
@@ -81,7 +80,7 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
});
|
||||
|
||||
shortcutService.bindElShortcut(this.$searchString, 'esc', () => {
|
||||
this.$dropdownToggle.dropdown('hide');
|
||||
this.dropdown.hide();
|
||||
});
|
||||
|
||||
return this.$widget;
|
||||
@@ -91,25 +90,25 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
const searchString = this.$searchString.val().trim();
|
||||
|
||||
if (!searchString) {
|
||||
this.$dropdownToggle.dropdown("hide");
|
||||
this.dropdown.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.$dropdownMenu.empty();
|
||||
this.$dropdownMenu.append('<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span> Searching ...</span>');
|
||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`);
|
||||
|
||||
const {searchResultNoteIds, error} = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
|
||||
const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
|
||||
|
||||
if (error) {
|
||||
this.$searchString.tooltip({
|
||||
let tooltip = new bootstrap.Tooltip(this.$searchString, {
|
||||
trigger: 'manual',
|
||||
title: `Search error: ${error}`,
|
||||
placement: 'right'
|
||||
});
|
||||
|
||||
this.$searchString.tooltip("show");
|
||||
tooltip.show();
|
||||
|
||||
setTimeout(() => this.$searchString.tooltip("dispose"), 4000);
|
||||
setTimeout(() => tooltip.dispose(), 4000);
|
||||
}
|
||||
|
||||
const displayedNoteIds = searchResultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResultNoteIds.length));
|
||||
@@ -117,15 +116,15 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
this.$dropdownMenu.empty();
|
||||
|
||||
if (displayedNoteIds.length === 0) {
|
||||
this.$dropdownMenu.append('<span class="dropdown-item disabled">No results found</span>');
|
||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.no-results")}</span>`);
|
||||
}
|
||||
|
||||
for (const note of await froca.getNotes(displayedNoteIds)) {
|
||||
const $link = await linkService.createLink(note.noteId, {showNotePath: true});
|
||||
const $link = await linkService.createLink(note.noteId, { showNotePath: true });
|
||||
$link.addClass('dropdown-item');
|
||||
$link.attr("tabIndex", "0");
|
||||
$link.on('click', e => {
|
||||
this.$dropdownToggle.dropdown("hide");
|
||||
this.dropdown.hide();
|
||||
|
||||
if (!e.target || e.target.nodeName !== 'A') {
|
||||
// click on the link is handled by link handling, but we want the whole item clickable
|
||||
@@ -133,7 +132,7 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
}
|
||||
});
|
||||
shortcutService.bindElShortcut($link, 'return', () => {
|
||||
this.$dropdownToggle.dropdown("hide");
|
||||
this.dropdown.hide();
|
||||
|
||||
appContext.tabManager.getActiveContext().setNote(note.noteId);
|
||||
});
|
||||
@@ -142,11 +141,12 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
if (searchResultNoteIds.length > MAX_DISPLAYED_NOTES) {
|
||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled">... and ${searchResultNoteIds.length - MAX_DISPLAYED_NOTES} more results.</span>`);
|
||||
const numRemainingResults = (searchResultNoteIds.length - MAX_DISPLAYED_NOTES);
|
||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.more-results", { number: numRemainingResults })}</span>`);
|
||||
}
|
||||
|
||||
const $showInFullButton = $('<a class="dropdown-item" tabindex="0">')
|
||||
.append($('<button class="btn btn-sm">Show in full search</button>'));
|
||||
.append($(`<button class="btn btn-sm">${t("quick-search.show-in-full-search")}</button>`));
|
||||
|
||||
this.$dropdownMenu.append($showInFullButton);
|
||||
|
||||
@@ -156,11 +156,11 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
|
||||
shortcutService.bindElShortcut(this.$dropdownMenu.find('.dropdown-item:first'), 'up', () => this.$searchString.focus());
|
||||
|
||||
this.$dropdownToggle.dropdown('update');
|
||||
this.dropdown.update();
|
||||
}
|
||||
|
||||
async showInFullSearch() {
|
||||
this.$dropdownToggle.dropdown("hide");
|
||||
this.dropdown.hide();
|
||||
|
||||
await appContext.triggerCommand('searchNotes', {
|
||||
searchString: this.$searchString.val()
|
||||
|
||||
@@ -17,12 +17,15 @@ const TPL = `
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.basic-properties-widget > * {
|
||||
margin-right: 30px;
|
||||
.basic-properties-widget > * {
|
||||
margin-top: 9px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.basic-properties-widget > * > :last-child {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.note-type-container, .editability-select-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -18,7 +18,7 @@ const TPL = `
|
||||
<div style="display: flex; align-items: baseline">
|
||||
<span style="white-space: nowrap">${t("book_properties.view_type")}: </span>
|
||||
|
||||
<select class="view-type-select form-control form-control-sm">
|
||||
<select class="view-type-select form-select form-select-sm">
|
||||
<option value="grid">${t("book_properties.grid")}</option>
|
||||
<option value="list">${t("book_properties.list")}</option>
|
||||
</select>
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
|
||||
return {
|
||||
show: this.isEnabled(),
|
||||
title: t("note_map.title"),
|
||||
icon: 'bx bx-map-alt'
|
||||
icon: 'bx bxs-network-chart'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user