Compare commits
6
Commits
c97304b786
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3de610e57c | ||
|
|
987dfbe9b1 | ||
|
|
f2718c6c14 | ||
|
|
ae0e920b18 | ||
|
|
4c9b5d4f5d | ||
|
|
fb8f52fe76 |
@@ -23,3 +23,6 @@ src/gen/
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# config
|
||||
nginx.conf
|
||||
@@ -18,7 +18,7 @@ Single-page Vue 3 app (JavaScript, no TypeScript). UI library is Element Plus, s
|
||||
|
||||
### gRPC-Web API layer
|
||||
|
||||
Backend communication uses ConnectRPC (gRPC-Web). Generated protobuf code lives in `src/gen/` — **gitignored**, produced at build time by `protoc` from `proto/yoresee_doc/v1/yoresee_doc.proto` (sibling repo directory). The production Dockerfile runs codegen before `npm run build`.
|
||||
Backend communication uses ConnectRPC (gRPC-Web). Generated protobuf code comes from the npm package `@yoresee-doc/sdk-node` (ESM/Connect build), pinned to tag `v0.1.0` in `package.json` (`git+https://git.yoresee.cc/YoreseeDoc/yoresee_doc_sdk_node.git#v0.1.0`). No local codegen required.
|
||||
|
||||
- `src/services/grpc_client.js` — transport, client instances, `unaryCall` wrapper, `messages` re-export
|
||||
- `src/services/api/*.js` — domain API functions that wrap `unaryCall` with protobuf request/response mappers
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
FROM node:20-alpine AS dev
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
|
||||
@@ -5,19 +5,7 @@ WORKDIR /app/frontend
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
RUN apk add --no-cache protobuf
|
||||
|
||||
COPY frontend ./
|
||||
COPY proto /app/proto
|
||||
|
||||
RUN mkdir -p /app/frontend/src/gen
|
||||
|
||||
RUN protoc -I /app/proto \
|
||||
--plugin=protoc-gen-es=/app/frontend/node_modules/.bin/protoc-gen-es \
|
||||
--plugin=protoc-gen-connect-es=/app/frontend/node_modules/.bin/protoc-gen-connect-es \
|
||||
--es_out=/app/frontend/src/gen --es_opt=target=js,import_extension=.js \
|
||||
--connect-es_out=/app/frontend/src/gen --connect-es_opt=target=js,import_extension=.js \
|
||||
/app/proto/yoresee_doc/v1/yoresee_doc.proto
|
||||
|
||||
RUN npm run build
|
||||
|
||||
|
||||
Generated
+430
@@ -23,6 +23,7 @@
|
||||
"@tiptap/extension-underline": "^3.22.2",
|
||||
"@tiptap/starter-kit": "^3.22.2",
|
||||
"@tiptap/vue-3": "^3.22.2",
|
||||
"@yoresee-doc/sdk-node": "git+https://git.yoresee.cc/YoreseeDoc/yoresee_doc_sdk_node.git#v0.1.0",
|
||||
"axios": "^1.13.5",
|
||||
"codemirror": "^5.65.21",
|
||||
"diff": "^8.0.4",
|
||||
@@ -710,6 +711,37 @@
|
||||
"@babel/runtime": "^7.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz",
|
||||
"integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.8.0",
|
||||
"@js-sdsl/ordered-map": "^4.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/proto-loader": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz",
|
||||
"integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"long": "^5.0.0",
|
||||
"protobufjs": "^7.5.5",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
|
||||
@@ -799,6 +831,16 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@js-sdsl/ordered-map": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
|
||||
"integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/js-sdsl"
|
||||
}
|
||||
},
|
||||
"node_modules/@mixmark-io/domino": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
|
||||
@@ -828,6 +870,63 @@
|
||||
"hulk": "bin/hulk"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
||||
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
||||
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
|
||||
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
|
||||
"integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@remirror/core-constants": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
|
||||
@@ -1821,6 +1920,15 @@
|
||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
|
||||
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tern": {
|
||||
"version": "0.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||
@@ -2129,6 +2237,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@yoresee-doc/sdk-node": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "git+https://git.yoresee.cc/YoreseeDoc/yoresee_doc_sdk_node.git#d4ffd77e1c1a71ec2008b3daa07ff4e4e22f70c0",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.10.0",
|
||||
"@connectrpc/connect": "^1.7.0",
|
||||
"@grpc/grpc-js": "^1.12.0",
|
||||
"google-protobuf": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@@ -2458,6 +2576,64 @@
|
||||
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "5.65.21",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.21.tgz",
|
||||
@@ -2473,6 +2649,24 @@
|
||||
"typo-js": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -3226,6 +3420,12 @@
|
||||
"vue": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
@@ -3400,6 +3600,15 @@
|
||||
"@esbuild/win32-x64": "0.24.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
@@ -3524,6 +3733,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -3561,6 +3779,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/google-protobuf": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-4.0.2.tgz",
|
||||
"integrity": "sha512-yD2fqbNgvJPuQwdKJiPdbUcXveNRxgqy070gzsBsCyFJA8Qdj9oxa9xtkddb/JEhcDk0RD5SfGUWg+nhINfMxA==",
|
||||
"license": "(BSD-3-Clause AND Apache-2.0)"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@@ -4130,12 +4354,24 @@
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lowlight": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
|
||||
@@ -5041,6 +5277,29 @@
|
||||
"prosemirror-transform": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.6.5",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
|
||||
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.5",
|
||||
"@protobufjs/eventemitter": "^1.1.1",
|
||||
"@protobufjs/fetch": "^1.1.1",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@@ -5113,6 +5372,15 @@
|
||||
"integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
|
||||
@@ -5484,6 +5752,12 @@
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
|
||||
@@ -6253,6 +6527,82 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
|
||||
@@ -6352,6 +6702,15 @@
|
||||
"yjs": "^13.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||
@@ -6367,6 +6726,77 @@
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
|
||||
"integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yjs": {
|
||||
"version": "13.6.30",
|
||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.30.tgz",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@tiptap/extension-underline": "^3.22.2",
|
||||
"@tiptap/starter-kit": "^3.22.2",
|
||||
"@tiptap/vue-3": "^3.22.2",
|
||||
"@yoresee-doc/sdk-node": "git+https://git.yoresee.cc/YoreseeDoc/yoresee_doc_sdk_node.git#v0.1.0",
|
||||
"axios": "^1.13.5",
|
||||
"codemirror": "^5.65.21",
|
||||
"diff": "^8.0.4",
|
||||
|
||||
+1
-68
@@ -11,6 +11,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, watch } from 'vue';
|
||||
import '@/styles/dark-mode-overrides.css';
|
||||
import { useUserStore } from './store/user';
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
@@ -37,7 +38,6 @@ watch(
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
applyDarkModeClass();
|
||||
darkModeObserver = new MutationObserver(() => {
|
||||
const enabled = Boolean(userStore.darkMode);
|
||||
const rootMatches = document.documentElement.classList.contains('dark-mode') === enabled;
|
||||
@@ -150,71 +150,4 @@ body {
|
||||
.common-tabs .el-tabs__active-bar {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 深色模式下的Element Plus组件样式 */
|
||||
.dark-mode {
|
||||
|
||||
/* 输入框文字颜色(EP 变量覆盖不到原生 input) */
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"] {
|
||||
color: var(--el-input-text-color);
|
||||
}
|
||||
|
||||
/* message box 背景统一 */
|
||||
.el-message-box,
|
||||
.el-message-box__header,
|
||||
.el-message-box__content,
|
||||
.el-message-box__btns {
|
||||
background-color: var(--el-fill-color-blank);
|
||||
}
|
||||
|
||||
.el-message-box__header { border-bottom: none; }
|
||||
.el-message-box__btns { border-top: none; }
|
||||
|
||||
/* select dropdown 项 */
|
||||
.el-select-dropdown__item.selected {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
/* date picker panel */
|
||||
.el-picker-panel__body,
|
||||
.el-picker-panel__content {
|
||||
background-color: var(--el-fill-color-blank);
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.el-date-table td.in-range div,
|
||||
.el-date-table td.available:hover div {
|
||||
background-color: var(--bg-medium);
|
||||
}
|
||||
|
||||
/* 数字输入按钮 */
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
background-color: var(--bg-medium);
|
||||
color: var(--text-dark);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.el-input-number__decrease:hover,
|
||||
.el-input-number__increase:hover {
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
/* 对话框边框 */
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.el-loading-spinner .path {
|
||||
stroke: var(--primary-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -188,9 +188,4 @@ defineExpose({
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.dark-mode .app-menu {
|
||||
background-color: var(--bg-white);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -56,7 +56,7 @@ const handleClick = () => {
|
||||
}
|
||||
|
||||
.dark-mode .app-menu-item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
background-color: var(--hover-overlay);
|
||||
}
|
||||
|
||||
.app-menu-item.is-danger:hover {
|
||||
|
||||
@@ -93,10 +93,6 @@ defineExpose({
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
:global(.dark-mode) .sidebar-header {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
padding: var(--spacing-md);
|
||||
font-size: 16px;
|
||||
@@ -108,11 +104,6 @@ defineExpose({
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:global(.dark-mode) .sidebar-title {
|
||||
color: var(--text-dark);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.collapse-button {
|
||||
padding: 4px;
|
||||
color: var(--text-light);
|
||||
|
||||
@@ -299,10 +299,6 @@ defineExpose({ treeRef, closeContextMenu });
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode .tree-toolbar {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.tree-toolbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -350,7 +346,7 @@ defineExpose({ treeRef, closeContextMenu });
|
||||
}
|
||||
|
||||
.dark-mode .editor-tree :deep(.el-tree-node__content:hover) {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
background-color: var(--hover-overlay);
|
||||
}
|
||||
|
||||
.editor-tree :deep(.el-tree-node.is-current > .el-tree-node__content),
|
||||
@@ -362,21 +358,12 @@ defineExpose({ treeRef, closeContextMenu });
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dark-mode .editor-tree :deep(.el-tree-node.is-current > .el-tree-node__content),
|
||||
.dark-mode .editor-tree :deep(.el-tree-node.is-focus > .el-tree-node__content),
|
||||
.dark-mode .editor-tree :deep(.el-tree-node:focus-within > .el-tree-node__content),
|
||||
.dark-mode .editor-tree :deep(.el-tree-node__content:focus),
|
||||
.dark-mode .editor-tree :deep(.el-tree-node__content:focus-visible) {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tree-node-content.is-selected {
|
||||
background-color: rgba(22, 93, 255, 0.1);
|
||||
}
|
||||
|
||||
.dark-mode .tree-node-content.is-selected {
|
||||
background-color: rgba(64, 128, 255, 0.35);
|
||||
background-color: var(--selected-overlay);
|
||||
}
|
||||
|
||||
.tree-node-content {
|
||||
|
||||
@@ -228,20 +228,4 @@ const resolveMetaRows = (item) => {
|
||||
background-color: var(--bg-white);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode .item-name {
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.dark-mode .item-description {
|
||||
color: var(--text-medium);
|
||||
}
|
||||
|
||||
.dark-mode .meta-label {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.dark-mode .meta-value {
|
||||
color: var(--text-medium);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -148,21 +148,21 @@ const innerPageSize = computed({
|
||||
}
|
||||
|
||||
:global(.dark-mode .common-list-pagination-popper.el-popper) {
|
||||
background: #1a2230;
|
||||
border-color: #2f3b4d;
|
||||
background: var(--bg-medium);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:global(.dark-mode .common-list-pagination-popper .el-select-dropdown__item) {
|
||||
color: #e5e7eb;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
:global(.dark-mode .common-list-pagination-popper .el-select-dropdown__item.is-hovering),
|
||||
:global(.dark-mode .common-list-pagination-popper .el-select-dropdown__item:hover) {
|
||||
background: #233147;
|
||||
background: var(--hover-overlay);
|
||||
}
|
||||
|
||||
:global(.dark-mode .common-list-pagination-popper .el-select-dropdown__item.is-selected) {
|
||||
color: #ffffff;
|
||||
background: #3370ff;
|
||||
color: var(--bg-white);
|
||||
background: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -173,26 +173,4 @@ watch(
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.dark-mode :deep(.el-input-number__decrease),
|
||||
.dark-mode :deep(.el-input-number__increase) {
|
||||
background-color: var(--bg-medium);
|
||||
color: var(--text-dark);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode :deep(.el-switch) {
|
||||
--el-switch-on-color: #3a7afe;
|
||||
--el-switch-off-color: #3a3a3a;
|
||||
}
|
||||
|
||||
.dark-mode :deep(.el-date-editor),
|
||||
.dark-mode :deep(.el-date-editor .el-input__wrapper) {
|
||||
background-color: var(--bg-medium);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode :deep(.el-date-editor .el-input__inner) {
|
||||
color: var(--text-dark);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -70,8 +70,8 @@ const showHeader = computed(() => Boolean(props.title || slots.header));
|
||||
}
|
||||
|
||||
.dark-mode .manage-section {
|
||||
background: #161b22;
|
||||
border-color: #2b2f36;
|
||||
background: var(--bg-white);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode .manage-section--plain {
|
||||
@@ -80,7 +80,7 @@ const showHeader = computed(() => Boolean(props.title || slots.header));
|
||||
}
|
||||
|
||||
.dark-mode .section-header {
|
||||
background: #161b22;
|
||||
border-bottom-color: #2b2f36;
|
||||
background: var(--bg-white);
|
||||
border-bottom-color: var(--border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -83,21 +83,4 @@ const formatStatText = (stat) => {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.dark-mode) .info-stats-card {
|
||||
background: var(--bg-medium);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:global(.dark-mode) .info-title {
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
:global(.dark-mode) .info-description {
|
||||
color: var(--text-medium);
|
||||
}
|
||||
|
||||
:global(.dark-mode) .stat-item {
|
||||
color: var(--text-medium);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,9 +2,6 @@ import { ElMessage } from 'element-plus';
|
||||
import { useAsyncAction } from '@/composables/actions/useAsyncAction';
|
||||
|
||||
export const isActionCancelled = (error) => {
|
||||
if (error === null || error === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (typeof error === 'string') {
|
||||
return error === 'cancel' || error === 'close';
|
||||
}
|
||||
|
||||
@@ -105,8 +105,8 @@ export function useRichTextLinkOverlay(options = {}) {
|
||||
const maxTop = container.scrollTop + container.clientHeight - cardHeight - 8;
|
||||
|
||||
linkHoverStyle.value = {
|
||||
left: `${Math.max(minLeft, Math.min(rawLeft, Math.max(minLeft, maxLeft)))}px`,
|
||||
top: `${Math.max(minTop, Math.min(rawTop, Math.max(minTop, maxTop)))}px`
|
||||
left: `${Math.max(minLeft, Math.min(rawLeft, maxLeft))}px`,
|
||||
top: `${Math.max(minTop, Math.min(rawTop, maxTop))}px`
|
||||
};
|
||||
linkHoverHref.value = href;
|
||||
hoverAnchorPos.value = resolveAnchorPos(anchorEl);
|
||||
|
||||
@@ -16,8 +16,6 @@ export function useDocumentEditorPolicy(options = {}) {
|
||||
const isSlideDocument = computed(() => normalizedDocType.value === '3');
|
||||
const isRichTextDocument = computed(() => normalizedDocType.value === '4');
|
||||
|
||||
const canManageAttachments = computed(() => hasDocument.value);
|
||||
const canManageSettings = computed(() => hasDocument.value);
|
||||
const collabEnabled = computed(
|
||||
() => hasDocument.value && (isMarkdownDocument.value || isRichTextDocument.value)
|
||||
);
|
||||
@@ -35,8 +33,6 @@ export function useDocumentEditorPolicy(options = {}) {
|
||||
isTableDocument,
|
||||
isSlideDocument,
|
||||
isRichTextDocument,
|
||||
canManageAttachments,
|
||||
canManageSettings,
|
||||
collabEnabled,
|
||||
inlineCommentEnabled,
|
||||
createDialogKnowledgeBaseId,
|
||||
|
||||
@@ -13,7 +13,7 @@ export function useDocumentRouteContext({ props, route }) {
|
||||
const collabEnabled = computed(() => !!docId.value);
|
||||
const collabRoom = computed(() => (docId.value ? `${docId.value}` : ''));
|
||||
const collabUrl = computed(() => '/ws/doc');
|
||||
const collabToken = computed(() => localStorage.getItem('token') || '');
|
||||
const collabToken = localStorage.getItem('token') || '';
|
||||
const collabReady = ref(false);
|
||||
const lastSyncedDocId = ref('');
|
||||
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const resolveBool = (valueOrGetterOrRef, fallback = false) => {
|
||||
if (typeof valueOrGetterOrRef === 'function') {
|
||||
return valueOrGetterOrRef();
|
||||
}
|
||||
if (valueOrGetterOrRef && typeof valueOrGetterOrRef === 'object' && 'value' in valueOrGetterOrRef) {
|
||||
return valueOrGetterOrRef.value;
|
||||
}
|
||||
if (valueOrGetterOrRef === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
return Boolean(valueOrGetterOrRef);
|
||||
};
|
||||
import { resolveBool } from '@/utils/resolveBool';
|
||||
|
||||
export function useDocumentTreeContextMenu(options = {}) {
|
||||
const { enabled = true } = options;
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
const resolveBool = (valueOrGetterOrRef, fallback = false) => {
|
||||
if (typeof valueOrGetterOrRef === 'function') {
|
||||
return valueOrGetterOrRef();
|
||||
}
|
||||
if (valueOrGetterOrRef && typeof valueOrGetterOrRef === 'object' && 'value' in valueOrGetterOrRef) {
|
||||
return valueOrGetterOrRef.value;
|
||||
}
|
||||
if (valueOrGetterOrRef === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
return Boolean(valueOrGetterOrRef);
|
||||
};
|
||||
import { resolveBool } from '@/utils/resolveBool';
|
||||
|
||||
export function useInlineRename(options = {}) {
|
||||
const { revertRename = true, onConfirm = null } = options;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useTemplateCatalog } from '@/composables/template/useTemplateCatalog';
|
||||
import { usePageBoot } from '@/composables/shell/usePageBoot';
|
||||
import { useApiAction } from '@/composables/actions/useApiAction';
|
||||
import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType';
|
||||
import { formatDate as _formatDate } from '@/utils/formatDate';
|
||||
|
||||
export function useKnowledgeBaseDetailPage({ t, router, route, userStore, locale }) {
|
||||
const { runWithLoading, createApiErrorHandler } = useApiAction({ t });
|
||||
@@ -73,11 +74,7 @@ export function useKnowledgeBaseDetailPage({ t, router, route, userStore, locale
|
||||
{ value: 'type', label: t('knowledgeBase.sortByType') }
|
||||
]);
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return t('common.unknown');
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString();
|
||||
};
|
||||
const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
|
||||
|
||||
const knowledgeBaseStats = computed(() => [
|
||||
{ key: 'documents', icon: Document, label: t('knowledgeBase.documentsCount'), value: totalDocuments.value },
|
||||
@@ -88,16 +85,19 @@ export function useKnowledgeBaseDetailPage({ t, router, route, userStore, locale
|
||||
const directoryTreeData = computed(() => {
|
||||
if (!knowledgeBaseData.value?.documents) return [];
|
||||
|
||||
const mapDoc = (doc, parentId = null) => ({
|
||||
id: doc.external_id,
|
||||
label: doc.title,
|
||||
type: doc.type,
|
||||
isFolder: doc.hasChildren || (doc.children && doc.children.length > 0),
|
||||
isLeaf: !(doc.hasChildren || (doc.children && doc.children.length > 0)),
|
||||
tags: doc.tags || [],
|
||||
parentId,
|
||||
children: doc.children ? doc.children.map((child) => mapDoc(child, doc.external_id)) : []
|
||||
});
|
||||
const mapDoc = (doc, parentId = null) => {
|
||||
const isFolder = doc.hasChildren || (doc.children && doc.children.length > 0);
|
||||
return {
|
||||
id: doc.external_id,
|
||||
label: doc.title,
|
||||
type: doc.type,
|
||||
isFolder,
|
||||
isLeaf: !isFolder,
|
||||
tags: doc.tags || [],
|
||||
parentId,
|
||||
children: doc.children ? doc.children.map((child) => mapDoc(child, doc.external_id)) : []
|
||||
};
|
||||
};
|
||||
|
||||
return knowledgeBaseData.value.documents.map((doc) => mapDoc(doc));
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as api from '@/services/api';
|
||||
import { usePagedList } from '@/composables/list/usePagedList';
|
||||
import { useLazyTabLoader } from '@/composables/list/useLazyTabLoader';
|
||||
import { isActionCancelled, useApiAction } from '@/composables/actions/useApiAction';
|
||||
import { formatDate as _formatDate } from '@/utils/formatDate';
|
||||
|
||||
export function useKnowledgeBaseListPage({ t, router }) {
|
||||
const { runApi } = useApiAction({ t });
|
||||
@@ -39,11 +40,7 @@ export function useKnowledgeBaseListPage({ t, router }) {
|
||||
}
|
||||
});
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return t('common.unknown');
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString();
|
||||
};
|
||||
const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
|
||||
|
||||
const myTagMapper = (kb) => (kb.is_public ? null : { type: 'info', label: t('knowledgeBase.private') });
|
||||
const recentTagMapper = (kb) => (kb.is_public ? { type: 'success', label: t('knowledgeBase.public') } : null);
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
const resolveCurrentUserKey = () => {
|
||||
if (typeof window === 'undefined') return 'guest';
|
||||
try {
|
||||
const raw = localStorage.getItem('userInfo');
|
||||
if (!raw) return 'guest';
|
||||
const userInfo = JSON.parse(raw);
|
||||
return String(userInfo?.external_id || userInfo?.externalId || 'guest').trim() || 'guest';
|
||||
} catch {
|
||||
return 'guest';
|
||||
}
|
||||
};
|
||||
|
||||
export const createExpiringCache = ({ storageKey, itemsField }) => {
|
||||
const memoryCache = new Map();
|
||||
|
||||
const readStorage = () => {
|
||||
if (typeof window === 'undefined') return {};
|
||||
try {
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
if (!raw) return {};
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== 'object') return {};
|
||||
return parsed;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const writeStorage = (cacheObj) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(cacheObj));
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const normalizeItems = (items) => {
|
||||
if (!Array.isArray(items)) return [];
|
||||
return items.map((item) => String(item || '').trim()).filter(Boolean);
|
||||
};
|
||||
|
||||
const isEntryExpired = (entry, now = Date.now()) => {
|
||||
const expiresAt = Number(entry?.expiresAt || 0);
|
||||
return !Number.isFinite(expiresAt) || expiresAt <= now;
|
||||
};
|
||||
|
||||
const normalizeEntry = (entry) => {
|
||||
if (Array.isArray(entry)) return null;
|
||||
if (!entry || typeof entry !== 'object') return null;
|
||||
const items = normalizeItems(entry[itemsField]);
|
||||
const expiresAt = Number(entry.expiresAt || 0);
|
||||
if (!Number.isFinite(expiresAt)) return null;
|
||||
return { [itemsField]: items, expiresAt };
|
||||
};
|
||||
|
||||
const purgeExpired = (storageCache, now = Date.now()) => {
|
||||
let changed = false;
|
||||
Object.keys(storageCache || {}).forEach((key) => {
|
||||
const normalized = normalizeEntry(storageCache[key]);
|
||||
if (!normalized || isEntryExpired(normalized, now)) {
|
||||
delete storageCache[key];
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) writeStorage(storageCache);
|
||||
};
|
||||
|
||||
const buildKey = (scope) => {
|
||||
const scopeKey = String(scope || '').trim();
|
||||
if (!scopeKey) return '';
|
||||
return `${resolveCurrentUserKey()}::${scopeKey}`;
|
||||
};
|
||||
|
||||
const get = (scope) => {
|
||||
const key = buildKey(scope);
|
||||
if (!key) return null;
|
||||
const now = Date.now();
|
||||
const memoryValue = normalizeEntry(memoryCache.get(key));
|
||||
if (memoryValue && !isEntryExpired(memoryValue, now)) {
|
||||
return [...memoryValue[itemsField]];
|
||||
}
|
||||
if (memoryValue) memoryCache.delete(key);
|
||||
const storageCache = readStorage();
|
||||
purgeExpired(storageCache, now);
|
||||
const storageEntry = normalizeEntry(storageCache[key]);
|
||||
if (!storageEntry || isEntryExpired(storageEntry, now)) return null;
|
||||
memoryCache.set(key, storageEntry);
|
||||
return [...storageEntry[itemsField]];
|
||||
};
|
||||
|
||||
const set = (scope, items) => {
|
||||
const key = buildKey(scope);
|
||||
if (!key) return;
|
||||
const normalized = normalizeItems(items);
|
||||
const entry = { [itemsField]: normalized, expiresAt: Date.now() + CACHE_TTL_MS };
|
||||
memoryCache.set(key, entry);
|
||||
const storageCache = readStorage();
|
||||
purgeExpired(storageCache);
|
||||
storageCache[key] = entry;
|
||||
writeStorage(storageCache);
|
||||
};
|
||||
|
||||
return { get, set };
|
||||
};
|
||||
@@ -16,7 +16,7 @@ export function usePanelSidebar(options = {}) {
|
||||
|
||||
const toValidWidth = (value) => {
|
||||
const dynamicMax = typeof getMaxWidth === 'function' ? getMaxWidth() : maxWidth;
|
||||
const hardMax = Math.max(minWidth, dynamicMax || maxWidth);
|
||||
const hardMax = Math.max(minWidth, dynamicMax ?? maxWidth);
|
||||
return Math.min(Math.max(value, minWidth), hardMax);
|
||||
};
|
||||
|
||||
@@ -41,8 +41,8 @@ export function usePanelSidebar(options = {}) {
|
||||
});
|
||||
|
||||
const resizing = ref(false);
|
||||
const startX = ref(0);
|
||||
const startWidth = ref(width.value);
|
||||
let startX = 0;
|
||||
let startWidth = width.value;
|
||||
|
||||
const stopResize = () => {
|
||||
if (!resizing.value) return;
|
||||
@@ -56,17 +56,17 @@ export function usePanelSidebar(options = {}) {
|
||||
const onResizeMove = (event) => {
|
||||
if (!resizing.value) return;
|
||||
const delta = resizeEdge === 'left'
|
||||
? startX.value - event.clientX
|
||||
: event.clientX - startX.value;
|
||||
width.value = toValidWidth(startWidth.value + delta);
|
||||
? startX - event.clientX
|
||||
: event.clientX - startX;
|
||||
width.value = toValidWidth(startWidth + delta);
|
||||
};
|
||||
|
||||
const startResize = (event) => {
|
||||
if (collapsed.value) return;
|
||||
event.preventDefault();
|
||||
resizing.value = true;
|
||||
startX.value = event.clientX;
|
||||
startWidth.value = width.value;
|
||||
startX = event.clientX;
|
||||
startWidth = width.value;
|
||||
document.body.style.cursor = 'col-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
window.addEventListener('mousemove', onResizeMove);
|
||||
|
||||
@@ -1,126 +1,6 @@
|
||||
const CACHE_STORAGE_KEY = 'sideNavDisplayTabsCacheV2';
|
||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
const memoryCache = new Map();
|
||||
import { createExpiringCache } from './createExpiringCache';
|
||||
|
||||
const resolveCurrentUserKey = () => {
|
||||
if (typeof window === 'undefined') return 'guest';
|
||||
try {
|
||||
const raw = localStorage.getItem('userInfo');
|
||||
if (!raw) return 'guest';
|
||||
const userInfo = JSON.parse(raw);
|
||||
return String(userInfo?.external_id || userInfo?.externalId || 'guest').trim() || 'guest';
|
||||
} catch {
|
||||
return 'guest';
|
||||
}
|
||||
};
|
||||
const cache = createExpiringCache({ storageKey: 'sideNavDisplayTabsCacheV2', itemsField: 'tabs' });
|
||||
|
||||
const buildScopedSceneKey = (scene) => {
|
||||
const sceneKey = String(scene || '').trim();
|
||||
if (!sceneKey) return '';
|
||||
return `${resolveCurrentUserKey()}::${sceneKey}`;
|
||||
};
|
||||
|
||||
const readStorageCache = () => {
|
||||
if (typeof window === 'undefined') return {};
|
||||
try {
|
||||
const raw = localStorage.getItem(CACHE_STORAGE_KEY);
|
||||
if (!raw) return {};
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== 'object') return {};
|
||||
return parsed;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const writeStorageCache = (cacheObj) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(cacheObj));
|
||||
} catch {
|
||||
// ignore storage write errors
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeTabs = (tabs) => {
|
||||
if (!Array.isArray(tabs)) return [];
|
||||
return tabs
|
||||
.map((item) => String(item || '').trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const isEntryExpired = (entry, now = Date.now()) => {
|
||||
const expiresAt = Number(entry?.expiresAt || 0);
|
||||
return !Number.isFinite(expiresAt) || expiresAt <= now;
|
||||
};
|
||||
|
||||
const normalizeEntry = (entry) => {
|
||||
if (Array.isArray(entry)) {
|
||||
// Backward compatibility for old schema: treat as expired and ignore.
|
||||
return null;
|
||||
}
|
||||
if (!entry || typeof entry !== 'object') return null;
|
||||
const tabs = normalizeTabs(entry.tabs);
|
||||
const expiresAt = Number(entry.expiresAt || 0);
|
||||
if (!Number.isFinite(expiresAt)) return null;
|
||||
return {
|
||||
tabs,
|
||||
expiresAt
|
||||
};
|
||||
};
|
||||
|
||||
const purgeExpiredStorageEntries = (storageCache, now = Date.now()) => {
|
||||
let changed = false;
|
||||
Object.keys(storageCache || {}).forEach((key) => {
|
||||
const normalized = normalizeEntry(storageCache[key]);
|
||||
if (!normalized || isEntryExpired(normalized, now)) {
|
||||
delete storageCache[key];
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
writeStorageCache(storageCache);
|
||||
}
|
||||
};
|
||||
|
||||
export const getSideBarDisplayTabsCache = (scene) => {
|
||||
const key = buildScopedSceneKey(scene);
|
||||
if (!key) return null;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
const memoryValue = normalizeEntry(memoryCache.get(key));
|
||||
if (memoryValue && !isEntryExpired(memoryValue, now)) {
|
||||
return [...memoryValue.tabs];
|
||||
}
|
||||
if (memoryValue && isEntryExpired(memoryValue, now)) {
|
||||
memoryCache.delete(key);
|
||||
}
|
||||
|
||||
const storageCache = readStorageCache();
|
||||
purgeExpiredStorageEntries(storageCache, now);
|
||||
const storageEntry = normalizeEntry(storageCache[key]);
|
||||
if (!storageEntry || isEntryExpired(storageEntry, now)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
memoryCache.set(key, storageEntry);
|
||||
return [...storageEntry.tabs];
|
||||
};
|
||||
|
||||
export const setSideBarDisplayTabsCache = (scene, tabs) => {
|
||||
const key = buildScopedSceneKey(scene);
|
||||
if (!key) return;
|
||||
|
||||
const normalized = normalizeTabs(tabs);
|
||||
const entry = {
|
||||
tabs: normalized,
|
||||
expiresAt: Date.now() + CACHE_TTL_MS
|
||||
};
|
||||
memoryCache.set(key, entry);
|
||||
|
||||
const storageCache = readStorageCache();
|
||||
purgeExpiredStorageEntries(storageCache);
|
||||
storageCache[key] = entry;
|
||||
writeStorageCache(storageCache);
|
||||
};
|
||||
export const getSideBarDisplayTabsCache = (scene) => cache.get(scene);
|
||||
export const setSideBarDisplayTabsCache = (scene, tabs) => cache.set(scene, tabs);
|
||||
|
||||
@@ -1,118 +1,6 @@
|
||||
const CACHE_STORAGE_KEY = 'topNavDisplayMenusCacheV1';
|
||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
const memoryCache = new Map();
|
||||
import { createExpiringCache } from './createExpiringCache';
|
||||
|
||||
const resolveCurrentUserKey = () => {
|
||||
if (typeof window === 'undefined') return 'guest';
|
||||
try {
|
||||
const raw = localStorage.getItem('userInfo');
|
||||
if (!raw) return 'guest';
|
||||
const userInfo = JSON.parse(raw);
|
||||
return String(userInfo?.external_id || userInfo?.externalId || 'guest').trim() || 'guest';
|
||||
} catch {
|
||||
return 'guest';
|
||||
}
|
||||
};
|
||||
const cache = createExpiringCache({ storageKey: 'topNavDisplayMenusCacheV1', itemsField: 'menus' });
|
||||
|
||||
const buildScopedKey = () => `${resolveCurrentUserKey()}::top-nav`;
|
||||
|
||||
const readStorageCache = () => {
|
||||
if (typeof window === 'undefined') return {};
|
||||
try {
|
||||
const raw = localStorage.getItem(CACHE_STORAGE_KEY);
|
||||
if (!raw) return {};
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed !== 'object') return {};
|
||||
return parsed;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const writeStorageCache = (cacheObj) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(cacheObj));
|
||||
} catch {
|
||||
// ignore storage write errors
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeMenus = (menus) => {
|
||||
if (!Array.isArray(menus)) return [];
|
||||
return menus
|
||||
.map((item) => String(item || '').trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const isEntryExpired = (entry, now = Date.now()) => {
|
||||
const expiresAt = Number(entry?.expiresAt || 0);
|
||||
return !Number.isFinite(expiresAt) || expiresAt <= now;
|
||||
};
|
||||
|
||||
const normalizeEntry = (entry) => {
|
||||
if (Array.isArray(entry)) {
|
||||
// Backward compatibility for old schema: treat as expired.
|
||||
return null;
|
||||
}
|
||||
if (!entry || typeof entry !== 'object') return null;
|
||||
const menus = normalizeMenus(entry.menus);
|
||||
const expiresAt = Number(entry.expiresAt || 0);
|
||||
if (!Number.isFinite(expiresAt)) return null;
|
||||
return {
|
||||
menus,
|
||||
expiresAt
|
||||
};
|
||||
};
|
||||
|
||||
const purgeExpiredStorageEntries = (storageCache, now = Date.now()) => {
|
||||
let changed = false;
|
||||
Object.keys(storageCache || {}).forEach((key) => {
|
||||
const normalized = normalizeEntry(storageCache[key]);
|
||||
if (!normalized || isEntryExpired(normalized, now)) {
|
||||
delete storageCache[key];
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
writeStorageCache(storageCache);
|
||||
}
|
||||
};
|
||||
|
||||
export const getTopNavDisplayMenusCache = () => {
|
||||
const key = buildScopedKey();
|
||||
const now = Date.now();
|
||||
|
||||
const memoryValue = normalizeEntry(memoryCache.get(key));
|
||||
if (memoryValue && !isEntryExpired(memoryValue, now)) {
|
||||
return [...memoryValue.menus];
|
||||
}
|
||||
if (memoryValue && isEntryExpired(memoryValue, now)) {
|
||||
memoryCache.delete(key);
|
||||
}
|
||||
|
||||
const storageCache = readStorageCache();
|
||||
purgeExpiredStorageEntries(storageCache, now);
|
||||
const storageEntry = normalizeEntry(storageCache[key]);
|
||||
if (!storageEntry || isEntryExpired(storageEntry, now)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
memoryCache.set(key, storageEntry);
|
||||
return [...storageEntry.menus];
|
||||
};
|
||||
|
||||
export const setTopNavDisplayMenusCache = (menus) => {
|
||||
const key = buildScopedKey();
|
||||
const normalized = normalizeMenus(menus);
|
||||
const entry = {
|
||||
menus: normalized,
|
||||
expiresAt: Date.now() + CACHE_TTL_MS
|
||||
};
|
||||
memoryCache.set(key, entry);
|
||||
|
||||
const storageCache = readStorageCache();
|
||||
purgeExpiredStorageEntries(storageCache);
|
||||
storageCache[key] = entry;
|
||||
writeStorageCache(storageCache);
|
||||
};
|
||||
export const getTopNavDisplayMenusCache = () => cache.get('top-nav');
|
||||
export const setTopNavDisplayMenusCache = (menus) => cache.set('top-nav', menus);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { listNotifications, markNotificationsRead, markAllNotificationsRead } from '@/services/api';
|
||||
import { useApiAction } from '@/composables/actions/useApiAction';
|
||||
import { formatDate as _formatDate } from '@/utils/formatDate';
|
||||
|
||||
export function useNotificationCenter({ t }) {
|
||||
const { runApi, runSilent, runWithLoading } = useApiAction({ t });
|
||||
const runNotificationSilent = (context, action, options = {}) =>
|
||||
runSilent(action, { context, ...options });
|
||||
const runNotificationSilent = (action, options = {}) =>
|
||||
runSilent(action, options);
|
||||
|
||||
const activeTab = ref('all');
|
||||
const notifications = ref([]);
|
||||
@@ -23,25 +24,23 @@ export function useNotificationCenter({ t }) {
|
||||
{ key: 'actions', label: t('common.actions'), minWidth: 160, align: 'center' }
|
||||
]);
|
||||
|
||||
const displayNotifications = computed(() => notifications.value);
|
||||
|
||||
const selectAll = computed(() => {
|
||||
if (!displayNotifications.value.length) return false;
|
||||
return displayNotifications.value.every((item) => selectedIds.value.includes(item.external_id));
|
||||
if (!notifications.value.length) return false;
|
||||
return notifications.value.every((item) => selectedIds.value.includes(item.external_id));
|
||||
});
|
||||
|
||||
const selectIndeterminate = computed(() => {
|
||||
const selectedCount = displayNotifications.value.filter((item) => selectedIds.value.includes(item.external_id)).length;
|
||||
return selectedCount > 0 && selectedCount < displayNotifications.value.length;
|
||||
const selectedCount = notifications.value.filter((item) => selectedIds.value.includes(item.external_id)).length;
|
||||
return selectedCount > 0 && selectedCount < notifications.value.length;
|
||||
});
|
||||
|
||||
const toggleSelectAll = (value) => {
|
||||
if (value) {
|
||||
selectedIds.value = displayNotifications.value.map((item) => item.external_id);
|
||||
selectedIds.value = notifications.value.map((item) => item.external_id);
|
||||
return;
|
||||
}
|
||||
selectedIds.value = selectedIds.value.filter(
|
||||
(id) => !displayNotifications.value.some((item) => item.external_id === id)
|
||||
(id) => !notifications.value.some((item) => item.external_id === id)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -67,12 +66,7 @@ export function useNotificationCenter({ t }) {
|
||||
return row.title || row.content || '-';
|
||||
};
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!value) return '-';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleString();
|
||||
};
|
||||
const formatDate = (value) => _formatDate(value, { fallback: '-', useLocaleString: true });
|
||||
|
||||
const emitUnread = (hasUnread) => {
|
||||
window.dispatchEvent(new CustomEvent('notifications:unread', { detail: { hasUnread } }));
|
||||
@@ -80,7 +74,6 @@ export function useNotificationCenter({ t }) {
|
||||
|
||||
const refreshUnreadStatus = async () => {
|
||||
await runNotificationSilent(
|
||||
'refreshUnreadStatus',
|
||||
() => listNotifications({ page: 1, page_size: 1, status: 'unread' }),
|
||||
{
|
||||
onSuccess: (resp) => {
|
||||
@@ -97,7 +90,6 @@ export function useNotificationCenter({ t }) {
|
||||
await runWithLoading(
|
||||
loading,
|
||||
() => runNotificationSilent(
|
||||
'loadNotifications',
|
||||
() =>
|
||||
listNotifications({
|
||||
page: currentPage.value,
|
||||
@@ -177,11 +169,11 @@ export function useNotificationCenter({ t }) {
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
notifications,
|
||||
totalCount,
|
||||
currentPage,
|
||||
pageSize,
|
||||
columns,
|
||||
displayNotifications,
|
||||
selectedIds,
|
||||
selectAll,
|
||||
selectIndeterminate,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useLazyTabLoader } from '@/composables/list/useLazyTabLoader';
|
||||
import { useTemplateCatalog } from '@/composables/template/useTemplateCatalog';
|
||||
import { useApiAction } from '@/composables/actions/useApiAction';
|
||||
import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType';
|
||||
import { formatDate as _formatDate } from '@/utils/formatDate';
|
||||
|
||||
export function useTemplateListPage({ t, router }) {
|
||||
const { runWithLoading, createApiErrorHandler } = useApiAction({ t });
|
||||
@@ -55,12 +56,7 @@ export function useTemplateListPage({ t, router }) {
|
||||
onError: handleTemplateLoadError
|
||||
});
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!value) return t('common.unknown');
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleDateString();
|
||||
};
|
||||
const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
|
||||
|
||||
const openTemplate = (tpl) => {
|
||||
if (!tpl?.id) return;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { createDocument as createDocumentApi, getTemplate } from '@/services/api';
|
||||
import { useApiAction } from '@/composables/actions/useApiAction';
|
||||
import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType';
|
||||
import { formatDate as _formatDate } from '@/utils/formatDate';
|
||||
|
||||
export function useTemplatePreviewPage({ t, route, router }) {
|
||||
const { runWithLoading } = useApiAction({ t });
|
||||
@@ -14,11 +15,7 @@ export function useTemplatePreviewPage({ t, route, router }) {
|
||||
|
||||
const templateId = computed(() => route.params.templateId || route.params.id);
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return t('common.unknown');
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString();
|
||||
};
|
||||
const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
|
||||
|
||||
const scopeLabel = computed(() => {
|
||||
const scope = template.value?.scope;
|
||||
|
||||
@@ -8,7 +8,7 @@ const defaultBaseURL = (() => {
|
||||
return '';
|
||||
})();
|
||||
|
||||
export const API_BASE_URL = trimTrailingSlash(import.meta.env.VITE_API_BASE_URL || defaultBaseURL);
|
||||
const API_BASE_URL = trimTrailingSlash(import.meta.env.VITE_API_BASE_URL || defaultBaseURL);
|
||||
|
||||
export const resolveWithApiBase = (target = '') => {
|
||||
const value = String(target || '').trim();
|
||||
|
||||
+1
-2
@@ -14,8 +14,7 @@ const i18n = createI18n({
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en: enUS,
|
||||
zh: zhCN,
|
||||
'zh-CN': zhCN
|
||||
zh: zhCN
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -408,7 +408,6 @@ export default {
|
||||
documentsCount: 'Documents Count',
|
||||
createdAt: 'Created At',
|
||||
updatedAt: 'Updated At',
|
||||
owner: 'Owner: {owner}',
|
||||
access: 'Access',
|
||||
view: 'View Details',
|
||||
fetchError: 'Failed to fetch knowledge bases',
|
||||
@@ -434,7 +433,6 @@ export default {
|
||||
template: 'Template',
|
||||
selectTemplate: 'Select template',
|
||||
createDocumentSuccess: 'Document created successfully',
|
||||
documentCreatedSuccessfully: 'Document created successfully',
|
||||
createDocumentError: 'Failed to create document',
|
||||
newDocument: 'New Document',
|
||||
treeRefreshed: 'Tree structure refreshed',
|
||||
|
||||
@@ -406,7 +406,6 @@ export default {
|
||||
documentsCount: '文档数量',
|
||||
createdAt: '创建时间',
|
||||
updatedAt: '更新时间',
|
||||
owner: '所有者: {owner}',
|
||||
access: '访问',
|
||||
view: '查看详情',
|
||||
fetchError: '获取知识库失败',
|
||||
@@ -432,7 +431,6 @@ export default {
|
||||
template: '模板',
|
||||
selectTemplate: '请选择模板',
|
||||
createDocumentSuccess: '文档创建成功',
|
||||
documentCreatedSuccessfully: '文档创建成功',
|
||||
createDocumentError: '创建文档失败',
|
||||
newDocument: '新文档',
|
||||
treeRefreshed: '树形结构已刷新',
|
||||
|
||||
+36
-62
@@ -6,6 +6,40 @@ const { t } = i18n.global;
|
||||
|
||||
const APP_NAME = 'Yoresee';
|
||||
|
||||
const makeDocumentChildRoutes = (basePath, namePrefix, propsFactory) => [
|
||||
{
|
||||
path: `${basePath}/:docId`,
|
||||
name: `${namePrefix}Editor`,
|
||||
component: () => import('../views/document/DocumentEditor.vue'),
|
||||
meta: { requiresAuth: true, dynamicTitle: true },
|
||||
props: propsFactory
|
||||
},
|
||||
{
|
||||
path: `${basePath}/:docId/attachments`,
|
||||
name: `${namePrefix}Attachments`,
|
||||
component: () => import('../views/document/DocumentAttachments.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.attachments.title' }
|
||||
},
|
||||
{
|
||||
path: `${basePath}/:docId/history`,
|
||||
name: `${namePrefix}History`,
|
||||
component: () => import('../views/document/DocumentHistory.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'history.title' }
|
||||
},
|
||||
{
|
||||
path: `${basePath}/:docId/setting`,
|
||||
name: `${namePrefix}Settings`,
|
||||
component: () => import('../views/document/DocumentSettings.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.settings.title' }
|
||||
},
|
||||
{
|
||||
path: `${basePath}/:docId/attachment/:attachmentId`,
|
||||
name: `${namePrefix}AttachmentOpen`,
|
||||
component: () => import('../views/document/DocumentAttachmentOpen.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.attachments.previewTitle' }
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
@@ -38,37 +72,7 @@ const router = createRouter({
|
||||
name: 'MyDocumentsAlias',
|
||||
redirect: '/mydocuments'
|
||||
},
|
||||
{
|
||||
path: '/mydocument/:docId',
|
||||
name: 'MyDocumentEditor',
|
||||
component: () => import('../views/document/DocumentEditor.vue'),
|
||||
meta: { requiresAuth: true, dynamicTitle: true },
|
||||
props: (route) => ({ kbId: 'personal', docId: route.params.docId })
|
||||
},
|
||||
{
|
||||
path: '/mydocument/:docId/attachments',
|
||||
name: 'MyDocumentAttachments',
|
||||
component: () => import('../views/document/DocumentAttachments.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.attachments.title' }
|
||||
},
|
||||
{
|
||||
path: '/mydocument/:docId/history',
|
||||
name: 'MyDocumentHistory',
|
||||
component: () => import('../views/document/DocumentHistory.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'history.title' }
|
||||
},
|
||||
{
|
||||
path: '/mydocument/:docId/setting',
|
||||
name: 'MyDocumentSettings',
|
||||
component: () => import('../views/document/DocumentSettings.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.settings.title' }
|
||||
},
|
||||
{
|
||||
path: '/mydocument/:docId/attachment/:attachmentId',
|
||||
name: 'MyDocumentAttachmentOpen',
|
||||
component: () => import('../views/document/DocumentAttachmentOpen.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.attachments.previewTitle' }
|
||||
},
|
||||
...makeDocumentChildRoutes('/mydocument', 'MyDocument', (route) => ({ kbId: 'personal', docId: route.params.docId })),
|
||||
{
|
||||
path: '/knowledge-base',
|
||||
name: 'KnowledgeBase',
|
||||
@@ -100,37 +104,7 @@ const router = createRouter({
|
||||
meta: { requiresAuth: true, dynamicTitle: true },
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/knowledge-base/:kbId/document/:docId',
|
||||
name: 'KnowledgeBaseDocumentEditor',
|
||||
component: () => import('../views/document/DocumentEditor.vue'),
|
||||
meta: { requiresAuth: true, dynamicTitle: true },
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/knowledge-base/:kbId/document/:docId/attachments',
|
||||
name: 'KnowledgeBaseDocumentAttachments',
|
||||
component: () => import('../views/document/DocumentAttachments.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.attachments.title' }
|
||||
},
|
||||
{
|
||||
path: '/knowledge-base/:kbId/document/:docId/history',
|
||||
name: 'KnowledgeBaseDocumentHistory',
|
||||
component: () => import('../views/document/DocumentHistory.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'history.title' }
|
||||
},
|
||||
{
|
||||
path: '/knowledge-base/:kbId/document/:docId/setting',
|
||||
name: 'KnowledgeBaseDocumentSettings',
|
||||
component: () => import('../views/document/DocumentSettings.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.settings.title' }
|
||||
},
|
||||
{
|
||||
path: '/knowledge-base/:kbId/document/:docId/attachment/:attachmentId',
|
||||
name: 'KnowledgeBaseDocumentAttachmentOpen',
|
||||
component: () => import('../views/document/DocumentAttachmentOpen.vue'),
|
||||
meta: { requiresAuth: true, titleKey: 'document.attachments.previewTitle' }
|
||||
},
|
||||
...makeDocumentChildRoutes('/knowledge-base/:kbId/document', 'KnowledgeBaseDocument', true),
|
||||
{
|
||||
path: '/user_info/profile',
|
||||
name: 'UserProfile',
|
||||
|
||||
@@ -6,23 +6,3 @@ export * from './api/invitation';
|
||||
export * from './api/setting';
|
||||
export * from './api/notification';
|
||||
export * from './api/comment';
|
||||
|
||||
import * as knowledgeBaseApi from './api/knowledgeBase';
|
||||
import * as documentApi from './api/document';
|
||||
import * as templateApi from './api/template';
|
||||
import * as membershipApi from './api/membership';
|
||||
import * as invitationApi from './api/invitation';
|
||||
import * as settingApi from './api/setting';
|
||||
import * as notificationApi from './api/notification';
|
||||
import * as commentApi from './api/comment';
|
||||
|
||||
export default {
|
||||
...knowledgeBaseApi,
|
||||
...documentApi,
|
||||
...templateApi,
|
||||
...membershipApi,
|
||||
...invitationApi,
|
||||
...settingApi,
|
||||
...notificationApi,
|
||||
...commentApi
|
||||
};
|
||||
|
||||
@@ -91,10 +91,9 @@ export const getRecentKnowledgeBases = async (params = {}) => {
|
||||
|
||||
export const getKnowledgeBaseDocuments = async (knowledgeBaseExternalID, params = {}) => {
|
||||
return getKnowledgeBaseDetail(knowledgeBaseExternalID, {
|
||||
...params,
|
||||
record_recent_log: false,
|
||||
page: 1,
|
||||
page_size: 1000,
|
||||
directory_only: params.directory_only || false,
|
||||
...params
|
||||
page: params.page ?? 1,
|
||||
page_size: params.page_size ?? 1000
|
||||
});
|
||||
};
|
||||
|
||||
@@ -36,7 +36,9 @@ export function handleResponse(base, data) {
|
||||
if (base.code === 0) {
|
||||
return { ...base, ...data };
|
||||
}
|
||||
throw new Error('request failed');
|
||||
const err = new Error(base.message || 'request failed');
|
||||
err.code = base.code;
|
||||
throw err;
|
||||
}
|
||||
|
||||
export function toTimeRange(input) {
|
||||
@@ -162,8 +164,7 @@ export function mapInvitationRecord(rec) {
|
||||
used_by: rec.usedBy,
|
||||
used_by_external_id: rec.usedByExternalId ?? null,
|
||||
used_at: rec.usedAt,
|
||||
status: rec.status,
|
||||
row_key: `${rec.code || ''}_${rec.usedAt || ''}_${rec.status || ''}`
|
||||
status: rec.status
|
||||
};
|
||||
}
|
||||
|
||||
@@ -176,8 +177,7 @@ export function mapNotification(item) {
|
||||
title: item.title,
|
||||
content: item.content,
|
||||
payload: item.payload,
|
||||
created_at: item.createdAt,
|
||||
read: item.status === 'read'
|
||||
created_at: item.createdAt
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
handleResponse,
|
||||
mapTemplate
|
||||
} from './shared';
|
||||
import { normalizeDocumentType } from '@/utils/documentType';
|
||||
import { toDocumentTypeNumber } from '@/utils/documentType';
|
||||
|
||||
const {
|
||||
CreateTemplateRequest,
|
||||
@@ -23,24 +23,12 @@ const containerMap = {
|
||||
public: CreateTemplateContainer.PUBLIC_TEMPLATE
|
||||
};
|
||||
|
||||
const resolveDocumentType = (type) => {
|
||||
const normalized = normalizeDocumentType(type, '');
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Number(normalized);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return undefined;
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
export const createTemplate = async (data = {}) => {
|
||||
const req = new CreateTemplateRequest({
|
||||
targetContainer: containerMap[data.target_container] ?? CreateTemplateContainer.OWN_TEMPLATE,
|
||||
knowledgeBaseId: data.knowledge_base_id || '',
|
||||
templateContent: data.template_content || '',
|
||||
type: resolveDocumentType(data.type)
|
||||
type: toDocumentTypeNumber(data.type)
|
||||
});
|
||||
|
||||
const resp = await unaryCall(documentClient, 'createTemplate', req);
|
||||
@@ -52,7 +40,7 @@ export const listTemplates = async (params = {}) => {
|
||||
const req = new ListTemplatesRequest({
|
||||
onlyMine: Boolean(params.only_mine),
|
||||
targetContainer: params.target_container ? containerMap[params.target_container] : undefined,
|
||||
type: resolveDocumentType(params.type),
|
||||
type: toDocumentTypeNumber(params.type),
|
||||
knowledgeBaseId: params.knowledge_base_id || undefined,
|
||||
nameKeyword: params.name_keyword || undefined,
|
||||
orderBy: params.order_by || undefined,
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
SettingService,
|
||||
SystemService,
|
||||
InvitationService
|
||||
} from '@/gen/yoresee_doc/v1/yoresee_doc_connect.js';
|
||||
import * as messages from '@/gen/yoresee_doc/v1/yoresee_doc_pb.js';
|
||||
} from '@yoresee-doc/sdk-node/esm/yoresee_doc/v1/yoresee_doc_connect.js';
|
||||
import * as messages from '@yoresee-doc/sdk-node/esm/yoresee_doc/v1/yoresee_doc_pb.js';
|
||||
import { GRPC_WEB_ENDPOINT } from '@/config/baseUrl';
|
||||
|
||||
const transport = createConnectTransport({
|
||||
@@ -29,7 +29,7 @@ const settingClient = createPromiseClient(SettingService, transport);
|
||||
const systemClient = createPromiseClient(SystemService, transport);
|
||||
const invitationClient = createPromiseClient(InvitationService, transport);
|
||||
|
||||
export function buildHeaders() {
|
||||
function buildHeaders() {
|
||||
const token = localStorage.getItem('token');
|
||||
const language = localStorage.getItem('language') || 'en';
|
||||
const headers = {
|
||||
@@ -46,7 +46,7 @@ export async function unaryCall(client, method, request) {
|
||||
return await client[method](request, { headers: buildHeaders() });
|
||||
} catch (err) {
|
||||
const code = err?.code;
|
||||
if (code === Code.Unauthenticated || code === 'unauthenticated') {
|
||||
if (code === Code.Unauthenticated) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userInfo');
|
||||
window.location.href = '/login';
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
.auth-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.auth-form .el-form-item {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.auth-form .el-input {
|
||||
height: 40px;
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.auth-form .el-input__wrapper {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.auth-form .el-input__wrapper.is-focus {
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.auth-form .login-button {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-radius: var(--border-radius-md);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.auth-form .login-button:hover,
|
||||
.auth-form .login-button:active {
|
||||
background-color: var(--primary-dark);
|
||||
border-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.auth-form .error-message {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.auth-form .error-alert {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.auth-form .register-link {
|
||||
text-align: center;
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.auth-form .register-link a {
|
||||
color: var(--primary-color);
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-form .register-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
.dark-mode {
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"] {
|
||||
color: var(--el-input-text-color);
|
||||
}
|
||||
|
||||
.el-message-box,
|
||||
.el-message-box__header,
|
||||
.el-message-box__content,
|
||||
.el-message-box__btns {
|
||||
background-color: var(--el-fill-color-blank);
|
||||
}
|
||||
|
||||
.el-message-box__header { border-bottom: none; }
|
||||
.el-message-box__btns { border-top: none; }
|
||||
|
||||
.el-select-dropdown__item.selected {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-picker-panel__body,
|
||||
.el-picker-panel__content {
|
||||
background-color: var(--el-fill-color-blank);
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.el-date-table td.in-range div,
|
||||
.el-date-table td.available:hover div {
|
||||
background-color: var(--bg-medium);
|
||||
}
|
||||
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
background-color: var(--bg-medium);
|
||||
color: var(--text-dark);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.el-input-number__decrease:hover,
|
||||
.el-input-number__increase:hover {
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.el-loading-spinner .path {
|
||||
stroke: var(--primary-color);
|
||||
}
|
||||
}
|
||||
@@ -122,4 +122,8 @@
|
||||
--el-messagebox-content-color: var(--text-dark);
|
||||
|
||||
--el-loading-fill-color: rgba(16, 18, 22, 0.72);
|
||||
|
||||
--hover-overlay: rgba(255, 255, 255, 0.08);
|
||||
--selected-overlay: rgba(64, 128, 255, 0.2);
|
||||
--loading-overlay: rgba(11, 17, 26, 0.72);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { unref } from 'vue';
|
||||
|
||||
export const navigateToDocument = (router, kbId, docId) => {
|
||||
const kb = unref(kbId);
|
||||
const doc = unref(docId);
|
||||
if (kb === 'personal') {
|
||||
router.push(`/mydocument/${doc}`);
|
||||
return;
|
||||
}
|
||||
router.push(`/knowledge-base/${kb}/document/${doc}`);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export const formatDate = (value, { fallback = '-', useLocaleString = false } = {}) => {
|
||||
if (!value) return fallback;
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return useLocaleString ? date.toLocaleString() : date.toLocaleDateString();
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const resolveBool = (valueOrGetterOrRef, fallback = false) => {
|
||||
if (typeof valueOrGetterOrRef === 'function') {
|
||||
return valueOrGetterOrRef();
|
||||
}
|
||||
if (valueOrGetterOrRef && typeof valueOrGetterOrRef === 'object' && 'value' in valueOrGetterOrRef) {
|
||||
return valueOrGetterOrRef.value;
|
||||
}
|
||||
if (valueOrGetterOrRef === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
return Boolean(valueOrGetterOrRef);
|
||||
};
|
||||
@@ -11,7 +11,7 @@
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
ref="loginFormRef"
|
||||
class="login-form"
|
||||
class="auth-form"
|
||||
>
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
@@ -67,6 +67,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import AuthLayout from '@/components/layout/AuthLayout.vue';
|
||||
import { useAuthShell } from '@/composables/shell/useAuthShell';
|
||||
import { usePageBoot } from '@/composables/shell/usePageBoot';
|
||||
import '@/styles/auth-form.css';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -130,65 +131,3 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-form .el-form-item {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.login-form .el-input {
|
||||
height: 40px;
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.login-form .el-input__wrapper {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.login-form .el-input__wrapper.is-focus {
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-radius: var(--border-radius-md);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.login-button:hover,
|
||||
.login-button:active {
|
||||
background-color: var(--primary-dark);
|
||||
border-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.error-alert {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.register-link {
|
||||
text-align: center;
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.register-link a {
|
||||
color: var(--primary-color);
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.register-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@change-language="handleLanguageChange"
|
||||
@toggle-theme="toggleTheme"
|
||||
>
|
||||
<el-form :model="registerForm" :rules="registerRules" ref="registerFormRef" class="register-form">
|
||||
<el-form :model="registerForm" :rules="registerRules" ref="registerFormRef" class="auth-form">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="registerForm.username"
|
||||
@@ -89,6 +89,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import AuthLayout from '@/components/layout/AuthLayout.vue';
|
||||
import { useAuthShell } from '@/composables/shell/useAuthShell';
|
||||
import { usePageBoot } from '@/composables/shell/usePageBoot';
|
||||
import '@/styles/auth-form.css';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
@@ -194,65 +195,3 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.register-form .el-form-item {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.register-form .el-input {
|
||||
height: 40px;
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.register-form .el-input__wrapper {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.register-form .el-input__wrapper.is-focus {
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-radius: var(--border-radius-md);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.login-button:hover,
|
||||
.login-button:active {
|
||||
background-color: var(--primary-dark);
|
||||
border-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.error-alert {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.register-link {
|
||||
text-align: center;
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.register-link a {
|
||||
color: var(--primary-color);
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.register-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -254,6 +254,6 @@ onMounted(async () => {
|
||||
|
||||
:global(.dark-mode) .preview-meta,
|
||||
:global(.dark-mode) .preview-body {
|
||||
background: var(--bg-secondary);
|
||||
background: var(--bg-medium);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -202,13 +202,9 @@ const formatSize = (bytes) => {
|
||||
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
||||
};
|
||||
|
||||
const goBackToDocument = () => {
|
||||
if (kbId.value === 'personal') {
|
||||
router.push(`/mydocument/${docId.value}`);
|
||||
return;
|
||||
}
|
||||
router.push(`/knowledge-base/${kbId.value}/document/${docId.value}`);
|
||||
};
|
||||
import { navigateToDocument } from '@/utils/documentRoute';
|
||||
|
||||
const goBackToDocument = () => navigateToDocument(router, kbId, docId);
|
||||
|
||||
onMounted(async () => {
|
||||
await initLanguage();
|
||||
|
||||
@@ -341,8 +341,7 @@ const {
|
||||
isTableDocument,
|
||||
isSlideDocument,
|
||||
isRichTextDocument,
|
||||
canManageAttachments,
|
||||
canManageSettings,
|
||||
hasDocument,
|
||||
collabEnabled,
|
||||
inlineCommentEnabled,
|
||||
createDialogKnowledgeBaseId,
|
||||
@@ -352,6 +351,8 @@ const {
|
||||
docId,
|
||||
currentDocType
|
||||
});
|
||||
const canManageAttachments = computed(() => hasDocument.value);
|
||||
const canManageSettings = computed(() => hasDocument.value);
|
||||
const {
|
||||
isEditingTitle,
|
||||
pendingTitle,
|
||||
@@ -494,10 +495,6 @@ watch(collabReady, async (ready) => {
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.dark-mode .editor-main {
|
||||
background-color: var(--bg-white);
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -532,21 +529,6 @@ watch(collabReady, async (ready) => {
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.editor-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
border-top: 1px solid var(--border-color);
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.dark-mode .editor-footer {
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.editor-fullscreen-header-btn {
|
||||
color: var(--text-medium);
|
||||
}
|
||||
|
||||
@@ -248,13 +248,9 @@ const handleSizeChange = async (size) => {
|
||||
await fetchVersions();
|
||||
};
|
||||
|
||||
const goBackToDocument = () => {
|
||||
if (kbId.value === 'personal') {
|
||||
router.push(`/mydocument/${docId.value}`);
|
||||
return;
|
||||
}
|
||||
router.push(`/knowledge-base/${kbId.value}/document/${docId.value}`);
|
||||
};
|
||||
import { navigateToDocument } from '@/utils/documentRoute';
|
||||
|
||||
const goBackToDocument = () => navigateToDocument(router, kbId, docId);
|
||||
|
||||
onMounted(async () => {
|
||||
await initLanguage();
|
||||
|
||||
@@ -97,13 +97,9 @@ const settings = reactive({
|
||||
isPublic: false
|
||||
});
|
||||
|
||||
const goBackToDocument = () => {
|
||||
if (kbId.value === 'personal') {
|
||||
router.push(`/mydocument/${docId.value}`);
|
||||
return;
|
||||
}
|
||||
router.push(`/knowledge-base/${kbId.value}/document/${docId.value}`);
|
||||
};
|
||||
import { navigateToDocument } from '@/utils/documentRoute';
|
||||
|
||||
const goBackToDocument = () => navigateToDocument(router, kbId, docId);
|
||||
|
||||
const loadSettings = async () => {
|
||||
if (!docId.value) {
|
||||
|
||||
@@ -95,7 +95,7 @@ const goBack = () => {
|
||||
}
|
||||
|
||||
:global(body.dark-mode) .not-found-card {
|
||||
box-shadow: none;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
|
||||
@@ -142,111 +142,10 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 垂直布局 */
|
||||
.knowledge-base-vertical-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 深色模式支持 */
|
||||
.dark-mode .search-input :deep(.el-input__wrapper) {
|
||||
background-color: var(--input-bg);
|
||||
border-color: var(--input-border);
|
||||
color: var(--input-text);
|
||||
}
|
||||
|
||||
.dark-mode .search-input :deep(.el-input__inner) {
|
||||
background-color: var(--input-bg);
|
||||
border-color: var(--input-border);
|
||||
color: var(--input-text);
|
||||
}
|
||||
|
||||
.dark-mode .filter-select :deep(.el-input__wrapper) {
|
||||
background-color: var(--select-bg);
|
||||
border-color: var(--select-border);
|
||||
color: var(--select-text);
|
||||
}
|
||||
|
||||
.dark-mode .filter-select :deep(.el-input__inner) {
|
||||
background-color: var(--select-bg);
|
||||
border-color: var(--select-border);
|
||||
color: var(--select-text);
|
||||
}
|
||||
|
||||
.dark-mode .filter-select :deep(.el-select__dropdown) {
|
||||
background-color: var(--select-bg);
|
||||
border-color: var(--select-border);
|
||||
}
|
||||
|
||||
.dark-mode .filter-select :deep(.el-popper) {
|
||||
background-color: var(--select-bg);
|
||||
border-color: var(--select-border);
|
||||
color: var(--select-text);
|
||||
}
|
||||
|
||||
.dark-mode .filter-select :deep(.el-select-dropdown__item) {
|
||||
background-color: var(--select-option-bg);
|
||||
color: var(--select-text);
|
||||
}
|
||||
|
||||
.dark-mode .filter-select :deep(.el-select-dropdown__item:hover) {
|
||||
background-color: var(--select-option-hover);
|
||||
}
|
||||
|
||||
.dark-mode .el-pagination :deep(.el-pager li) {
|
||||
background-color: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode .el-pagination :deep(button) {
|
||||
background-color: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode .el-pagination.is-background :deep(.btn-next),
|
||||
.dark-mode .el-pagination.is-background :deep(.btn-prev),
|
||||
.dark-mode .el-pagination.is-background :deep(.el-pager li) {
|
||||
background-color: var(--bg-medium);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dark-mode .el-pagination.is-background :deep(.el-pager li:not(.is-disabled):hover) {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.dark-mode .el-pagination.is-background :deep(.el-pager li.is-active) {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dark-mode .el-button {
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dark-mode .el-button--primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dark-mode .el-button--primary:hover {
|
||||
background-color: var(--primary-light);
|
||||
border-color: var(--primary-light);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dark-mode .column {
|
||||
background-color: var(--bg-medium);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.dark-mode .column-header {
|
||||
background-color: var(--bg-medium);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
:section-title="t('system.organization.placeholderTitle')"
|
||||
body-padding="md"
|
||||
mode="tree"
|
||||
:rows="pagedOrgNodes"
|
||||
:rows="orgTreeData"
|
||||
:columns="orgColumns"
|
||||
:is-dark="isDarkMode"
|
||||
row-key="external_id"
|
||||
@@ -259,8 +259,6 @@ const orgColumns = computed(() => [
|
||||
const pageSizes = [6, 10, 20];
|
||||
const paginationLayout = 'total, prev, pager, next';
|
||||
|
||||
const pagedOrgNodes = computed(() => orgTreeData.value);
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
if (createParent.value?.name) {
|
||||
return t('system.organization.createChildWith', { name: createParent.value.name });
|
||||
|
||||
@@ -200,18 +200,14 @@ onMounted(() => {
|
||||
padding: var(--spacing-lg) 0;
|
||||
}
|
||||
|
||||
.dark-mode .template-preview {
|
||||
background: var(--bg-white);
|
||||
}
|
||||
|
||||
.dark-mode .template-preview-title {
|
||||
color: #f3f4f6;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dark-mode .template-preview-meta,
|
||||
.dark-mode .template-preview-desc,
|
||||
.dark-mode .template-preview-date {
|
||||
color: #cbd5e1;
|
||||
color: var(--text-medium);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</el-tabs>
|
||||
|
||||
<CommonList
|
||||
:rows="displayNotifications"
|
||||
:rows="notifications"
|
||||
:columns="columns"
|
||||
:is-dark="isDarkMode"
|
||||
row-key="external_id"
|
||||
@@ -127,7 +127,7 @@ const {
|
||||
currentPage,
|
||||
pageSize,
|
||||
columns,
|
||||
displayNotifications,
|
||||
notifications,
|
||||
selectedIds,
|
||||
selectAll,
|
||||
selectIndeterminate,
|
||||
@@ -216,14 +216,14 @@ const openDetail = (row) => {
|
||||
}
|
||||
|
||||
.dark-mode .notice-main {
|
||||
color: #e5e7eb;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dark-mode .notice-title.is-unread .notice-main {
|
||||
color: #8ab4ff;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.dark-mode .notice-sub {
|
||||
color: #9aa4b2;
|
||||
color: var(--text-light);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user