mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 03:16:11 +01:00
Compare commits
178 Commits
v0.90.5-be
...
nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e64cab2d4 | ||
|
|
9c8cf0bc09 | ||
|
|
bafc556b00 | ||
|
|
0ec3232c81 | ||
|
|
469c1ceb07 | ||
|
|
5e80f120c9 | ||
|
|
8faa26b663 | ||
|
|
201e2fcfdb | ||
|
|
5678487a16 | ||
|
|
6646e8c311 | ||
|
|
96fc4d3280 | ||
|
|
cb3ea3fb75 | ||
|
|
787a2d1957 | ||
|
|
89e1275dda | ||
|
|
ddc927d617 | ||
|
|
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 |
153
.github/workflows/main-docker-alpine.yml
vendored
153
.github/workflows/main-docker-alpine.yml
vendored
@@ -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
|
||||
209
.github/workflows/main-docker.yml
vendored
209
.github/workflows/main-docker.yml
vendored
@@ -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 }}
|
||||
|
||||
122
.github/workflows/nightly.yml
vendored
Normal file
122
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
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:
|
||||
|
||||
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: # find out this value by opening https://api.github.com/repos/<owner>/<repo>/releases in your browser and copy the full "upload_url" value including the {?name,label} part
|
||||
# release_id: # same as above (id can just be taken out the upload_url, it's used to find old releases)
|
||||
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: # find out this value by opening https://api.github.com/repos/<owner>/<repo>/releases in your browser and copy the full "upload_url" value including the {?name,label} part
|
||||
# release_id: # same as above (id can just be taken out the upload_url, it's used to find old releases)
|
||||
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: # find out this value by opening https://api.github.com/repos/<owner>/<repo>/releases in your browser and copy the full "upload_url" value including the {?name,label} part
|
||||
# release_id: # same as above (id can just be taken out the upload_url, it's used to find old releases)
|
||||
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
.vscode/i18n-ally-custom-framework.yml
vendored
2
.vscode/i18n-ally-custom-framework.yml
vendored
@@ -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)
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -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"
|
||||
|
||||
@@ -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,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,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,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)をご覧ください:
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -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,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):
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ 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
|
||||
chmod 755 $PKG_DIR/trilium.sh
|
||||
|
||||
@@ -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";
|
||||
|
||||
4
electron-main.ts
Normal file
4
electron-main.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { initializeTranslations } from "./src/services/i18n.js";
|
||||
|
||||
await initializeTranslations();
|
||||
await import("./electron.js")
|
||||
@@ -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');
|
||||
|
||||
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
|
||||
1727
package-lock.json
generated
1727
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
209
package.json
209
package.json
@@ -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.6-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.17",
|
||||
"compression": "1.7.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
"csurf": "1.11.0",
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "^2.1.0",
|
||||
"ejs": "^3.1.10",
|
||||
"electron-debug": "3.2.0",
|
||||
"electron-dl": "3.5.2",
|
||||
"electron-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.1",
|
||||
"i18next-fs-backend": "2.3.2",
|
||||
"i18next-http-backend": "2.6.1",
|
||||
"image-type": "4.1.0",
|
||||
"ini": "^4.1.3",
|
||||
"ini": "5.0.0",
|
||||
"is-animated": "2.0.2",
|
||||
"is-svg": "4.3.2",
|
||||
"jimp": "0.22.12",
|
||||
"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.2.0",
|
||||
"mime-types": "2.1.35",
|
||||
"mind-elixir": "^4.0.5",
|
||||
"mind-elixir": "4.1.1",
|
||||
"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.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.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.0",
|
||||
"jsdoc": "4.0.3",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"nodemon": "^3.1.4",
|
||||
"nodemon": "3.1.4",
|
||||
"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.2",
|
||||
"webpack": "5.94.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
|
||||
}
|
||||
|
||||
@@ -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
src/main.ts
Normal file
13
src/main.ts
Normal file
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/public/app/widgets/buttons/show_toc_widget_button.js
Normal file
50
src/public/app/widgets/buttons/show_toc_widget_button.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import OnClickButtonWidget from "./onclick_button.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
export default class ShowTocWidgetButton extends OnClickButtonWidget {
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& this.note
|
||||
&& this.note.type === 'text'
|
||||
&& this.noteContext.viewScope.viewMode === 'default';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-objects-horizontal-left")
|
||||
.title(t("show_toc_widget_button.show_toc"))
|
||||
.titlePlacement("bottom")
|
||||
.onClick(widget => {
|
||||
this.noteContext.viewScope.tocTemporarilyHidden = false;
|
||||
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
|
||||
this.toggleInt(false);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
|
||||
}
|
||||
async reEvaluateTocWidgetVisibilityEvent({ noteId }) {
|
||||
if (noteId === this.noteId) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
await this.refresh();
|
||||
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
|
||||
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'toc')
|
||||
&& attributeService.isAffecting(attr, this.note))) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
||||
|
||||
if (promotedDefAttrs.length === 0) {
|
||||
return {show: false};
|
||||
return { show: false };
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -167,7 +167,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
attributeValues = attributeValues.map(attribute => ({value: attribute}));
|
||||
attributeValues = attributeValues.map(attribute => ({ value: attribute }));
|
||||
|
||||
$input.autocomplete({
|
||||
appendTo: document.querySelector('body'),
|
||||
@@ -229,9 +229,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
.prop("title", t("promoted_attributes.open_external_link"))
|
||||
.on('click', () => window.open($input.val(), '_blank'));
|
||||
|
||||
$input.after($("<div>")
|
||||
.addClass("input-group-append")
|
||||
.append($openButton));
|
||||
$input.after($openButton);
|
||||
}
|
||||
else {
|
||||
ws.logError(t("promoted_attributes.unknown_label_type", { type: definitionAttr.labelType }));
|
||||
@@ -244,7 +242,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
|
||||
if (utils.isDesktop()) {
|
||||
// no need to wait for this
|
||||
noteAutocompleteService.initNoteAutocomplete($input, {allowCreatingNotes: true});
|
||||
noteAutocompleteService.initNoteAutocomplete($input, { allowCreatingNotes: true });
|
||||
|
||||
$input.on('autocomplete:noteselected', (event, suggestion, dataset) => {
|
||||
this.promotedAttributeChanged(event);
|
||||
@@ -257,7 +255,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
}
|
||||
else {
|
||||
ws.logError(t(`promoted_attributes.unknown_attribute_type`, {type: valueAttr.type}));
|
||||
ws.logError(t(`promoted_attributes.unknown_attribute_type`, { type: valueAttr.type }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -346,7 +344,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
this.$widget.find(".promoted-attribute-input:first").focus();
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
entitiesReloadedEvent({ loadResults }) {
|
||||
if (loadResults.getAttributeRows(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ const TPL = `
|
||||
</button>
|
||||
|
||||
<div class="dropdown" style="display: inline-block;">
|
||||
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="bx bxs-zap"></span>
|
||||
${t('search_definition.action')}
|
||||
</button>
|
||||
@@ -219,7 +219,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
});
|
||||
|
||||
this.$widget.on('click', '[data-action-add]', async event => {
|
||||
this.$widget.find('.action-add-toggle').dropdown('toggle');
|
||||
bootstrap.Dropdown.getOrCreateInstance(this.$widget.find('.action-add-toggle'));
|
||||
|
||||
const actionName = $(event.target).attr('data-action-add');
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const TPL = `
|
||||
|
||||
<div style="margin-left: 10px; margin-right: 10px">${t('ancestor.depth_label')}:</div>
|
||||
|
||||
<select name="depth" class="form-control d-inline ancestor-depth" style="flex-shrink: 3">
|
||||
<select name="depth" class="form-select d-inline ancestor-depth" style="flex-shrink: 3">
|
||||
<option value="">${t('ancestor.depth_doesnt_matter')}</option>
|
||||
<option value="eq1">${t('ancestor.depth_eq', {count: 1})} (${t('ancestor.direct_children')})</option>
|
||||
<option value="eq2">${t('ancestor.depth_eq', {count: 2})}</option>
|
||||
|
||||
@@ -9,10 +9,10 @@ const TPL = `
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
||||
<p>${t("debug.debug_info")}</p>
|
||||
<p>${t("debug.access_info")}</p>
|
||||
${t("debug.access_info")}
|
||||
</div>
|
||||
</div>
|
||||
<span class="bx bx-x icon-action search-option-del"></span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user