mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
Refactoring Project to use proper Inversion of Control (#171)
* chore: reworking plugin internals into modules * feat: settings module is now working, added debug module, enabled new api module * chore: restoring external API * feat: refactoring project to use proper inversion of control * feat: plugins can now be loaded in any order * chore: final file migration, all typescript is in modules now * chore: fixing dependencies of cellParser * feat: csv and json views are only registered when they are not colliding with other plugins * feat: fixed library behaviour on canvas
This commit is contained in:
parent
dfdad63476
commit
285684dfc4
103 changed files with 2722 additions and 1639 deletions
5
.changeset/mean-paths-fold.md
Normal file
5
.changeset/mean-paths-fold.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"sqlseal": minor
|
||||
---
|
||||
|
||||
tables and charts now render better on canvas and work when you use external files too
|
||||
5
.changeset/rare-sloths-train.md
Normal file
5
.changeset/rare-sloths-train.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"sqlseal": patch
|
||||
---
|
||||
|
||||
csv and json views are now only registered when not colliding with other existing plugins
|
||||
5
.changeset/twenty-colts-roll.md
Normal file
5
.changeset/twenty-colts-roll.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"sqlseal": minor
|
||||
---
|
||||
|
||||
reworking plugin internals to organise code into modules
|
||||
|
|
@ -52,7 +52,7 @@ const workerPlugin = {
|
|||
build.onLoad({ filter: /.*/, namespace: 'worker-code' }, async () => {
|
||||
// Build worker code
|
||||
const result = await esbuild.build({
|
||||
entryPoints: ['src/database/worker/database.ts'],
|
||||
entryPoints: ['src/modules/database/worker/database.ts'],
|
||||
bundle: true,
|
||||
write: false,
|
||||
format: 'iife',
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@
|
|||
"@codemirror/language": "^6.11.1",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.37.1",
|
||||
"@hypersphere/dity": "^0.0.9",
|
||||
"@hypersphere/dity-graph": "^0.0.8",
|
||||
"@hypersphere/omnibus": "^0.1.6",
|
||||
"@jlongster/sql.js": "^1.6.7",
|
||||
"@types/jsonpath": "^0.2.4",
|
||||
|
|
|
|||
510
pnpm-lock.yaml
510
pnpm-lock.yaml
|
|
@ -20,6 +20,12 @@ importers:
|
|||
'@codemirror/view':
|
||||
specifier: ^6.37.1
|
||||
version: 6.37.1
|
||||
'@hypersphere/dity':
|
||||
specifier: ^0.0.9
|
||||
version: 0.0.9(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1)
|
||||
'@hypersphere/dity-graph':
|
||||
specifier: ^0.0.8
|
||||
version: 0.0.8(@types/node@22.15.29)(graphology-types@0.24.8)(sass-embedded@1.89.0)(sass@1.89.1)
|
||||
'@hypersphere/omnibus':
|
||||
specifier: ^0.1.6
|
||||
version: 0.1.6
|
||||
|
|
@ -149,7 +155,7 @@ importers:
|
|||
version: 5.8.3
|
||||
vitepress:
|
||||
specifier: 2.0.0-alpha.5
|
||||
version: 2.0.0-alpha.5(@algolia/client-search@5.25.0)(@types/node@22.15.29)(postcss@8.5.4)(sass-embedded@1.89.0)(sass@1.89.1)(search-insights@2.17.3)(typescript@5.8.3)
|
||||
version: 2.0.0-alpha.5(@algolia/client-search@5.25.0)(@types/node@22.15.29)(postcss@8.5.6)(sass-embedded@1.89.0)(sass@1.89.1)(search-insights@2.17.3)(typescript@5.8.3)
|
||||
vue:
|
||||
specifier: ^3.5.16
|
||||
version: 3.5.16(typescript@5.8.3)
|
||||
|
|
@ -403,8 +409,8 @@ packages:
|
|||
'@bcoe/v8-coverage@0.2.3':
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
|
||||
'@bufbuild/protobuf@2.5.1':
|
||||
resolution: {integrity: sha512-lut4UTvKL8tqtend0UDu7R79/n9jA7Jtxf77RNPbxtmWqfWI4qQ9bTjf7KCS4vfqLmpQbuHr1ciqJumAgJODdw==}
|
||||
'@bufbuild/protobuf@2.6.1':
|
||||
resolution: {integrity: sha512-DaG6XlyKpz08bmHY5SGX2gfIllaqtDJ/KwVoxsmP22COOLYwDBe7yD3DZGwXem/Xq7QOc9cuR7R3MpAv5CFfDw==}
|
||||
|
||||
'@changesets/apply-release-plan@7.0.12':
|
||||
resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==}
|
||||
|
|
@ -704,6 +710,15 @@ packages:
|
|||
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@hypersphere/dity-graph@0.0.8':
|
||||
resolution: {integrity: sha512-xm50liUz2iYLkNhwTy3sgmvcWJyJD+zweA3WCt90CiJ0eYRCJb5Bv4AnhilSBweTiZ2F/NGxrgzYT/jwRNnnHA==}
|
||||
|
||||
'@hypersphere/dity@0.0.8':
|
||||
resolution: {integrity: sha512-GH1jYfjEf5fgCXZUL6SL20kemzQB1u+69luyn5x4o8+6QQy4LE87UA3EQMBzy75gYZPsPKfdpNfHRzfJElv/Wg==}
|
||||
|
||||
'@hypersphere/dity@0.0.9':
|
||||
resolution: {integrity: sha512-oHwWDjMEyWsbAn2oYVXFuDu5xpg4G8FfmaACUkYeijPxeVLTfzaEhyedhOzxfQ8veIg3S9JxyKwN+mj/S09X0w==}
|
||||
|
||||
'@hypersphere/omnibus@0.1.6':
|
||||
resolution: {integrity: sha512-agZuKyhdW0n1JoLYZUuA6Du1QoQn39/LapFgRtbJs7fyRM62C9O2PWISHUCwAKnC1Splshpd8glQgx5pA2zkCg==}
|
||||
|
||||
|
|
@ -941,101 +956,201 @@ packages:
|
|||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.45.1':
|
||||
resolution: {integrity: sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.41.1':
|
||||
resolution: {integrity: sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.45.1':
|
||||
resolution: {integrity: sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.41.1':
|
||||
resolution: {integrity: sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.45.1':
|
||||
resolution: {integrity: sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.41.1':
|
||||
resolution: {integrity: sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.45.1':
|
||||
resolution: {integrity: sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.41.1':
|
||||
resolution: {integrity: sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.45.1':
|
||||
resolution: {integrity: sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.41.1':
|
||||
resolution: {integrity: sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.45.1':
|
||||
resolution: {integrity: sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.41.1':
|
||||
resolution: {integrity: sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.45.1':
|
||||
resolution: {integrity: sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.41.1':
|
||||
resolution: {integrity: sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.45.1':
|
||||
resolution: {integrity: sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.45.1':
|
||||
resolution: {integrity: sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.41.1':
|
||||
resolution: {integrity: sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.45.1':
|
||||
resolution: {integrity: sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.45.1':
|
||||
resolution: {integrity: sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.45.1':
|
||||
resolution: {integrity: sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.45.1':
|
||||
resolution: {integrity: sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.41.1':
|
||||
resolution: {integrity: sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.45.1':
|
||||
resolution: {integrity: sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.45.1':
|
||||
resolution: {integrity: sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.41.1':
|
||||
resolution: {integrity: sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.45.1':
|
||||
resolution: {integrity: sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.41.1':
|
||||
resolution: {integrity: sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.45.1':
|
||||
resolution: {integrity: sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.41.1':
|
||||
resolution: {integrity: sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.45.1':
|
||||
resolution: {integrity: sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.41.1':
|
||||
resolution: {integrity: sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.45.1':
|
||||
resolution: {integrity: sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.41.1':
|
||||
resolution: {integrity: sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.45.1':
|
||||
resolution: {integrity: sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@shikijs/core@3.5.0':
|
||||
resolution: {integrity: sha512-iycvvnVG7MWZHRNuoqpYkV3Qc8DNLU74Lxh/roDwUqJJoXRnCTbbVJGfSWAdBslUgJMsjSHwFL42i55voavDDg==}
|
||||
|
||||
|
|
@ -1060,6 +1175,11 @@ packages:
|
|||
'@shikijs/vscode-textmate@10.0.2':
|
||||
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
|
||||
|
||||
'@sigma/node-border@3.0.0':
|
||||
resolution: {integrity: sha512-mE3zUfjvJVuAMhSjiP/zdlkqe0OVTETxd04XHUwof01YqdzTk0OB4ACJIhWrwgsBXl7tTd9lPuKoroafLh8MtQ==}
|
||||
peerDependencies:
|
||||
sigma: '>=3.0.0-beta.17'
|
||||
|
||||
'@sinclair/typebox@0.27.8':
|
||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||
|
||||
|
|
@ -1111,6 +1231,9 @@ packages:
|
|||
'@types/estree@1.0.7':
|
||||
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/graceful-fs@4.1.9':
|
||||
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
|
||||
|
||||
|
|
@ -1414,8 +1537,8 @@ packages:
|
|||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
acorn@8.14.1:
|
||||
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
|
||||
acorn@8.15.0:
|
||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -1639,6 +1762,9 @@ packages:
|
|||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chroma-js@3.1.2:
|
||||
resolution: {integrity: sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg==}
|
||||
|
||||
ci-info@3.9.0:
|
||||
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -1895,8 +2021,8 @@ packages:
|
|||
engines: {node: '>=4.0'}
|
||||
hasBin: true
|
||||
|
||||
eslint-scope@8.3.0:
|
||||
resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
|
||||
eslint-scope@8.4.0:
|
||||
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
eslint-visitor-keys@3.4.3:
|
||||
|
|
@ -1907,6 +2033,10 @@ packages:
|
|||
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
eslint-visitor-keys@4.2.1:
|
||||
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
eslint@9.15.0:
|
||||
resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
|
@ -1917,8 +2047,8 @@ packages:
|
|||
jiti:
|
||||
optional: true
|
||||
|
||||
espree@10.3.0:
|
||||
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
|
||||
espree@10.4.0:
|
||||
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
esprima@1.2.2:
|
||||
|
|
@ -1954,6 +2084,10 @@ packages:
|
|||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
|
||||
execa@5.1.1:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -2003,6 +2137,14 @@ packages:
|
|||
picomatch:
|
||||
optional: true
|
||||
|
||||
fdir@6.4.6:
|
||||
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
|
@ -2139,6 +2281,29 @@ packages:
|
|||
graphemer@1.4.0:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||
|
||||
graphology-layout-forceatlas2@0.10.1:
|
||||
resolution: {integrity: sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==}
|
||||
peerDependencies:
|
||||
graphology-types: '>=0.19.0'
|
||||
|
||||
graphology-layout@0.6.1:
|
||||
resolution: {integrity: sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==}
|
||||
peerDependencies:
|
||||
graphology-types: '>=0.19.0'
|
||||
|
||||
graphology-types@0.24.8:
|
||||
resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==}
|
||||
|
||||
graphology-utils@2.5.2:
|
||||
resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==}
|
||||
peerDependencies:
|
||||
graphology-types: '>=0.23.0'
|
||||
|
||||
graphology@0.26.0:
|
||||
resolution: {integrity: sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==}
|
||||
peerDependencies:
|
||||
graphology-types: '>=0.24.0'
|
||||
|
||||
handlebars@4.7.8:
|
||||
resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
|
||||
engines: {node: '>=0.4.7'}
|
||||
|
|
@ -2229,6 +2394,9 @@ packages:
|
|||
immutable@5.1.2:
|
||||
resolution: {integrity: sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==}
|
||||
|
||||
immutable@5.1.3:
|
||||
resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -2733,6 +2901,9 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
mnemonist@0.39.8:
|
||||
resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==}
|
||||
|
||||
moment@2.29.4:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
|
||||
|
|
@ -2808,6 +2979,9 @@ packages:
|
|||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||
deprecated: This package is no longer supported.
|
||||
|
||||
obliterator@2.0.5:
|
||||
resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==}
|
||||
|
||||
obsidian@1.8.7:
|
||||
resolution: {integrity: sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==}
|
||||
peerDependencies:
|
||||
|
|
@ -2889,6 +3063,9 @@ packages:
|
|||
package-manager-detector@0.2.11:
|
||||
resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==}
|
||||
|
||||
pandemonium@2.4.1:
|
||||
resolution: {integrity: sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==}
|
||||
|
||||
papaparse@5.5.3:
|
||||
resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==}
|
||||
|
||||
|
|
@ -2933,6 +3110,10 @@ packages:
|
|||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pify@4.0.1:
|
||||
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -2953,6 +3134,10 @@ packages:
|
|||
resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
preact@10.26.8:
|
||||
resolution: {integrity: sha512-1nMfdFjucm5hKvq0IClqZwK4FJkGXhRrQstOQ3P4vp8HxKrJEMFcY6RdBRVTdfQS/UlnX6gfbPuTvaqx/bDoeQ==}
|
||||
|
||||
|
|
@ -3097,6 +3282,11 @@ packages:
|
|||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
rollup@4.45.1:
|
||||
resolution: {integrity: sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
||||
|
|
@ -3279,6 +3469,9 @@ packages:
|
|||
shiki@3.5.0:
|
||||
resolution: {integrity: sha512-1lyPuqIPPAlmR1BKtDkxiuoZTB2IKSyr+GeHXu4ReOyHoEMhCnUoGZDUv4SJRH0Bi4QmsEPsrkQCRSOgnVRC+g==}
|
||||
|
||||
sigma@3.0.2:
|
||||
resolution: {integrity: sha512-/BUbeOwPGruiBOm0YQQ6ZMcLIZ6tf/W+Jcm7dxZyAX0tK3WP9/sq7/NAWBxPIxVahdGjCJoGwej0Gdrv0DxlQQ==}
|
||||
|
||||
signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
|
|
@ -3631,6 +3824,46 @@ packages:
|
|||
yaml:
|
||||
optional: true
|
||||
|
||||
vite@7.0.5:
|
||||
resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
jiti: '>=1.21.0'
|
||||
less: ^4.0.0
|
||||
lightningcss: ^1.21.0
|
||||
sass: ^1.70.0
|
||||
sass-embedded: ^1.70.0
|
||||
stylus: '>=0.54.8'
|
||||
sugarss: ^5.0.0
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitepress@2.0.0-alpha.5:
|
||||
resolution: {integrity: sha512-fhuGpJ4CETS/lrAHjKu3m88HwesZvAjZLFeIRr9Jejmewyogn1tm2L6lsVg7PWxPmOGoMfihzl3+L6jg6hrTnA==}
|
||||
hasBin: true
|
||||
|
|
@ -4026,7 +4259,7 @@ snapshots:
|
|||
|
||||
'@bcoe/v8-coverage@0.2.3': {}
|
||||
|
||||
'@bufbuild/protobuf@2.5.1': {}
|
||||
'@bufbuild/protobuf@2.6.1': {}
|
||||
|
||||
'@changesets/apply-release-plan@7.0.12':
|
||||
dependencies:
|
||||
|
|
@ -4316,7 +4549,7 @@ snapshots:
|
|||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.4.1
|
||||
espree: 10.3.0
|
||||
espree: 10.4.0
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.2
|
||||
import-fresh: 3.3.1
|
||||
|
|
@ -4350,6 +4583,64 @@ snapshots:
|
|||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
|
||||
'@hypersphere/dity-graph@0.0.8(@types/node@22.15.29)(graphology-types@0.24.8)(sass-embedded@1.89.0)(sass@1.89.1)':
|
||||
dependencies:
|
||||
'@hypersphere/dity': 0.0.8(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1)
|
||||
'@sigma/node-border': 3.0.0(sigma@3.0.2(graphology-types@0.24.8))
|
||||
chroma-js: 3.1.2
|
||||
graphology: 0.26.0(graphology-types@0.24.8)
|
||||
graphology-layout: 0.6.1(graphology-types@0.24.8)
|
||||
graphology-layout-forceatlas2: 0.10.1(graphology-types@0.24.8)
|
||||
lodash: 4.17.21
|
||||
sigma: 3.0.2(graphology-types@0.24.8)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- graphology-types
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@hypersphere/dity@0.0.8(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1)':
|
||||
dependencies:
|
||||
typescript: 5.8.3
|
||||
vite: 7.0.5(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@hypersphere/dity@0.0.9(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1)':
|
||||
dependencies:
|
||||
typescript: 5.8.3
|
||||
vite: 7.0.5(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@hypersphere/omnibus@0.1.6': {}
|
||||
|
||||
'@iconify-json/simple-icons@1.2.37':
|
||||
|
|
@ -4669,63 +4960,123 @@ snapshots:
|
|||
'@rollup/rollup-android-arm-eabi@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.41.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@shikijs/core@3.5.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.5.0
|
||||
|
|
@ -4764,6 +5115,10 @@ snapshots:
|
|||
|
||||
'@shikijs/vscode-textmate@10.0.2': {}
|
||||
|
||||
'@sigma/node-border@3.0.0(sigma@3.0.2(graphology-types@0.24.8))':
|
||||
dependencies:
|
||||
sigma: 3.0.2(graphology-types@0.24.8)
|
||||
|
||||
'@sinclair/typebox@0.27.8': {}
|
||||
|
||||
'@sindresorhus/is@4.6.0': {}
|
||||
|
|
@ -4826,6 +5181,8 @@ snapshots:
|
|||
|
||||
'@types/estree@1.0.7': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/graceful-fs@4.1.9':
|
||||
dependencies:
|
||||
'@types/node': 22.15.29
|
||||
|
|
@ -5191,11 +5548,11 @@ snapshots:
|
|||
dependencies:
|
||||
safari-14-idb-fix: 1.0.6
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.14.1):
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
acorn: 8.15.0
|
||||
|
||||
acorn@8.14.1: {}
|
||||
acorn@8.15.0: {}
|
||||
|
||||
ag-charts-types@10.3.5: {}
|
||||
|
||||
|
|
@ -5474,6 +5831,8 @@ snapshots:
|
|||
|
||||
chownr@2.0.0: {}
|
||||
|
||||
chroma-js@3.1.2: {}
|
||||
|
||||
ci-info@3.9.0: {}
|
||||
|
||||
cjs-module-lexer@1.4.3: {}
|
||||
|
|
@ -5729,7 +6088,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
source-map: 0.6.1
|
||||
|
||||
eslint-scope@8.3.0:
|
||||
eslint-scope@8.4.0:
|
||||
dependencies:
|
||||
esrecurse: 4.3.0
|
||||
estraverse: 5.3.0
|
||||
|
|
@ -5738,6 +6097,8 @@ snapshots:
|
|||
|
||||
eslint-visitor-keys@4.2.0: {}
|
||||
|
||||
eslint-visitor-keys@4.2.1: {}
|
||||
|
||||
eslint@9.15.0:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.15.0)
|
||||
|
|
@ -5750,16 +6111,16 @@ snapshots:
|
|||
'@humanfs/node': 0.16.6
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.7
|
||||
'@types/estree': 1.0.8
|
||||
'@types/json-schema': 7.0.15
|
||||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.1
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.3.0
|
||||
eslint-visitor-keys: 4.2.0
|
||||
espree: 10.3.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
esquery: 1.6.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
|
|
@ -5777,11 +6138,11 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
espree@10.3.0:
|
||||
espree@10.4.0:
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
acorn-jsx: 5.3.2(acorn@8.14.1)
|
||||
eslint-visitor-keys: 4.2.0
|
||||
acorn: 8.15.0
|
||||
acorn-jsx: 5.3.2(acorn@8.15.0)
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
esprima@1.2.2: {}
|
||||
|
||||
|
|
@ -5803,6 +6164,8 @@ snapshots:
|
|||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
events@3.3.0: {}
|
||||
|
||||
execa@5.1.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
|
@ -5861,6 +6224,10 @@ snapshots:
|
|||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fdir@6.4.6(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
|
|
@ -6026,6 +6393,28 @@ snapshots:
|
|||
|
||||
graphemer@1.4.0: {}
|
||||
|
||||
graphology-layout-forceatlas2@0.10.1(graphology-types@0.24.8):
|
||||
dependencies:
|
||||
graphology-types: 0.24.8
|
||||
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
||||
|
||||
graphology-layout@0.6.1(graphology-types@0.24.8):
|
||||
dependencies:
|
||||
graphology-types: 0.24.8
|
||||
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
||||
pandemonium: 2.4.1
|
||||
|
||||
graphology-types@0.24.8: {}
|
||||
|
||||
graphology-utils@2.5.2(graphology-types@0.24.8):
|
||||
dependencies:
|
||||
graphology-types: 0.24.8
|
||||
|
||||
graphology@0.26.0(graphology-types@0.24.8):
|
||||
dependencies:
|
||||
events: 3.3.0
|
||||
graphology-types: 0.24.8
|
||||
|
||||
handlebars@4.7.8:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
|
|
@ -6124,6 +6513,8 @@ snapshots:
|
|||
|
||||
immutable@5.1.2: {}
|
||||
|
||||
immutable@5.1.3: {}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
dependencies:
|
||||
parent-module: 1.0.1
|
||||
|
|
@ -6812,6 +7203,10 @@ snapshots:
|
|||
|
||||
mkdirp@1.0.4: {}
|
||||
|
||||
mnemonist@0.39.8:
|
||||
dependencies:
|
||||
obliterator: 2.0.5
|
||||
|
||||
moment@2.29.4: {}
|
||||
|
||||
mri@1.2.0: {}
|
||||
|
|
@ -6881,6 +7276,8 @@ snapshots:
|
|||
gauge: 4.0.4
|
||||
set-blocking: 2.0.0
|
||||
|
||||
obliterator@2.0.5: {}
|
||||
|
||||
obsidian@1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.37.1):
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
|
|
@ -6974,6 +7371,10 @@ snapshots:
|
|||
dependencies:
|
||||
quansync: 0.2.10
|
||||
|
||||
pandemonium@2.4.1:
|
||||
dependencies:
|
||||
mnemonist: 0.39.8
|
||||
|
||||
papaparse@5.5.3: {}
|
||||
|
||||
parent-module@1.0.1:
|
||||
|
|
@ -7005,6 +7406,8 @@ snapshots:
|
|||
|
||||
picomatch@4.0.2: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pify@4.0.1: {}
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
|
@ -7021,6 +7424,12 @@ snapshots:
|
|||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
preact@10.26.8: {}
|
||||
|
||||
prelude-ls@1.1.2: {}
|
||||
|
|
@ -7158,6 +7567,32 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc': 4.41.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
rollup@4.45.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.45.1
|
||||
'@rollup/rollup-android-arm64': 4.45.1
|
||||
'@rollup/rollup-darwin-arm64': 4.45.1
|
||||
'@rollup/rollup-darwin-x64': 4.45.1
|
||||
'@rollup/rollup-freebsd-arm64': 4.45.1
|
||||
'@rollup/rollup-freebsd-x64': 4.45.1
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.45.1
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.45.1
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.45.1
|
||||
'@rollup/rollup-linux-arm64-musl': 4.45.1
|
||||
'@rollup/rollup-linux-loongarch64-gnu': 4.45.1
|
||||
'@rollup/rollup-linux-powerpc64le-gnu': 4.45.1
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.45.1
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.45.1
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.45.1
|
||||
'@rollup/rollup-linux-x64-gnu': 4.45.1
|
||||
'@rollup/rollup-linux-x64-musl': 4.45.1
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.45.1
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.45.1
|
||||
'@rollup/rollup-win32-x64-msvc': 4.45.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
run-parallel@1.2.0:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
|
@ -7242,10 +7677,10 @@ snapshots:
|
|||
|
||||
sass-embedded@1.89.0:
|
||||
dependencies:
|
||||
'@bufbuild/protobuf': 2.5.1
|
||||
'@bufbuild/protobuf': 2.6.1
|
||||
buffer-builder: 0.2.0
|
||||
colorjs.io: 0.5.2
|
||||
immutable: 5.1.2
|
||||
immutable: 5.1.3
|
||||
rxjs: 7.8.2
|
||||
supports-color: 8.1.1
|
||||
sync-child-process: 1.0.2
|
||||
|
|
@ -7314,6 +7749,13 @@ snapshots:
|
|||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
sigma@3.0.2(graphology-types@0.24.8):
|
||||
dependencies:
|
||||
events: 3.3.0
|
||||
graphology-utils: 2.5.2(graphology-types@0.24.8)
|
||||
transitivePeerDependencies:
|
||||
- graphology-types
|
||||
|
||||
signal-exit@3.0.7: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
|
@ -7612,7 +8054,21 @@ snapshots:
|
|||
sass: 1.89.1
|
||||
sass-embedded: 1.89.0
|
||||
|
||||
vitepress@2.0.0-alpha.5(@algolia/client-search@5.25.0)(@types/node@22.15.29)(postcss@8.5.4)(sass-embedded@1.89.0)(sass@1.89.1)(search-insights@2.17.3)(typescript@5.8.3):
|
||||
vite@7.0.5(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.5
|
||||
fdir: 6.4.6(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rollup: 4.45.1
|
||||
tinyglobby: 0.2.14
|
||||
optionalDependencies:
|
||||
'@types/node': 22.15.29
|
||||
fsevents: 2.3.3
|
||||
sass: 1.89.1
|
||||
sass-embedded: 1.89.0
|
||||
|
||||
vitepress@2.0.0-alpha.5(@algolia/client-search@5.25.0)(@types/node@22.15.29)(postcss@8.5.6)(sass-embedded@1.89.0)(sass@1.89.1)(search-insights@2.17.3)(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@docsearch/css': 3.9.0
|
||||
'@docsearch/js': 3.9.0(@algolia/client-search@5.25.0)(search-insights@2.17.3)
|
||||
|
|
@ -7632,7 +8088,7 @@ snapshots:
|
|||
vite: 6.3.5(@types/node@22.15.29)(sass-embedded@1.89.0)(sass@1.89.1)
|
||||
vue: 3.5.16(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
postcss: 8.5.4
|
||||
postcss: 8.5.6
|
||||
transitivePeerDependencies:
|
||||
- '@algolia/client-search'
|
||||
- '@types/node'
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
// This is render context which collects all the parsed elements and maintains overvability of them.
|
||||
|
||||
import { ModernCellParser, OnRunCallback, UnregisterCallback } from "./ModernCellParser";
|
||||
|
||||
class RenderContext {
|
||||
|
||||
private isContextActive: boolean = false;
|
||||
private contextActivateCalbacks: (() => ReturnType<OnRunCallback>)[] = []
|
||||
private contextDeactivateCallbacks: UnregisterCallback[] = []
|
||||
|
||||
constructor(private readonly cellParser: ModernCellParser) {
|
||||
|
||||
}
|
||||
|
||||
render(context: string) {
|
||||
const res = this.cellParser.prepare(context)
|
||||
if (typeof res === 'string' || typeof res === 'number') {
|
||||
return res
|
||||
}
|
||||
if (res instanceof HTMLElement) {
|
||||
return res
|
||||
}
|
||||
|
||||
if (res.onRunCallback) {
|
||||
// FIXME: not sure if this is the proper element, this might get recreated inside the handlebars. To check.
|
||||
this.contextActivateCalbacks.push(res.onRunCallback.apply(res.element, [res.element]))
|
||||
}
|
||||
return res.element
|
||||
}
|
||||
|
||||
activate() {
|
||||
if (this.isContextActive) {
|
||||
throw new Error('Context already active')
|
||||
}
|
||||
this.isContextActive = true
|
||||
for (const cb of this.contextActivateCalbacks) {
|
||||
const res = cb()
|
||||
if (res) {
|
||||
this.contextDeactivateCallbacks.push(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
if (!this.isContextActive) {
|
||||
throw new Error('Context not active')
|
||||
}
|
||||
this.isContextActive = false
|
||||
for (const cb of this.contextDeactivateCallbacks) {
|
||||
cb()
|
||||
}
|
||||
this.contextDeactivateCallbacks = []
|
||||
}
|
||||
|
||||
clear() {
|
||||
try {
|
||||
this.deactivate()
|
||||
} catch (e) { }
|
||||
|
||||
this.contextActivateCalbacks = []
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { App } from "obsidian"
|
||||
import { ModernCellParser } from "./ModernCellParser"
|
||||
import { LinkParser } from "./parser/link"
|
||||
import { ImageParser } from "./parser/image"
|
||||
import { CheckboxParser } from "./parser/checkbox"
|
||||
|
||||
export const getCellParser = (app: App, create = createEl) => {
|
||||
const cellParser = new ModernCellParser()
|
||||
cellParser.register(new LinkParser(app, create))
|
||||
cellParser.register(new ImageParser(app, create))
|
||||
cellParser.register(new CheckboxParser(app, create))
|
||||
return cellParser
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
import { OmnibusRegistrator } from "@hypersphere/omnibus";
|
||||
import { App, MarkdownPostProcessorContext, MarkdownRenderChild } from "obsidian";
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
import { Sync } from "../datamodel/sync";
|
||||
import { RendererRegistry, RenderReturn } from "../renderer/rendererRegistry";
|
||||
import { transformQuery } from "../sql/sqlTransformer";
|
||||
import { displayError, displayNotice } from "../utils/ui";
|
||||
import SqlSealPlugin from "../main";
|
||||
import { registerObservers } from "../utils/registerObservers";
|
||||
import { ParserResult, parseWithDefaults, TableDefinition } from "../grammar/parser";
|
||||
|
||||
export class CodeblockProcessor extends MarkdownRenderChild {
|
||||
|
||||
registrator: OmnibusRegistrator
|
||||
renderer: RenderReturn
|
||||
private flags: ParserResult['flags']
|
||||
private extrasEl: HTMLElement
|
||||
private explainEl: HTMLElement
|
||||
|
||||
constructor(
|
||||
private el: HTMLElement,
|
||||
private source: string,
|
||||
private ctx: MarkdownPostProcessorContext,
|
||||
private rendererRegistry: RendererRegistry,
|
||||
private db: SqlSealDatabase,
|
||||
private plugin: SqlSealPlugin,
|
||||
private app: App,
|
||||
private sync: Sync) {
|
||||
super(el)
|
||||
|
||||
this.registrator = this.sync.getRegistrator()
|
||||
}
|
||||
|
||||
query: string;
|
||||
|
||||
async onload() {
|
||||
try {
|
||||
|
||||
const defaults: ParserResult = {
|
||||
flags: {
|
||||
refresh: this.plugin.settings.enableDynamicUpdates,
|
||||
explain: false
|
||||
},
|
||||
query: '',
|
||||
renderer: { options: '', type: this.plugin.settings.defaultView.toUpperCase() },
|
||||
tables: []
|
||||
|
||||
}
|
||||
|
||||
const results = parseWithDefaults(this.source, this.rendererRegistry.getViewDefinitions(), defaults, this.rendererRegistry.flags)
|
||||
|
||||
// const results = parseLanguage(this.source, this.ctx.sourcePath)
|
||||
if (results.tables) {
|
||||
await this.registerTables(results.tables)
|
||||
if (!results.query) {
|
||||
displayNotice(this.el, `Creating tables: ${results.tables.map(t => t.tableAlias).join(', ')}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.flags = results.flags
|
||||
let rendererEl = this.el
|
||||
|
||||
if (this.flags.explain) {
|
||||
this.extrasEl = this.el.createDiv({ cls: 'sqlseal-extras-container' })
|
||||
if (this.flags.explain) {
|
||||
this.explainEl = this.extrasEl.createEl('pre', { cls: 'sqlseal-extras-explain-container' })
|
||||
}
|
||||
rendererEl = this.el.createDiv({ cls: 'sqlseal-renderer-container' })
|
||||
}
|
||||
|
||||
this.renderer = this.rendererRegistry
|
||||
.prepareRender(
|
||||
results.renderer.type.toLowerCase(), results.renderer.options
|
||||
)(rendererEl, {
|
||||
cellParser: this.plugin.cellParser,
|
||||
sourcePath: this.ctx.sourcePath
|
||||
})
|
||||
|
||||
// FIXME: probably should save the one before transform and perform transform every time we execute it.
|
||||
this.query = results.query
|
||||
await this.render()
|
||||
} catch (e) {
|
||||
displayError(this.el, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.registrator.offAll()
|
||||
if (this.renderer?.cleanup) {
|
||||
this.renderer.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
async render() {
|
||||
try {
|
||||
const registeredTablesForContext = await this.sync.getTablesMappingForContext(this.ctx.sourcePath)
|
||||
|
||||
const res = transformQuery(this.query, registeredTablesForContext)
|
||||
const transformedQuery = res.sql
|
||||
|
||||
if (this.flags.refresh) {
|
||||
registerObservers({
|
||||
bus: this.registrator,
|
||||
callback: () => this.render(),
|
||||
fileName: this.ctx.sourcePath,
|
||||
tables: res.mappedTables
|
||||
})
|
||||
}
|
||||
|
||||
let variables = {}
|
||||
const file = this.app.vault.getFileByPath(this.ctx.sourcePath)
|
||||
if (file) {
|
||||
const fileCache = this.app.metadataCache.getFileCache(file)
|
||||
variables = {
|
||||
...(fileCache?.frontmatter ?? {}),
|
||||
path: file.path,
|
||||
fileName: file.name,
|
||||
basename: file.basename,
|
||||
parent: file.parent?.path,
|
||||
extension: file.extension,
|
||||
}
|
||||
}
|
||||
|
||||
if (this.flags.explain) {
|
||||
// Rendering explain
|
||||
const result = await this.db.explain(transformedQuery, variables)
|
||||
this.explainEl.textContent = result
|
||||
}
|
||||
|
||||
const { data, columns } = await this.db.select(transformedQuery, variables)
|
||||
this.renderer.render({ data, columns, flags: this.flags, frontmatter: variables })
|
||||
} catch (e) {
|
||||
this.renderer.error(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
async registerTables(tables: TableDefinition[]) {
|
||||
await Promise.all(tables.map((table) => this.sync.registerTable({
|
||||
...table,
|
||||
sourceFile: this.ctx.sourcePath
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
import { EditorView, ViewPlugin, ViewUpdate, DecorationSet, Decoration, WidgetType } from "@codemirror/view";
|
||||
import { RangeSetBuilder } from "@codemirror/state";
|
||||
import { syntaxTree } from "@codemirror/language";
|
||||
import { App } from "obsidian";
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
import { Sync } from "../datamodel/sync";
|
||||
import { SqlSealInlineHandler } from "../codeblockHandler/inline/InlineCodeHandler";
|
||||
import { InlineProcessor } from "../codeblockHandler/inline/InlineProcessor";
|
||||
import SqlSealPlugin from "../main";
|
||||
|
||||
export function createSqlSealEditorExtension(
|
||||
app: App,
|
||||
db: SqlSealDatabase,
|
||||
plugin: SqlSealPlugin,
|
||||
sync: Sync,
|
||||
) {
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet;
|
||||
inlineHandler: SqlSealInlineHandler;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.inlineHandler = new SqlSealInlineHandler(app, db, plugin, sync);
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (update.docChanged || update.viewportChanged || update.selectionSet) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView) {
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
const tree = syntaxTree(view.state);
|
||||
|
||||
tree.iterate({
|
||||
enter: ({ type, from, to }) => {
|
||||
if (type.name.includes("inline-code")) {
|
||||
const text = view.state.doc.sliceString(from, to);
|
||||
if (text.startsWith('S>')) {
|
||||
// Check if we're currently editing this specific inline code
|
||||
const isEditing = view.hasFocus &&
|
||||
view.state.selection.ranges.some(range =>
|
||||
range.from >= from && range.to <= to);
|
||||
|
||||
if (!isEditing) {
|
||||
// Create a replacement decoration
|
||||
builder.add(from, to, Decoration.replace({
|
||||
widget: new SqlSealInlineWidget(
|
||||
text,
|
||||
this.inlineHandler,
|
||||
view.state.doc.lineAt(from).number,
|
||||
app
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class SqlSealInlineWidget extends WidgetType {
|
||||
constructor(
|
||||
private query: string,
|
||||
private handler: SqlSealInlineHandler,
|
||||
private line: number,
|
||||
private app: App
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: SqlSealInlineWidget): boolean {
|
||||
return (
|
||||
other.query === this.query &&
|
||||
other.line === this.line
|
||||
);
|
||||
}
|
||||
|
||||
processor: InlineProcessor
|
||||
|
||||
toDOM(): HTMLElement {
|
||||
const container = document.createElement('span');
|
||||
container.classList.add('sqlseal-inline-result');
|
||||
|
||||
// Create a new context for the inline query
|
||||
const ctx = {
|
||||
sourcePath: this.app.workspace.getActiveFile()?.path ?? '',
|
||||
addChild: () => {},
|
||||
getSectionInfo: () => ({
|
||||
lineStart: this.line,
|
||||
lineEnd: this.line
|
||||
})
|
||||
};
|
||||
|
||||
// Show the original query on hover
|
||||
container.setAttribute('aria-label', this.query);
|
||||
container.classList.add('has-tooltip');
|
||||
|
||||
this.processor = this.handler.instantiateProcessor(this.query, container, ctx.sourcePath)
|
||||
|
||||
this.processor.load()
|
||||
return container;
|
||||
}
|
||||
|
||||
destroy(dom: HTMLElement): void {
|
||||
if (this.processor) {
|
||||
this.processor.unload()
|
||||
}
|
||||
}
|
||||
|
||||
ignoreEvent(event: Event): boolean {
|
||||
// Return false to allow events to propagate (important for editing)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
265
src/main.ts
265
src/main.ts
|
|
@ -1,259 +1,34 @@
|
|||
import { Menu, Plugin, TAbstractFile, TFile } from 'obsidian';
|
||||
import { GridRenderer } from './renderer/GridRenderer';
|
||||
import { MarkdownRenderer } from './renderer/MarkdownRenderer';
|
||||
import { TableRenderer } from './renderer/TableRenderer';
|
||||
import { Flag, RendererConfig, RendererRegistry } from './renderer/rendererRegistry';
|
||||
import { DEFAULT_SETTINGS, SQLSealSettings, SQLSealSettingsTab } from './settings/SQLSealSettingsTab';
|
||||
import { SqlSeal } from './sqlSeal';
|
||||
import { SealFileSync } from './vaultSync/SealFileSync';
|
||||
import { CSV_VIEW_EXTENSIONS, CSV_VIEW_TYPE, CSVView } from './view/CSVView';
|
||||
import { createSqlSealEditorExtension } from './editorExtension/inlineCodeBlock';
|
||||
import { ListRenderer } from './renderer/ListRenderer';
|
||||
import { JSON_VIEW_EXTENSIONS, JSON_VIEW_TYPE, JsonView } from './view/JsonView';
|
||||
import { registerAPI } from '@vanakat/plugin-api';
|
||||
import { SQLSealRegisterApi } from './pluginApi/sqlSealApi';
|
||||
import { DatabaseTable } from './database/table';
|
||||
import { FilepathHasher } from './utils/hasher';
|
||||
import { SQLSealViewPlugin } from './editorExtension/syntaxHighlight';
|
||||
import { EditorView, ViewPlugin } from '@codemirror/view';
|
||||
import { TemplateRenderer } from './renderer/TemplateRenderer';
|
||||
import { ModernCellParser } from './cellParser/ModernCellParser';
|
||||
import { getCellParser } from './cellParser/factory';
|
||||
import { CellFunction } from './cellParser/CellFunction';
|
||||
import { Plugin } from "obsidian";
|
||||
|
||||
import { AllCommunityModule, ModuleRegistry, provideGlobalGridOptions } from 'ag-grid-community';
|
||||
import {
|
||||
AllCommunityModule,
|
||||
ModuleRegistry,
|
||||
provideGlobalGridOptions,
|
||||
} from "ag-grid-community";
|
||||
import { mainModule } from "./modules/main/module";
|
||||
|
||||
// Register all community features
|
||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||
|
||||
// Mark all grids as using legacy themes
|
||||
provideGlobalGridOptions({ theme: "legacy"});
|
||||
provideGlobalGridOptions({ theme: "legacy" });
|
||||
|
||||
export default class SqlSealPlugin extends Plugin {
|
||||
settings: SQLSealSettings;
|
||||
fileSync: SealFileSync;
|
||||
sqlSeal: SqlSeal;
|
||||
rendererRegistry: RendererRegistry = new RendererRegistry();
|
||||
|
||||
cellParser: ModernCellParser;
|
||||
container: ReturnType<(typeof mainModule)["build"]>;
|
||||
|
||||
async onload() {
|
||||
// Preparing cell parser
|
||||
this.cellParser = getCellParser(this.app, createEl)
|
||||
|
||||
|
||||
await this.loadSettings();
|
||||
const settingsTab = new SQLSealSettingsTab(this.app, this, this.settings)
|
||||
this.addSettingTab(settingsTab);
|
||||
settingsTab.onChange(async settings => {
|
||||
this.settings = settings
|
||||
// FIXME: check how to unregister the view
|
||||
await this.unregisterSQLSealExtensions()
|
||||
await this.registerSQLSealExtensions()
|
||||
})
|
||||
|
||||
await this.registerViews();
|
||||
await this.registerSQLSealExtensions()
|
||||
await this.loadSyntaxHighlighting()
|
||||
|
||||
this.registerGlobalApi();
|
||||
const sqlSeal = new SqlSeal(this.app, false, this.rendererRegistry, this) // FIXME: set verbose based on the env.
|
||||
this.sqlSeal = sqlSeal
|
||||
|
||||
await this.sqlSeal.db.connect()
|
||||
|
||||
this.cellParser.registerDbFunctions(this.sqlSeal.db)
|
||||
|
||||
// REGISTERING VIEWS
|
||||
|
||||
this.rendererRegistry.register('sql-seal-internal-table', new TableRenderer(this.app))
|
||||
this.rendererRegistry.register('sql-seal-internal-grid', new GridRenderer(this.app, this))
|
||||
this.rendererRegistry.register('sql-seal-internal-markdown', new MarkdownRenderer(this.app))
|
||||
this.rendererRegistry.register('sql-seal-internal-list', new ListRenderer(this.app))
|
||||
this.rendererRegistry.register('sql-seal-internal-template', new TemplateRenderer(this.app))
|
||||
|
||||
// start syncing when files are loaded
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
await this.sqlSeal.startFileSync(this)
|
||||
this.registerMarkdownCodeBlockProcessor("sqlseal", sqlSeal.getHandler())
|
||||
this.registerInlineCodeblocks()
|
||||
})
|
||||
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-menu', (menu: Menu, file: TAbstractFile) => {
|
||||
this.addCSVCreatorMenuItem(menu, file);
|
||||
// CONTAINER
|
||||
this.container = mainModule
|
||||
.resolve({
|
||||
"obsidian.app": this.app,
|
||||
"obsidian.plugin": this,
|
||||
"obsidian.vault": this.app.vault,
|
||||
})
|
||||
);
|
||||
.build();
|
||||
|
||||
const init = await this.container.get("init");
|
||||
init();
|
||||
}
|
||||
|
||||
private registerInlineCodeblocks() {
|
||||
|
||||
// Extension for Live Preview
|
||||
const editorExtension = createSqlSealEditorExtension(
|
||||
this.app,
|
||||
this.sqlSeal.db,
|
||||
this,
|
||||
this.sqlSeal.sync,
|
||||
);
|
||||
this.registerEditorExtension(editorExtension);
|
||||
|
||||
// Extension for Read mode
|
||||
this.registerMarkdownPostProcessor((el, ctx) => {
|
||||
const inlineCodeBlocks = el.querySelectorAll('code');
|
||||
inlineCodeBlocks.forEach((node: HTMLSpanElement) => {
|
||||
const text = node.innerText;
|
||||
if (text.startsWith('S>')) {
|
||||
const container = createEl('span', { cls: 'sqlseal-inline-result' });
|
||||
container.setAttribute('aria-label', text.slice(3));
|
||||
container.classList.add('has-tooltip');
|
||||
node.replaceWith(container);
|
||||
this.sqlSeal.getInlineHandler()(text, container, ctx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private addCSVCreatorMenuItem(menu: Menu, file: TAbstractFile) {
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle('New CSV file')
|
||||
.setIcon('table')
|
||||
.onClick(async () => {
|
||||
try {
|
||||
await this.createNewCSVFile(file)
|
||||
} catch (error) {
|
||||
console.error('Failed to create CSV file:', error)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private loadSyntaxHighlighting() {
|
||||
if (!this.settings.enableSyntaxHighlighting) {
|
||||
return
|
||||
}
|
||||
|
||||
this.registerEditorExtension([
|
||||
ViewPlugin.define(
|
||||
(view: EditorView) => new SQLSealViewPlugin(view, this.app, this.rendererRegistry),
|
||||
{ decorations: v => v.decorations }
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
private async createNewCSVFile(file: TAbstractFile) {
|
||||
const targetDir = file instanceof TFile ? file.parent : file
|
||||
const basePath = targetDir!.path
|
||||
|
||||
const csvTemplate = 'Id,Name\n1,Test Data'
|
||||
|
||||
const defaultName = 'Untitled CSV'
|
||||
let fileName = defaultName
|
||||
let filePath = `${basePath}/${fileName}.csv`;
|
||||
let counter = 1;
|
||||
|
||||
while (await this.app.vault.adapter.exists(filePath)) {
|
||||
fileName = `${defaultName} ${counter}`;
|
||||
filePath = `${basePath}/${fileName}.csv`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
try {
|
||||
const newFile = await this.app.vault.create(filePath, csvTemplate);
|
||||
|
||||
const leaf = this.app.workspace.getLeaf(false);
|
||||
await leaf.openFile(newFile);
|
||||
|
||||
const fileExplorer = this.app.workspace.getLeavesOfType('file-explorer')[0]?.view;
|
||||
if (fileExplorer) {
|
||||
await (fileExplorer as any).revealInFolder(newFile);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating CSV file:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async registerSQLSealExtensions() {
|
||||
if (this.settings.enableViewer) {
|
||||
this.registerExtensions(CSV_VIEW_EXTENSIONS, CSV_VIEW_TYPE);
|
||||
}
|
||||
if (this.settings.enableJSONViewer) {
|
||||
this.registerExtensions(JSON_VIEW_EXTENSIONS, JSON_VIEW_TYPE)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async unregisterSQLSealExtensions() {
|
||||
this.app.workspace.detachLeavesOfType(CSV_VIEW_TYPE);
|
||||
this.app.workspace.detachLeavesOfType(JSON_VIEW_TYPE);
|
||||
|
||||
(this.app as any).viewRegistry.unregisterExtensions([
|
||||
...CSV_VIEW_EXTENSIONS,
|
||||
...JSON_VIEW_EXTENSIONS
|
||||
])
|
||||
}
|
||||
|
||||
async registerViews() {
|
||||
this.registerView(
|
||||
CSV_VIEW_TYPE,
|
||||
(leaf) => new CSVView(leaf, this.settings.enableEditing, this.cellParser)
|
||||
);
|
||||
this.registerView(
|
||||
JSON_VIEW_TYPE,
|
||||
(leaf) => new JsonView(leaf)
|
||||
)
|
||||
}
|
||||
|
||||
registerSQLSealView(name: string, config: RendererConfig) {
|
||||
this.rendererRegistry.register(name, config)
|
||||
}
|
||||
|
||||
unregisterSQLSealView(name: string) {
|
||||
this.rendererRegistry.unregister(name)
|
||||
}
|
||||
|
||||
registerSQLSealFunction(fn: CellFunction) {
|
||||
this.cellParser.register(fn)
|
||||
}
|
||||
|
||||
unregisterSQLSealFunction(name: string) {
|
||||
this.cellParser.unregister(name)
|
||||
}
|
||||
|
||||
registerSQLSealFlag(flag: Flag) {
|
||||
this.rendererRegistry.registerFlag(flag)
|
||||
}
|
||||
|
||||
unregisterSQLSealFlag(name: string) {
|
||||
this.rendererRegistry.unregisterFlag(name)
|
||||
}
|
||||
|
||||
async registerTable<const columns extends string[]>(plugin: Plugin, name: string, columns: columns) {
|
||||
const hash = await FilepathHasher.sha256(`${plugin.manifest.name}`)
|
||||
const tableName = `external_table_${hash}_name`
|
||||
const newTable = new DatabaseTable(this.sqlSeal.db, tableName, columns)
|
||||
await newTable.connect()
|
||||
// FIXME: probably register this table in some type of map for the future use?
|
||||
return newTable
|
||||
}
|
||||
|
||||
registerGlobalApi() {
|
||||
registerAPI('sqlseal', new SQLSealRegisterApi(this), this)
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.sqlSeal.db.disconect();
|
||||
this.unregisterSQLSealExtensions();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
onunload() {}
|
||||
}
|
||||
|
||||
|
|
|
|||
55
src/modules/api/init.ts
Normal file
55
src/modules/api/init.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { Plugin } from "obsidian";
|
||||
import {
|
||||
PluginRegister,
|
||||
SQLSealApi,
|
||||
SQLSealRegisterApi,
|
||||
} from "./pluginApi/sqlSealApi";
|
||||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { ApiModule } from "./module";
|
||||
import { RendererRegistry } from "../editor/renderer/rendererRegistry";
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
import { ModernCellParser } from "../syntaxHighlight/cellParser/ModernCellParser";
|
||||
|
||||
const SQLSEAL_API_KEY = "___sqlSeal";
|
||||
const SQLSEAL_QUEUED_PLUGINS = "___sqlSeal_queue";
|
||||
|
||||
@(makeInjector<ApiModule, "factory">()([
|
||||
"plugin",
|
||||
"cellParser",
|
||||
"rendererRegistry",
|
||||
"db",
|
||||
]))
|
||||
export class ApiInit {
|
||||
make(
|
||||
plugin: Plugin,
|
||||
cellParser: ModernCellParser,
|
||||
rendererRegistry: RendererRegistry,
|
||||
db: SqlSealDatabase,
|
||||
) {
|
||||
return () => {
|
||||
const api = new SQLSealRegisterApi(
|
||||
plugin,
|
||||
cellParser,
|
||||
rendererRegistry,
|
||||
db,
|
||||
);
|
||||
(window as any)[SQLSEAL_API_KEY] = api;
|
||||
plugin.register(() => {
|
||||
delete (window as any)[SQLSEAL_API_KEY];
|
||||
});
|
||||
|
||||
const queuedPlugins = (window as any)[SQLSEAL_QUEUED_PLUGINS] as
|
||||
| PluginRegister[]
|
||||
| undefined;
|
||||
if (!queuedPlugins) {
|
||||
return;
|
||||
}
|
||||
|
||||
queuedPlugins.forEach((pl) => {
|
||||
api.registerForPluginNew(pl);
|
||||
});
|
||||
|
||||
(window as any)[SQLSEAL_QUEUED_PLUGINS] = [];
|
||||
};
|
||||
}
|
||||
}
|
||||
21
src/modules/api/module.ts
Normal file
21
src/modules/api/module.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { asFactory, buildContainer } from "@hypersphere/dity";
|
||||
import { ApiInit } from "./init";
|
||||
import { Plugin } from "obsidian";
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
import { RendererRegistry } from "../editor/renderer/rendererRegistry";
|
||||
import { ModernCellParser } from "../syntaxHighlight/cellParser/ModernCellParser";
|
||||
|
||||
export const apiModule = buildContainer(c => c
|
||||
.register({
|
||||
init: asFactory(ApiInit)
|
||||
})
|
||||
.exports('init')
|
||||
.externals<{
|
||||
plugin: Plugin,
|
||||
cellParser: ModernCellParser,
|
||||
db: SqlSealDatabase,
|
||||
rendererRegistry: RendererRegistry
|
||||
}>()
|
||||
)
|
||||
|
||||
export type ApiModule = typeof apiModule
|
||||
134
src/modules/api/pluginApi/sqlSealApi.ts
Normal file
134
src/modules/api/pluginApi/sqlSealApi.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import { Plugin } from "obsidian"
|
||||
import { version } from '../../../../package.json'
|
||||
import { RendererConfig, RendererRegistry } from "../../editor/renderer/rendererRegistry";
|
||||
import { FilepathHasher } from "../../../utils/hasher";
|
||||
import { DatabaseTable } from "./table";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { ModernCellParser } from "../../syntaxHighlight/cellParser/ModernCellParser";
|
||||
import { CellFunction } from "../../syntaxHighlight/cellParser/CellFunction";
|
||||
|
||||
|
||||
export type PluginRegister = {
|
||||
plugin: Plugin,
|
||||
run: (api: SQLSealApi) => void
|
||||
}
|
||||
|
||||
|
||||
const API_VERSION = 4;
|
||||
|
||||
export class SQLSealRegisterApi {
|
||||
registeredApis: Array<SQLSealApi> = []
|
||||
constructor(
|
||||
sqlSealPlugin: Plugin,
|
||||
private readonly cellParser: ModernCellParser,
|
||||
private readonly rendererRegistry: RendererRegistry,
|
||||
private readonly db: SqlSealDatabase
|
||||
) {
|
||||
sqlSealPlugin.register(() => {
|
||||
this.registeredApis.forEach(p => {
|
||||
p.unregister()
|
||||
})
|
||||
this.registeredApis = []
|
||||
})
|
||||
}
|
||||
|
||||
get sqlSealVersion() {
|
||||
return version
|
||||
}
|
||||
|
||||
get apiVersion() {
|
||||
return API_VERSION
|
||||
}
|
||||
|
||||
registerForPluginNew(reg: PluginRegister) {
|
||||
const api = new SQLSealApi(reg.plugin, this.cellParser, this.rendererRegistry, this.db)
|
||||
this.registeredApis.push(api)
|
||||
|
||||
reg.run(api)
|
||||
|
||||
// If plugin gets unregistered, we need to handle it.
|
||||
reg.plugin.register(() => {
|
||||
api.unregister()
|
||||
this.registeredApis = this.registeredApis.filter(a => a !== api)
|
||||
})
|
||||
return api
|
||||
}
|
||||
}
|
||||
|
||||
interface RegisteredView {
|
||||
name: string;
|
||||
viewClass: any;
|
||||
}
|
||||
|
||||
// TODO: use the type from registrator
|
||||
export interface RegisteredFlag {
|
||||
name: string,
|
||||
key: string
|
||||
}
|
||||
|
||||
export class SQLSealApi {
|
||||
private views: Array<RegisteredView> = []
|
||||
private functions: Array<CellFunction> = []
|
||||
private flags: Array<RegisteredFlag> = []
|
||||
|
||||
constructor(
|
||||
private readonly plugin: Plugin,
|
||||
private readonly cellParser: ModernCellParser,
|
||||
private readonly rendererRegistry: RendererRegistry,
|
||||
private readonly db: SqlSealDatabase
|
||||
) {
|
||||
plugin.register(() => {
|
||||
this.unregister()
|
||||
})
|
||||
}
|
||||
|
||||
get sqlSealVersion() {
|
||||
return version
|
||||
}
|
||||
|
||||
get apiVersion() {
|
||||
return API_VERSION
|
||||
}
|
||||
|
||||
registerView(name: string, viewClass: RendererConfig) {
|
||||
this.views.push({
|
||||
name,
|
||||
viewClass
|
||||
})
|
||||
this.rendererRegistry.register(name, viewClass)
|
||||
}
|
||||
|
||||
registerCustomFunction(fn: CellFunction) {
|
||||
this.functions.push(fn)
|
||||
this.cellParser.register(fn)
|
||||
}
|
||||
|
||||
async registerTable<const columns extends string[]>(name: string, columns: columns) {
|
||||
const hash = await FilepathHasher.sha256(`${this.plugin.manifest.name}`)
|
||||
const tableName = `external_table_${hash}_name`
|
||||
const newTable = new DatabaseTable(this.db, tableName, columns)
|
||||
await newTable.connect()
|
||||
// FIXME: probably register this table in some type of map for the future use?
|
||||
return newTable
|
||||
}
|
||||
|
||||
registerFlag(flag: RegisteredFlag) {
|
||||
this.flags.push(flag)
|
||||
this.rendererRegistry.registerFlag(flag)
|
||||
}
|
||||
|
||||
unregister() {
|
||||
for(const view of this.views) {
|
||||
this.rendererRegistry.unregister(view.name)
|
||||
}
|
||||
this.views = []
|
||||
|
||||
for(const fn of this.functions) {
|
||||
this.cellParser.unregister(fn.name)
|
||||
}
|
||||
|
||||
for(const flag of this.flags) {
|
||||
this.rendererRegistry.unregisterFlag(flag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { SqlSealDatabase } from "./database";
|
||||
import { SqlSealDatabase } from "../../database/database"
|
||||
|
||||
type Item<Columns extends string[]> = Record<Columns[number], string | number | undefined>
|
||||
|
||||
|
|
@ -14,11 +14,17 @@ export class DatabaseTable<const Columns extends string[]> {
|
|||
|
||||
async getAll() {
|
||||
const data = await this.db.select(`SELECT * FROM ${this.tableName}`, {})
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
return data.data as Item<Columns>[]
|
||||
}
|
||||
|
||||
async getByKey(key: Columns[number], value: string | number) {
|
||||
const data = await this.db.select(`SELECT * FROM ${this.tableName} WHERE ${key}=@value`, { '@value': value })
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
return data.data as Item<Columns>[]
|
||||
}
|
||||
|
||||
68
src/modules/contextMenu/init.ts
Normal file
68
src/modules/contextMenu/init.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { makeInjector } from "@hypersphere/dity"
|
||||
import { ContextMenuModule } from "./module"
|
||||
import { App, Menu, Plugin, TAbstractFile, TFile, TFolder } from "obsidian"
|
||||
|
||||
@(makeInjector<ContextMenuModule, 'factory'>()([
|
||||
'plugin', 'app'
|
||||
]))
|
||||
export class ContextMenuInit {
|
||||
make(plugin: Plugin, app: App) {
|
||||
const createNewCSVFile = async (file: TAbstractFile) => {
|
||||
const targetDir = file instanceof TFile ? file.parent : file
|
||||
const basePath = targetDir!.path
|
||||
|
||||
const csvTemplate = 'Id,Name\n1,Test Data'
|
||||
|
||||
const defaultName = 'Untitled CSV'
|
||||
let fileName = defaultName
|
||||
let filePath = `${basePath}/${fileName}.csv`;
|
||||
let counter = 1;
|
||||
|
||||
while (await app.vault.adapter.exists(filePath)) {
|
||||
fileName = `${defaultName} ${counter}`;
|
||||
filePath = `${basePath}/${fileName}.csv`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
try {
|
||||
const newFile = await app.vault.create(filePath, csvTemplate);
|
||||
|
||||
const leaf = app.workspace.getLeaf(false);
|
||||
await leaf.openFile(newFile);
|
||||
|
||||
const fileExplorer = app.workspace.getLeavesOfType('file-explorer')[0]?.view;
|
||||
if (fileExplorer) {
|
||||
await(fileExplorer as any).revealInFolder(newFile);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating CSV file:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const addCSVCreatorMenuItem = (menu: Menu, file: TAbstractFile) => {
|
||||
if (!(file instanceof TFolder)) {
|
||||
return
|
||||
}
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
.setTitle('New CSV file')
|
||||
.setSection('action-primary')
|
||||
.setIcon('table')
|
||||
.onClick(async () => {
|
||||
try {
|
||||
await createNewCSVFile(file)
|
||||
} catch (error) {
|
||||
console.error('Failed to create CSV file:', error)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
plugin.registerEvent(
|
||||
app.workspace.on('file-menu', addCSVCreatorMenuItem)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/modules/contextMenu/module.ts
Normal file
16
src/modules/contextMenu/module.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { asFactory, buildContainer } from "@hypersphere/dity";
|
||||
import { ContextMenuInit } from "./init";
|
||||
import { App, Plugin } from "obsidian";
|
||||
|
||||
export const contextMenu = buildContainer(c =>
|
||||
c.register({
|
||||
init: asFactory(ContextMenuInit)
|
||||
})
|
||||
.externals<{
|
||||
app: App,
|
||||
plugin: Plugin
|
||||
}>()
|
||||
.exports('init')
|
||||
)
|
||||
|
||||
export type ContextMenuModule = typeof contextMenu
|
||||
|
|
@ -3,19 +3,19 @@ import * as Comlink from 'comlink'
|
|||
// @ts-ignore
|
||||
import workerCode from 'virtual:worker-code'
|
||||
import { WorkerDatabase } from "./worker/database";
|
||||
import { sanitise } from "../utils/sanitiseColumn";
|
||||
import { ColumnDefinition } from "../utils/types";
|
||||
import { ColumnDefinition } from "../../utils/types";
|
||||
import { sanitise } from "../../utils/sanitiseColumn";
|
||||
|
||||
export class SqlSealDatabase {
|
||||
db: Comlink.Remote<WorkerDatabase>
|
||||
db?: Comlink.Remote<WorkerDatabase>
|
||||
private isConnected = false
|
||||
private connectingPromise: Promise<void>;
|
||||
constructor(private readonly app: App, private readonly verbose = false) {
|
||||
private connectingPromise?: Promise<void>;
|
||||
constructor(private readonly app: App) {
|
||||
|
||||
}
|
||||
|
||||
registerCustomFunction(name: string, argsCount = 1) {
|
||||
return this.db.registerCustomFunction(name, argsCount)
|
||||
return this.db?.registerCustomFunction(name, argsCount)
|
||||
}
|
||||
|
||||
async connect() {
|
||||
|
|
@ -56,62 +56,62 @@ export class SqlSealDatabase {
|
|||
if (!this.isConnected) {
|
||||
return
|
||||
}
|
||||
this.db.disconnect()
|
||||
this.db?.disconnect()
|
||||
this.isConnected = false
|
||||
}
|
||||
|
||||
async recreateDatabase() {
|
||||
await this.db.recreateDatabase()
|
||||
await this.db?.recreateDatabase()
|
||||
}
|
||||
|
||||
async updateData(name: string, data: Array<Record<string, unknown>>, key: string = 'id') {
|
||||
return this.db.updateData(name, data, key)
|
||||
return this.db?.updateData(name, data, key)
|
||||
}
|
||||
|
||||
async deleteData(name: string, data: Array<Record<string, unknown>>, key: string = 'id') {
|
||||
return this.db.deleteData(name, data, key)
|
||||
return this.db?.deleteData(name, data, key)
|
||||
}
|
||||
|
||||
async insertData(name: string, inData: Array<Record<string, unknown>>) {
|
||||
return this.db.insertData(name, inData)
|
||||
return this.db?.insertData(name, inData)
|
||||
}
|
||||
|
||||
async dropTable(name: string) {
|
||||
return this.db.dropTable(name)
|
||||
return this.db?.dropTable(name)
|
||||
}
|
||||
|
||||
async createTableNoTypes(name: string, columns: string[], noDrop?: boolean) {
|
||||
await this.db.createTableNoTypes(name, columns, noDrop)
|
||||
await this.db?.createTableNoTypes(name, columns, noDrop)
|
||||
}
|
||||
|
||||
async createTable(name: string, columns: ColumnDefinition[], noDrop?: boolean) {
|
||||
this.db.createTable(name, columns, noDrop)
|
||||
this.db?.createTable(name, columns, noDrop)
|
||||
}
|
||||
|
||||
async createIndex(indexName: string, tableName: string, columns: string[]) {
|
||||
await this.db.createIndex(indexName, tableName, columns)
|
||||
await this.db?.createIndex(indexName, tableName, columns)
|
||||
}
|
||||
|
||||
async getColumns(tableName: string) {
|
||||
return this.db.getColumns(tableName)
|
||||
return this.db?.getColumns(tableName)
|
||||
}
|
||||
|
||||
async addColumns(tableName: string, newColumns: string[]) {
|
||||
return this.db.addColumns(tableName, newColumns)
|
||||
return this.db?.addColumns(tableName, newColumns)
|
||||
}
|
||||
|
||||
async select(statement: string, frontmatter: Record<string, unknown>) {
|
||||
const result = await this.db.select(statement, frontmatter)
|
||||
const result = await this.db?.select(statement, frontmatter)
|
||||
return result
|
||||
}
|
||||
|
||||
async explain(statement: string, frontmatter: Record<string, unknown>) {
|
||||
const explainResults = await this.db.explainQuery(statement, frontmatter)
|
||||
const explainResults = await this.db?.explainQuery(statement, frontmatter)
|
||||
let strResult = '';
|
||||
const map = new Map<number, number>()
|
||||
const INDENT_INCREASE = 4
|
||||
map.set(0, -INDENT_INCREASE)
|
||||
for (const result of explainResults) {
|
||||
for (const result of explainResults || []) {
|
||||
const parent = parseInt((result.parent as unknown as string) ?? '0', 10)
|
||||
const indent = map.get(parent)! + INDENT_INCREASE
|
||||
for (let i=0;i<indent;i++) {
|
||||
16
src/modules/database/factory.ts
Normal file
16
src/modules/database/factory.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { App } from "obsidian";
|
||||
import { SqlSealDatabase } from "./database";
|
||||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { DbModel } from "./module";
|
||||
|
||||
|
||||
@(makeInjector<DbModel, 'factory'>()([
|
||||
'app'
|
||||
]))
|
||||
export class DatabaseFactory {
|
||||
async make(app: App) {
|
||||
const db = new SqlSealDatabase(app)
|
||||
await db.connect()
|
||||
return db
|
||||
}
|
||||
}
|
||||
13
src/modules/database/module.ts
Normal file
13
src/modules/database/module.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { asFactory, buildContainer } from "@hypersphere/dity";
|
||||
import { App } from "obsidian";
|
||||
import { DatabaseFactory } from "./factory";
|
||||
|
||||
export const db = buildContainer(c => c
|
||||
.register({
|
||||
db: asFactory(DatabaseFactory)
|
||||
})
|
||||
.externals<{ app: App }>()
|
||||
.exports('db')
|
||||
)
|
||||
|
||||
export type DbModel = typeof db
|
||||
|
|
@ -2,15 +2,15 @@ import * as Comlink from "comlink"
|
|||
// @ts-ignore
|
||||
import initSqlJs from '@jlongster/sql.js';
|
||||
// @ts-ignore
|
||||
import wasmBinary from '../../../node_modules/@jlongster/sql.js/dist/sql-wasm.wasm'
|
||||
import wasmBinary from '../../../../node_modules/@jlongster/sql.js/dist/sql-wasm.wasm'
|
||||
// @ts-ignore
|
||||
import { SQLiteFS } from 'absurd-sql';
|
||||
// @ts-ignore
|
||||
import IndexedDBBackend from '../../../node_modules/absurd-sql/dist/indexeddb-backend.js';
|
||||
import IndexedDBBackend from '../../../../node_modules/absurd-sql/dist/indexeddb-backend.js';
|
||||
import type { BindParams, Database, Statement } from "sql.js";
|
||||
import { sanitise } from "../../utils/sanitiseColumn";
|
||||
import { uniq, uniqBy } from "lodash";
|
||||
import { ColumnDefinition } from "../../utils/types";
|
||||
import { ColumnDefinition } from "../../../utils/types";
|
||||
import { sanitise } from "../../../utils/sanitiseColumn";
|
||||
|
||||
|
||||
function toObjectArray(stmt: Statement) {
|
||||
43
src/modules/debug/DityGraphView.ts
Normal file
43
src/modules/debug/DityGraphView.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import { DityGraph } from '@hypersphere/dity-graph'
|
||||
import { mainModule } from "../main/module";
|
||||
import { settingsModule } from "../settings/module";
|
||||
|
||||
export const VIEW_TYPE_EXAMPLE = "example-view";
|
||||
|
||||
export class DityGraphView extends ItemView {
|
||||
constructor(leaf: WorkspaceLeaf) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
getViewType() {
|
||||
return VIEW_TYPE_EXAMPLE;
|
||||
}
|
||||
|
||||
getDisplayText() {
|
||||
return "Example sidebar";
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
// container.createEl("h4", { text: "Example Sidebar" });
|
||||
// container.createEl("p", { text: "Your sidebar content goes here" });
|
||||
const el = container.createEl('div')
|
||||
el.style = 'width: 100%; height: 100%'
|
||||
|
||||
const module = settingsModule.resolve({ plugin: { } as any, app: {} as any, cellParser: { } as any})
|
||||
// .resolve({
|
||||
// 'obsidian.app': {} as any,
|
||||
// 'obsidian.plugin': {} as any,
|
||||
// 'obsidian.vault': {} as any,
|
||||
// })
|
||||
|
||||
const dityGraph = new DityGraph(module as any, el)
|
||||
dityGraph.render()
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
// Clean up if needed
|
||||
}
|
||||
}
|
||||
39
src/modules/debug/dityGraph.ts
Normal file
39
src/modules/debug/dityGraph.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { DebugModule } from "./module";
|
||||
import { Plugin, WorkspaceLeaf } from "obsidian";
|
||||
import { DityGraphView, VIEW_TYPE_EXAMPLE } from "./DityGraphView";
|
||||
|
||||
@(makeInjector<DebugModule, 'factory'>()(
|
||||
['plugin']
|
||||
))
|
||||
export class DityGraph {
|
||||
make(plugin: Plugin) {
|
||||
|
||||
const activateView = async () => {
|
||||
const { workspace } = plugin.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
const leaves = workspace.getLeavesOfType(VIEW_TYPE_EXAMPLE);
|
||||
|
||||
if (leaves.length > 0) {
|
||||
// A leaf with our view already exists, use that
|
||||
leaf = leaves[0];
|
||||
} else {
|
||||
// Our view could not be found in the workspace, create a new leaf
|
||||
// in the right sidebar for it
|
||||
leaf = workspace.getRightLeaf(false);
|
||||
if (!leaf) { return }
|
||||
await leaf.setViewState({ type: VIEW_TYPE_EXAMPLE, active: true });
|
||||
}
|
||||
|
||||
// "Reveal" the leaf in case it is in a collapsed sidebar
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
return () => {
|
||||
plugin.registerView(VIEW_TYPE_EXAMPLE, leaf => new DityGraphView(leaf))
|
||||
plugin.addRibbonIcon('dice', 'Dity Graph', activateView)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/modules/debug/module.ts
Normal file
16
src/modules/debug/module.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { asFactory, buildContainer } from "@hypersphere/dity";
|
||||
import { DityGraph } from "./dityGraph";
|
||||
import { Plugin } from "obsidian";
|
||||
|
||||
export const debugModule = buildContainer(c =>
|
||||
c
|
||||
.register({
|
||||
init: asFactory(DityGraph)
|
||||
})
|
||||
.exports('init')
|
||||
.externals<{
|
||||
plugin: Plugin
|
||||
}>()
|
||||
)
|
||||
|
||||
export type DebugModule = typeof debugModule
|
||||
210
src/modules/editor/codeblockHandler/CodeblockProcessor.ts
Normal file
210
src/modules/editor/codeblockHandler/CodeblockProcessor.ts
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
import { OmnibusRegistrator } from "@hypersphere/omnibus";
|
||||
import {
|
||||
App,
|
||||
MarkdownPostProcessorContext,
|
||||
MarkdownRenderChild,
|
||||
} from "obsidian";
|
||||
import { Sync } from "../../sync/sync/sync";
|
||||
import { RendererRegistry, RenderReturn } from "../renderer/rendererRegistry";
|
||||
import { ParserResult, parseWithDefaults, TableDefinition } from "../parser";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { displayError, displayNotice } from "../../../utils/ui";
|
||||
import { transformQuery } from "../sql/sqlTransformer";
|
||||
import { registerObservers } from "../../../utils/registerObservers";
|
||||
import { Settings } from "../../settings/Settings";
|
||||
import { ModernCellParser } from "../../syntaxHighlight/cellParser/ModernCellParser";
|
||||
|
||||
export class CodeblockProcessor extends MarkdownRenderChild {
|
||||
registrator: OmnibusRegistrator;
|
||||
renderer: RenderReturn;
|
||||
private flags: ParserResult["flags"];
|
||||
private extrasEl: HTMLElement;
|
||||
private explainEl: HTMLElement;
|
||||
|
||||
constructor(
|
||||
private el: HTMLElement,
|
||||
private source: string,
|
||||
private ctx: MarkdownPostProcessorContext,
|
||||
private rendererRegistry: RendererRegistry,
|
||||
private db: SqlSealDatabase,
|
||||
private cellParser: ModernCellParser,
|
||||
private settings: Settings,
|
||||
private app: App,
|
||||
private sync: Sync,
|
||||
) {
|
||||
super(el);
|
||||
|
||||
this.registrator = this.sync.getRegistrator();
|
||||
}
|
||||
|
||||
query: string;
|
||||
|
||||
async onload() {
|
||||
try {
|
||||
const defaults: ParserResult = {
|
||||
flags: {
|
||||
refresh: this.settings.get("enableDynamicUpdates"),
|
||||
explain: false,
|
||||
},
|
||||
query: "",
|
||||
renderer: {
|
||||
options: "",
|
||||
type: this.settings.get("defaultView").toUpperCase(),
|
||||
},
|
||||
tables: [],
|
||||
};
|
||||
|
||||
const results = parseWithDefaults(
|
||||
this.source,
|
||||
this.rendererRegistry.getViewDefinitions(),
|
||||
defaults,
|
||||
this.rendererRegistry.flags,
|
||||
);
|
||||
|
||||
if (results.tables) {
|
||||
await this.registerTables(results.tables);
|
||||
if (!results.query) {
|
||||
displayNotice(
|
||||
this.el,
|
||||
`Creating tables: ${results.tables.map((t) => t.tableAlias).join(", ")}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.flags = results.flags;
|
||||
let rendererEl = this.el;
|
||||
|
||||
if (this.flags.explain) {
|
||||
this.extrasEl = this.el.createDiv({ cls: "sqlseal-extras-container" });
|
||||
if (this.flags.explain) {
|
||||
this.explainEl = this.extrasEl.createEl("pre", {
|
||||
cls: "sqlseal-extras-explain-container",
|
||||
});
|
||||
}
|
||||
rendererEl = this.el.createDiv({ cls: "sqlseal-renderer-container" });
|
||||
}
|
||||
|
||||
// IF WE'RE ON CANVAS, LETS ADD BACKGRUND
|
||||
if (this.isOnCanvas) {
|
||||
rendererEl.classList.add('sqlseal-renderer-on-canvas')
|
||||
}
|
||||
|
||||
this.renderer = this.rendererRegistry.prepareRender(
|
||||
results.renderer.type.toLowerCase(),
|
||||
results.renderer.options,
|
||||
)(rendererEl, {
|
||||
cellParser: this.cellParser,
|
||||
sourcePath: this.sourceKey,
|
||||
});
|
||||
|
||||
// FIXME: probably should save the one before transform and perform transform every time we execute it.
|
||||
this.query = results.query;
|
||||
await this.render();
|
||||
} catch (e) {
|
||||
displayError(this.el, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.registrator.offAll();
|
||||
if (this.renderer?.cleanup) {
|
||||
this.renderer.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
async render() {
|
||||
try {
|
||||
const registeredTablesForContext =
|
||||
await this.sync.getTablesMappingForContext(this.sourceKey);
|
||||
|
||||
const res = transformQuery(this.query, registeredTablesForContext);
|
||||
const transformedQuery = res.sql;
|
||||
|
||||
if (this.flags.refresh) {
|
||||
registerObservers({
|
||||
bus: this.registrator,
|
||||
callback: () => this.render(),
|
||||
fileName: this.sourceKey,
|
||||
tables: res.mappedTables,
|
||||
});
|
||||
}
|
||||
|
||||
let variables = {};
|
||||
const file = this.app.vault.getFileByPath(this.sourceKey);
|
||||
if (file) {
|
||||
const fileCache = this.app.metadataCache.getFileCache(file);
|
||||
variables = {
|
||||
...(fileCache?.frontmatter ?? {}),
|
||||
path: file.path,
|
||||
fileName: file.name,
|
||||
basename: file.basename,
|
||||
parent: file.parent?.path,
|
||||
extension: file.extension,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.flags.explain) {
|
||||
// Rendering explain
|
||||
const result = await this.db.explain(transformedQuery, variables);
|
||||
this.explainEl.textContent = result;
|
||||
}
|
||||
|
||||
const { data, columns } = (await this.db.select(
|
||||
transformedQuery,
|
||||
variables,
|
||||
))!; // FIXME: check this
|
||||
this.renderer.render({
|
||||
data,
|
||||
columns,
|
||||
flags: this.flags,
|
||||
frontmatter: variables,
|
||||
});
|
||||
} catch (e) {
|
||||
this.renderer.error(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private cachedName: string | null = null
|
||||
|
||||
get isOnCanvas() {
|
||||
return !this.ctx.sourcePath
|
||||
}
|
||||
|
||||
get canvasName() {
|
||||
// This is hack to detect what name has current canvas.
|
||||
// It's not fool proof but should work for majority of use-cases for now.
|
||||
// We need to find proper way of getting it or ask Obsidian devs to expose some info.
|
||||
|
||||
if (this.cachedName !== null) {
|
||||
return this.cachedName
|
||||
}
|
||||
const canvasViews = this.app.workspace.getLeavesOfType("canvas");
|
||||
|
||||
for (const leaf of canvasViews) {
|
||||
const nodes = JSON.parse(leaf.view.data).nodes
|
||||
const node = nodes.filter(n => n.text).find(n => n.text.contains(this.source))
|
||||
if (node) {
|
||||
this.cachedName = leaf.view.file.path
|
||||
return this.cachedName as string
|
||||
}
|
||||
}
|
||||
this.cachedName = ''
|
||||
return ''
|
||||
}
|
||||
|
||||
get sourceKey() {
|
||||
return this.ctx.sourcePath.trim() ? this.ctx.sourcePath.trim() : this.canvasName;
|
||||
}
|
||||
|
||||
async registerTables(tables: TableDefinition[]) {
|
||||
await Promise.all(
|
||||
tables.map((table) =>
|
||||
this.sync.registerTable({
|
||||
...table,
|
||||
sourceFile: this.sourceKey,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,24 @@
|
|||
import { App, MarkdownPostProcessorContext } from "obsidian"
|
||||
import { SqlSealDatabase } from "../database/database"
|
||||
import { RendererRegistry } from "../renderer/rendererRegistry"
|
||||
import { Sync } from "../datamodel/sync"
|
||||
import { CodeblockProcessor } from "./CodeblockProcessor"
|
||||
import SqlSealPlugin from "../main"
|
||||
|
||||
import { makeInjector } from "@hypersphere/dity"
|
||||
import { EditorModule } from "../module"
|
||||
import { SqlSealDatabase } from "../../database/database"
|
||||
import { Sync } from "../../sync/sync/sync"
|
||||
import { Settings } from "../../settings/Settings"
|
||||
import { ModernCellParser } from "../../syntaxHighlight/cellParser/ModernCellParser"
|
||||
|
||||
@(makeInjector<EditorModule>()(
|
||||
['app', 'db', 'cellParser', 'sync', 'rendererRegistry', 'settings']
|
||||
))
|
||||
export class SqlSealCodeblockHandler {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly db: SqlSealDatabase,
|
||||
private readonly plugin: SqlSealPlugin,
|
||||
private readonly cellParser: ModernCellParser,
|
||||
private sync: Sync,
|
||||
private rendererRegistry: RendererRegistry
|
||||
private rendererRegistry: RendererRegistry,
|
||||
private settings: Settings
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +30,8 @@ export class SqlSealCodeblockHandler {
|
|||
ctx,
|
||||
this.rendererRegistry,
|
||||
this.db,
|
||||
this.plugin,
|
||||
this.cellParser,
|
||||
this.settings,
|
||||
this.app,
|
||||
this.sync
|
||||
)
|
||||
|
|
@ -1,14 +1,20 @@
|
|||
import { App, MarkdownPostProcessorContext } from "obsidian";
|
||||
import { App, MarkdownPostProcessorContext, Plugin } from "obsidian";
|
||||
import { InlineProcessor } from "./InlineProcessor";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { Sync } from "../../datamodel/sync";
|
||||
import SqlSealPlugin from "../../main";
|
||||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { EditorModule } from "../../module";
|
||||
import { SqlSealDatabase } from "../../../database/database";
|
||||
import { Sync } from "../../../sync/sync/sync";
|
||||
import { Settings } from "../../../settings/Settings";
|
||||
|
||||
|
||||
@(makeInjector<EditorModule>()(
|
||||
['app', 'db', 'settings', 'sync', 'rendererRegistry']
|
||||
))
|
||||
export class SqlSealInlineHandler {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly db: SqlSealDatabase,
|
||||
private readonly plugin: SqlSealPlugin,
|
||||
private readonly settings: Settings,
|
||||
private sync: Sync
|
||||
) { }
|
||||
|
||||
|
|
@ -25,7 +31,7 @@ export class SqlSealInlineHandler {
|
|||
query,
|
||||
sourcePath,
|
||||
this.db,
|
||||
this.plugin,
|
||||
this.settings,
|
||||
this.app,
|
||||
this.sync
|
||||
);
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { OmnibusRegistrator } from "@hypersphere/omnibus";
|
||||
import { App, MarkdownRenderChild } from "obsidian";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { Sync } from "../../datamodel/sync";
|
||||
import { transformQuery } from "../../sql/sqlTransformer";
|
||||
import { registerObservers } from "../../utils/registerObservers";
|
||||
import { displayError } from "../../utils/ui";
|
||||
import SqlSealPlugin from "../../main";
|
||||
import { SqlSealDatabase } from "../../../database/database";
|
||||
import { Sync } from "../../../sync/sync/sync";
|
||||
import { registerObservers } from "../../../../utils/registerObservers";
|
||||
import { displayError } from "../../../../utils/ui";
|
||||
import { Settings } from "../../../settings/Settings";
|
||||
|
||||
export class InlineProcessor extends MarkdownRenderChild {
|
||||
private registrator: OmnibusRegistrator;
|
||||
|
|
@ -15,7 +15,7 @@ export class InlineProcessor extends MarkdownRenderChild {
|
|||
private query: string,
|
||||
private sourcePath: string,
|
||||
private db: SqlSealDatabase,
|
||||
private plugin: SqlSealPlugin,
|
||||
private settings: Settings,
|
||||
private app: App,
|
||||
private sync: Sync
|
||||
) {
|
||||
|
|
@ -40,7 +40,8 @@ export class InlineProcessor extends MarkdownRenderChild {
|
|||
const registeredTablesForContext = await this.sync.getTablesMappingForContext(this.sourcePath);
|
||||
const transformedQuery = transformQuery(this.query, registeredTablesForContext);
|
||||
|
||||
if (this.plugin.settings.enableDynamicUpdates) {
|
||||
// FIXME: settings here instead of plugin
|
||||
if (this.settings.get('enableDynamicUpdates')) {
|
||||
registerObservers({
|
||||
bus: this.registrator,
|
||||
tables: transformedQuery.mappedTables,
|
||||
|
|
@ -66,10 +67,10 @@ export class InlineProcessor extends MarkdownRenderChild {
|
|||
extension: file.extension,
|
||||
}
|
||||
|
||||
const { data, columns } = await this.db.select(
|
||||
const { data, columns } = (await this.db.select(
|
||||
transformedQuery.sql,
|
||||
variables
|
||||
);
|
||||
))!; // FIXME: better code here.
|
||||
|
||||
this.el.empty()
|
||||
let value = data[0][columns[0]] ?? ''
|
||||
85
src/modules/editor/init.ts
Normal file
85
src/modules/editor/init.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
import { Sync } from "../sync/sync/sync";
|
||||
import { EditorModule } from "./module";
|
||||
import { RendererRegistry } from "./renderer/rendererRegistry";
|
||||
import { TableRenderer } from "./renderer/TableRenderer";
|
||||
import { GridRenderer } from "./renderer/GridRenderer";
|
||||
import { ListRenderer } from "./renderer/ListRenderer";
|
||||
import { TemplateRenderer } from "./renderer/TemplateRenderer";
|
||||
import { MarkdownRenderer } from "./renderer/MarkdownRenderer";
|
||||
import { Settings } from "../settings/Settings";
|
||||
import { SqlSealInlineHandler } from "./codeblockHandler/inline/InlineCodeHandler";
|
||||
import { SqlSealCodeblockHandler } from "./codeblockHandler/SqlSealCodeblockHandler";
|
||||
import { createSqlSealEditorExtension } from "../syntaxHighlight/editorExtension/inlineCodeBlock";
|
||||
|
||||
@(makeInjector<EditorModule, 'factory'>()([
|
||||
'app', 'db', 'plugin', 'sync', 'inlineHandler', 'blockHandler', 'rendererRegistry', 'settings'
|
||||
]))
|
||||
export class EditorInit {
|
||||
make(
|
||||
app: App,
|
||||
db: SqlSealDatabase,
|
||||
plugin: Plugin,
|
||||
sync: Sync,
|
||||
inlineHandler: SqlSealInlineHandler,
|
||||
blockHandler: SqlSealCodeblockHandler,
|
||||
rendererRegistry: RendererRegistry,
|
||||
settings: Settings
|
||||
) {
|
||||
|
||||
const registerInlineCodeblocks = () => {
|
||||
|
||||
// Extension for Live Preview
|
||||
const editorExtension = createSqlSealEditorExtension(
|
||||
app,
|
||||
db,
|
||||
settings,
|
||||
sync,
|
||||
);
|
||||
|
||||
plugin.registerEditorExtension(editorExtension);
|
||||
|
||||
// Extension for Read mode
|
||||
plugin.registerMarkdownPostProcessor((el, ctx) => {
|
||||
const inlineCodeBlocks = el.querySelectorAll('code');
|
||||
inlineCodeBlocks.forEach((node: HTMLSpanElement) => {
|
||||
const text = node.innerText;
|
||||
if (text.startsWith('S>')) {
|
||||
const container = createEl('span', { cls: 'sqlseal-inline-result' });
|
||||
container.setAttribute('aria-label', text.slice(3));
|
||||
container.classList.add('has-tooltip');
|
||||
node.replaceWith(container);
|
||||
inlineHandler.getHandler()(text, container, ctx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const registerBlockCodeblock = () => {
|
||||
plugin.registerMarkdownCodeBlockProcessor('sqlseal', blockHandler.getHandler())
|
||||
}
|
||||
|
||||
const registerViews = () => {
|
||||
|
||||
rendererRegistry.register('sql-seal-internal-table', new TableRenderer(app))
|
||||
rendererRegistry.register('sql-seal-internal-grid', new GridRenderer(settings, plugin, app))
|
||||
rendererRegistry.register('sql-seal-internal-markdown', new MarkdownRenderer(app))
|
||||
rendererRegistry.register('sql-seal-internal-list', new ListRenderer(app))
|
||||
rendererRegistry.register('sql-seal-internal-template', new TemplateRenderer(app))
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
||||
registerViews()
|
||||
|
||||
app.workspace.onLayoutReady(async () => {
|
||||
registerInlineCodeblocks()
|
||||
registerBlockCodeblock()
|
||||
})
|
||||
// FIXME: block
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/modules/editor/module.ts
Normal file
30
src/modules/editor/module.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { asClass, asFactory, buildContainer } from "@hypersphere/dity"
|
||||
import { App, Plugin } from "obsidian"
|
||||
import { SqlSealDatabase } from "../database/database"
|
||||
import { Sync } from "../sync/sync/sync"
|
||||
import { RendererRegistry } from "./renderer/rendererRegistry"
|
||||
import { EditorInit } from "./init"
|
||||
import { SqlSealCodeblockHandler } from "./codeblockHandler/SqlSealCodeblockHandler"
|
||||
import { SqlSealInlineHandler } from "./codeblockHandler/inline/InlineCodeHandler"
|
||||
import { Settings } from "../settings/Settings"
|
||||
import { ModernCellParser } from "../syntaxHighlight/cellParser/ModernCellParser"
|
||||
|
||||
export const editor = buildContainer(c => c
|
||||
.register({
|
||||
blockHandler: asClass(SqlSealCodeblockHandler),
|
||||
inlineHandler: asClass(SqlSealInlineHandler),
|
||||
rendererRegistry: asClass(RendererRegistry),
|
||||
init: asFactory(EditorInit)
|
||||
})
|
||||
.externals<{
|
||||
app: App,
|
||||
db: SqlSealDatabase,
|
||||
plugin: Plugin,
|
||||
sync: Sync,
|
||||
cellParser: ModernCellParser,
|
||||
settings: Settings
|
||||
}>()
|
||||
.exports('rendererRegistry', 'init')
|
||||
)
|
||||
|
||||
export type EditorModule = typeof editor
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import * as ohm from 'ohm-js';
|
||||
import { Flag } from '../renderer/rendererRegistry';
|
||||
import { Flag } from './renderer/rendererRegistry';
|
||||
|
||||
export interface ViewDefinition {
|
||||
name: string,
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { createGrid, GridApi, GridOptions, themeQuartz } from "ag-grid-community";
|
||||
import { merge } from "lodash";
|
||||
import { App } from "obsidian";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { RendererConfig, RendererContext } from "./rendererRegistry";
|
||||
import { parse } from 'json5'
|
||||
import { ViewDefinition } from "../grammar/parser";
|
||||
import SqlSealPlugin from "../main";
|
||||
import { ModernCellParser } from "../cellParser/ModernCellParser";
|
||||
import { parse } from 'json5';
|
||||
import { EventRef } from "obsidian";
|
||||
import { Settings } from "../../settings/Settings";
|
||||
import { ViewDefinition } from "../parser";
|
||||
import { ModernCellParser } from "../../syntaxHighlight/cellParser/ModernCellParser";
|
||||
|
||||
interface DataParam {
|
||||
data: Record<string, unknown>[],
|
||||
|
|
@ -35,7 +35,8 @@ export class GridRendererCommunicator {
|
|||
constructor(
|
||||
private el: HTMLElement,
|
||||
private config: Partial<GridOptions>,
|
||||
private plugin: SqlSealPlugin | null,
|
||||
private plugin: Plugin | null,
|
||||
private settings: Settings,
|
||||
private app: App,
|
||||
private cellParser?: ModernCellParser
|
||||
) {
|
||||
|
|
@ -131,7 +132,7 @@ export class GridRendererCommunicator {
|
|||
columnDefs: [],
|
||||
domLayout: 'autoHeight',
|
||||
enableCellTextSelection: true,
|
||||
paginationPageSize: this.plugin? this.plugin.settings.gridItemsPerPage : undefined,
|
||||
paginationPageSize: this.settings.get('gridItemsPerPage') ?? 10
|
||||
// ensureDomOrder: true
|
||||
}, this.config)
|
||||
this._gridApi = createGrid(
|
||||
|
|
@ -165,7 +166,7 @@ export class GridRendererCommunicator {
|
|||
}
|
||||
|
||||
export class GridRenderer implements RendererConfig {
|
||||
constructor(private app: App, private readonly plugin: SqlSealPlugin | null) { }
|
||||
constructor(private settings: Settings, private readonly plugin: Plugin | null, private readonly app: App) { }
|
||||
get viewDefinition(): ViewDefinition {
|
||||
return {
|
||||
name: this.rendererKey,
|
||||
|
|
@ -188,7 +189,7 @@ export class GridRenderer implements RendererConfig {
|
|||
|
||||
|
||||
render(config: Partial<GridOptions>, el: HTMLElement, { cellParser }: RendererContext) {
|
||||
const communicator = new GridRendererCommunicator(el, config, this.plugin, this.app, cellParser)
|
||||
const communicator = new GridRendererCommunicator(el, config, this.plugin, this.settings, this.app, cellParser)
|
||||
return {
|
||||
render: (data: DataParam) => {
|
||||
communicator.setData(data.columns ?? [], data.data)
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// This is renderer for a very basic List view.
|
||||
import { App } from "obsidian";
|
||||
import { RendererConfig, RendererContext } from "../renderer/rendererRegistry";
|
||||
import { displayError } from "../utils/ui";
|
||||
import { ViewDefinition } from "../grammar/parser";
|
||||
import { RendererConfig, RendererContext } from "./rendererRegistry";
|
||||
import { displayError } from "../../../utils/ui";
|
||||
import { ViewDefinition } from "../parser";
|
||||
|
||||
interface ListRendererConfig {
|
||||
classNames: string[]
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
// This is renderer for a very basic Table view.
|
||||
import { getMarkdownTable } from "markdown-table-ts";
|
||||
import { App } from "obsidian";
|
||||
import { RendererConfig, RendererContext } from "../renderer/rendererRegistry";
|
||||
import { displayError } from "../utils/ui";
|
||||
import { ViewDefinition } from "../grammar/parser";
|
||||
import { ParseResults } from "../cellParser/parseResults";
|
||||
import { RendererConfig, RendererContext } from "./rendererRegistry";
|
||||
import { displayError } from "../../../utils/ui";
|
||||
import { ViewDefinition } from "../parser";
|
||||
import { ParseResults } from "../../syntaxHighlight/cellParser/parseResults";
|
||||
|
||||
const mapDataFromHeaders = (columns: string[], data: Record<string, any>[]) => {
|
||||
return data.map(d => columns.map(c => String(d[c])))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// This is renderer for a very basic Table view.
|
||||
import { App } from "obsidian";
|
||||
import { RendererConfig, RendererContext } from "../renderer/rendererRegistry";
|
||||
import { displayError } from "../utils/ui";
|
||||
import { ViewDefinition } from "../grammar/parser";
|
||||
import { RendererConfig, RendererContext } from "./rendererRegistry";
|
||||
import { displayError } from "../../../utils/ui";
|
||||
import { ViewDefinition } from "../parser";
|
||||
|
||||
interface HTMLRendererConfig {
|
||||
classNames: string[]
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
// This is renderer for a very basic List view.
|
||||
import { App } from "obsidian";
|
||||
import { RendererConfig, RendererContext } from "./rendererRegistry";
|
||||
import { displayError } from "../utils/ui";
|
||||
import { ViewDefinition } from "../grammar/parser";
|
||||
import { displayError } from "../../../utils/ui";
|
||||
import Handlebars from "handlebars";
|
||||
import { ParseResults } from "../cellParser/parseResults";
|
||||
import { ViewDefinition } from "../parser";
|
||||
import { ParseResults } from "../../syntaxHighlight/cellParser/parseResults";
|
||||
|
||||
interface TemplateRendererConfig {
|
||||
template: HandlebarsTemplateDelegate
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { ModernCellParser } from "../cellParser/ModernCellParser";
|
||||
import { ViewDefinition } from "../grammar/parser";
|
||||
import { ModernCellParser } from "../../syntaxHighlight/cellParser/ModernCellParser";
|
||||
import { ViewDefinition } from "../parser";
|
||||
|
||||
export interface DataFormat {
|
||||
data: Record<string, any>[],
|
||||
36
src/modules/main/init.ts
Normal file
36
src/modules/main/init.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { MainModule } from "./module";
|
||||
|
||||
type InitFn = () => void
|
||||
|
||||
|
||||
@(makeInjector<MainModule, 'factory'>()([
|
||||
'settings.init',
|
||||
'editor.init',
|
||||
'syntaxHighlight.init',
|
||||
'contextMenu.init',
|
||||
'sync.init',
|
||||
'debug.init',
|
||||
'api.init'
|
||||
]))
|
||||
export class Init {
|
||||
async make(
|
||||
settingsInit: InitFn,
|
||||
editorInit: InitFn,
|
||||
highlighInit: InitFn,
|
||||
contextMenu: InitFn,
|
||||
syncInit: InitFn,
|
||||
debugInit: InitFn,
|
||||
apiInit: InitFn
|
||||
) {
|
||||
return () => {
|
||||
settingsInit()
|
||||
editorInit()
|
||||
highlighInit()
|
||||
contextMenu()
|
||||
syncInit()
|
||||
debugInit()
|
||||
apiInit()
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/modules/main/module.ts
Normal file
82
src/modules/main/module.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { asFactory, buildContainer } from '@hypersphere/dity'
|
||||
import { App, Plugin, Vault } from 'obsidian'
|
||||
import { db } from '../database/module'
|
||||
import { editor } from '../editor/module'
|
||||
import { sync } from '../sync/module'
|
||||
import { SQLSealSettings } from '../settings/SQLSealSettingsTab'
|
||||
import { Init } from './init'
|
||||
import { settingsModule } from '../settings/module'
|
||||
import { syntaxHighlight } from '../syntaxHighlight/module'
|
||||
import { contextMenu } from '../contextMenu/module'
|
||||
import { debugModule } from '../debug/module'
|
||||
import { apiModule } from '../api/module'
|
||||
|
||||
const obsidian = buildContainer(c => c
|
||||
.externals<{
|
||||
app: App,
|
||||
plugin: Plugin,
|
||||
vault: Vault
|
||||
}>()
|
||||
)
|
||||
|
||||
|
||||
export const mainModule = buildContainer(c => c
|
||||
.submodules({
|
||||
obsidian,
|
||||
db,
|
||||
editor,
|
||||
sync,
|
||||
settings: settingsModule,
|
||||
syntaxHighlight,
|
||||
contextMenu,
|
||||
debug: debugModule,
|
||||
api: apiModule
|
||||
})
|
||||
.register({
|
||||
init: asFactory(Init)
|
||||
})
|
||||
.externals<{ settings: SQLSealSettings }>()
|
||||
.resolve({
|
||||
'db.app': 'obsidian.app',
|
||||
})
|
||||
.resolve({
|
||||
'editor.app': 'obsidian.app', // THESE SHOULD BE INVALID NOW
|
||||
'editor.db': 'db.db',
|
||||
'editor.plugin': 'obsidian.plugin',
|
||||
'editor.sync': 'sync.syncBus',
|
||||
'editor.cellParser': 'syntaxHighlight.cellParser',
|
||||
'editor.settings': 'settings.settings'
|
||||
})
|
||||
.resolve({
|
||||
'sync.app': 'obsidian.app',
|
||||
'sync.db': 'db.db',
|
||||
'sync.plugin': 'obsidian.plugin',
|
||||
'sync.vault': 'obsidian.vault',
|
||||
})
|
||||
.resolve({
|
||||
'settings.app': 'obsidian.app',
|
||||
'settings.plugin': 'obsidian.plugin',
|
||||
'settings.cellParser': 'syntaxHighlight.cellParser',
|
||||
})
|
||||
.resolve({
|
||||
'syntaxHighlight.app': 'obsidian.app',
|
||||
'syntaxHighlight.db': 'db.db',
|
||||
'syntaxHighlight.plugin': 'obsidian.plugin',
|
||||
'syntaxHighlight.rendererRegistry': 'editor.rendererRegistry'
|
||||
})
|
||||
.resolve({
|
||||
'contextMenu.app': 'obsidian.app',
|
||||
'contextMenu.plugin': 'obsidian.plugin'
|
||||
})
|
||||
.resolve({
|
||||
'debug.plugin': 'obsidian.plugin'
|
||||
})
|
||||
.resolve({
|
||||
'api.plugin': 'obsidian.plugin',
|
||||
'api.cellParser': 'syntaxHighlight.cellParser',
|
||||
'api.db': 'db.db',
|
||||
'api.rendererRegistry': 'editor.rendererRegistry'
|
||||
})
|
||||
)
|
||||
|
||||
export type MainModule = typeof mainModule
|
||||
124
src/modules/settings/SQLSealSettingsTab.ts
Normal file
124
src/modules/settings/SQLSealSettingsTab.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { makeInjector } from '@hypersphere/dity';
|
||||
import { App, PluginSettingTab, Setting, Plugin } from 'obsidian';
|
||||
import { SettingsModule } from './module';
|
||||
import { Settings } from './Settings';
|
||||
import { SettingsCSVControls } from './settingsTabSection/SettingsCSVControls';
|
||||
import { SettingsJsonControls } from './settingsTabSection/SettingsJsonControls';
|
||||
import { SettingsControls } from './settingsTabSection/SettingsControls';
|
||||
|
||||
export interface SQLSealSettings {
|
||||
enableViewer: boolean;
|
||||
enableEditing: boolean;
|
||||
enableJSONViewer: boolean;
|
||||
enableDynamicUpdates: boolean;
|
||||
enableSyntaxHighlighting: boolean;
|
||||
defaultView: 'grid' | 'markdown' | 'html';
|
||||
gridItemsPerPage: number
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: SQLSealSettings = {
|
||||
enableViewer: true,
|
||||
enableEditing: true,
|
||||
enableJSONViewer: true,
|
||||
enableDynamicUpdates: true,
|
||||
enableSyntaxHighlighting: true,
|
||||
defaultView: 'grid',
|
||||
gridItemsPerPage: 20
|
||||
};
|
||||
|
||||
|
||||
@(makeInjector<SettingsModule>()(['app', 'plugin', 'settings']))
|
||||
export class SQLSealSettingsTab extends PluginSettingTab {
|
||||
plugin: Plugin;
|
||||
// settings: SQLSealSettings;
|
||||
private onChangeFns: Array<(setting: SQLSealSettings) => void> = []
|
||||
|
||||
constructor(app: App, plugin: Plugin, private settings: Settings) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
private controls: SettingsControls[] = []
|
||||
|
||||
registerControls(...controls: SettingsControls[]) {
|
||||
this.controls = controls
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
this.controls.forEach(c => {
|
||||
c.display(containerEl.createDiv())
|
||||
})
|
||||
|
||||
|
||||
containerEl.createEl('h3', { text: 'Behavior' });
|
||||
new Setting(containerEl)
|
||||
.setName('Enable Dynamic Updates')
|
||||
.setDesc('SQLSeal will refresh your tables when underlying data changes.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.settings.get('enableDynamicUpdates'))
|
||||
.onChange(async (value) => {
|
||||
this.settings.set('enableDynamicUpdates', !!value)
|
||||
// await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
// this.callChanges()
|
||||
}));
|
||||
new Setting(containerEl)
|
||||
.setName('Enable Syntax Highlighting')
|
||||
.setDesc('Syntax will get highlighted when editing SQLSeal code')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.settings.get('enableSyntaxHighlighting'))
|
||||
.onChange(async (value) => {
|
||||
this.settings.set('enableSyntaxHighlighting', !!value)
|
||||
// await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
// this.callChanges()
|
||||
}));
|
||||
|
||||
|
||||
containerEl.createEl('h3', { text: 'Views' });
|
||||
new Setting(containerEl)
|
||||
.setName('Default View')
|
||||
.setDesc('This view will be used by default when you don\'t provide any view definition in your query')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('grid', 'Grid')
|
||||
.addOption('html', 'HTML Table')
|
||||
.addOption('markdown', 'Markdown Table')
|
||||
.setValue(this.settings.get('defaultView'))
|
||||
.onChange(async (value: 'grid' | 'html' | 'markdown') => {
|
||||
this.settings.set('defaultView', value ?? DEFAULT_SETTINGS.defaultView)
|
||||
// await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
// this.callChanges()
|
||||
}));
|
||||
containerEl.createEl('h4', { text: 'Grid View' });
|
||||
new Setting(containerEl)
|
||||
.setName('Items per page ')
|
||||
.setDesc('How many items should display for each page of the Grid view')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('20', '20')
|
||||
.addOption('50', '50')
|
||||
.addOption('100', '100')
|
||||
.setValue(this.settings.get('gridItemsPerPage').toString())
|
||||
.onChange(async (value) => {
|
||||
this.settings.set('gridItemsPerPage', parseInt(value, 10) ?? DEFAULT_SETTINGS.gridItemsPerPage)
|
||||
// await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
// this.callChanges()
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
|
||||
// private callChanges() {
|
||||
// // this.onChangeFns.forEach(f => f(this.settings))
|
||||
// }
|
||||
|
||||
onChange(fn: (settings: SQLSealSettings) => void) {
|
||||
this.settings.onChange(fn)
|
||||
// this.onChangeFns.push(fn)
|
||||
}
|
||||
}
|
||||
25
src/modules/settings/Settings.ts
Normal file
25
src/modules/settings/Settings.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { args, BusBuilder, CallbackType } from "@hypersphere/omnibus";
|
||||
import { SQLSealSettings } from "./SQLSealSettingsTab";
|
||||
|
||||
export class Settings {
|
||||
bus = BusBuilder
|
||||
.init()
|
||||
.register('change', args<SQLSealSettings>())
|
||||
.build()
|
||||
|
||||
constructor(private settings: SQLSealSettings) {
|
||||
}
|
||||
|
||||
get<K extends keyof SQLSealSettings>(key: K): SQLSealSettings[K] {
|
||||
return this.settings[key]
|
||||
}
|
||||
|
||||
set<K extends keyof SQLSealSettings>(key: K, val: SQLSealSettings[K]) {
|
||||
this.settings[key] = val
|
||||
this.bus.trigger('change', this.settings)
|
||||
}
|
||||
|
||||
onChange(fn: CallbackType<[SQLSealSettings]>) {
|
||||
return this.bus.on('change', fn)
|
||||
}
|
||||
}
|
||||
32
src/modules/settings/init.ts
Normal file
32
src/modules/settings/init.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { SettingsModule } from "./module";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { SQLSealSettingsTab } from "./SQLSealSettingsTab";
|
||||
import { Settings } from "./Settings";
|
||||
import { SettingsCSVControls } from "./settingsTabSection/SettingsCSVControls";
|
||||
import { SettingsJsonControls } from "./settingsTabSection/SettingsJsonControls";
|
||||
|
||||
@(makeInjector<SettingsModule>()(["plugin", "settingsTab", "app", "settings"]))
|
||||
export class SettingsInit {
|
||||
async make(
|
||||
plugin: Plugin,
|
||||
settingsTab: SQLSealSettingsTab,
|
||||
app: App,
|
||||
settings: Settings,
|
||||
) {
|
||||
const csvControl = new SettingsCSVControls(settings, app, plugin);
|
||||
const jsonControl = new SettingsJsonControls(settings, app, plugin);
|
||||
|
||||
const controls = [csvControl, jsonControl];
|
||||
|
||||
settingsTab.registerControls(...controls);
|
||||
|
||||
return () => {
|
||||
controls.forEach((c) => c.register());
|
||||
plugin.addSettingTab(settingsTab);
|
||||
plugin.register(() => {
|
||||
controls.forEach((c) => c.unregister());
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { App, Modal, Notice, Setting, TFile } from "obsidian";
|
||||
import { sanitise } from "../utils/sanitiseColumn";
|
||||
import { sanitise } from "../../../utils/sanitiseColumn";
|
||||
|
||||
export class CodeSampleModal extends Modal {
|
||||
constructor(app: App, private file: TFile) {
|
||||
22
src/modules/settings/module.ts
Normal file
22
src/modules/settings/module.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { asClass, asFactory, buildContainer } from "@hypersphere/dity";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { SettingsFactory } from "./settingsFactory";
|
||||
import { SQLSealSettingsTab } from "./SQLSealSettingsTab";
|
||||
import { SettingsInit } from "./init";
|
||||
import { ModernCellParser } from "../syntaxHighlight/cellParser/ModernCellParser";
|
||||
|
||||
export const settingsModule = buildContainer(c => c
|
||||
.externals<{
|
||||
'plugin': Plugin,
|
||||
'app': App,
|
||||
'cellParser': ModernCellParser
|
||||
}>()
|
||||
.register({
|
||||
'settings': asFactory(SettingsFactory),
|
||||
'settingsTab': asClass(SQLSealSettingsTab),
|
||||
init: asFactory(SettingsInit)
|
||||
})
|
||||
.exports('settings', 'init')
|
||||
)
|
||||
|
||||
export type SettingsModule = typeof settingsModule
|
||||
19
src/modules/settings/settingsFactory.ts
Normal file
19
src/modules/settings/settingsFactory.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { SettingsModule } from "./module";
|
||||
import { Plugin } from "obsidian";
|
||||
import { DEFAULT_SETTINGS } from "./SQLSealSettingsTab";
|
||||
import { Settings } from "./Settings";
|
||||
|
||||
@(makeInjector<SettingsModule, 'factory'>()(['plugin']))
|
||||
export class SettingsFactory {
|
||||
async make(plugin: Plugin) {
|
||||
const settings = Object.assign({}, DEFAULT_SETTINGS, await plugin.loadData());
|
||||
const obj = new Settings(settings)
|
||||
|
||||
obj.onChange((settings) => {
|
||||
plugin.saveData(settings)
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import { App, Plugin, Setting } from "obsidian";
|
||||
import { Settings } from "../Settings";
|
||||
import {
|
||||
checkTypeViewAvaiability,
|
||||
} from "../utils/viewInspector";
|
||||
import { SettingsControls } from "./SettingsControls";
|
||||
import { CSV_VIEW_EXTENSIONS, CSV_VIEW_TYPE, CSVView } from "../view/CSVView";
|
||||
|
||||
export class SettingsCSVControls extends SettingsControls {
|
||||
private registeredView: string | null = null;
|
||||
|
||||
register() {
|
||||
if (this.settings.get("enableViewer")) {
|
||||
const view = checkTypeViewAvaiability(this.app, CSV_VIEW_EXTENSIONS[0]);
|
||||
if (view) {
|
||||
this.registeredView = view;
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.registerView(
|
||||
CSV_VIEW_TYPE,
|
||||
(leaf) => new CSVView(leaf, this.settings),
|
||||
);
|
||||
this.plugin.registerExtensions(CSV_VIEW_EXTENSIONS, CSV_VIEW_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.app.workspace.detachLeavesOfType(CSV_VIEW_TYPE);
|
||||
(this.app as any).viewRegistry.unregisterExtensions([
|
||||
...CSV_VIEW_EXTENSIONS,
|
||||
]);
|
||||
(this.app as any).viewRegistry.unregisterView(CSV_VIEW_TYPE);
|
||||
}
|
||||
|
||||
display(el: HTMLDivElement) {
|
||||
el.empty();
|
||||
el.createEl("h2", { text: "CSV File Viewer" });
|
||||
|
||||
const view = checkTypeViewAvaiability(this.app, "csv");
|
||||
|
||||
if (view && view !== CSV_VIEW_TYPE) {
|
||||
el.createDiv({
|
||||
text: "CSV files are already handled by different plugin. To enable SQLSeal CSV editor, disable other plugin that handles it",
|
||||
cls: "sqlseal-settings-warn",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
new Setting(el)
|
||||
.setName("Enable CSV Viewer")
|
||||
.setDesc(
|
||||
"Enables CSV files in your vault and adds ability to display them in a grid.",
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.settings.get("enableViewer"))
|
||||
.onChange(async (value) => {
|
||||
this.settings.set("enableViewer", !!value);
|
||||
if (!!value) {
|
||||
// Enabled
|
||||
this.register();
|
||||
} else {
|
||||
// Disabled
|
||||
this.unregister();
|
||||
}
|
||||
// await this.plugin.saveData(this.settings);
|
||||
// this.display();
|
||||
// this.callChanges()
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(el)
|
||||
.setName("Enable CSV Editing")
|
||||
.setDesc(
|
||||
"Enables Editing functions in the CSV Viewer. This will add buttons to add columns, remove individual rows and columns; and edit each entry.",
|
||||
)
|
||||
.setDisabled(!this.settings.get("enableViewer"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.settings.get("enableEditing"))
|
||||
.onChange(async (value) => {
|
||||
this.settings.set("enableEditing", value);
|
||||
// await this.plugin.saveData(this.settings);
|
||||
// this.callChanges()
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
12
src/modules/settings/settingsTabSection/SettingsControls.ts
Normal file
12
src/modules/settings/settingsTabSection/SettingsControls.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { App, Plugin } from "obsidian";
|
||||
import { Settings } from "../Settings";
|
||||
|
||||
export abstract class SettingsControls {
|
||||
constructor(
|
||||
protected settings: Settings,
|
||||
protected app: App,
|
||||
protected plugin: Plugin
|
||||
) {}
|
||||
|
||||
abstract display(el: HTMLDivElement): void
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { App, Setting } from "obsidian";
|
||||
import { Settings } from "../Settings";
|
||||
import { SettingsControls } from "./SettingsControls";
|
||||
import { checkTypeViewAvaiability } from "../utils/viewInspector";
|
||||
import {
|
||||
JSON_VIEW_EXTENSIONS,
|
||||
JSON_VIEW_TYPE,
|
||||
JsonView,
|
||||
} from "../view/JsonView";
|
||||
|
||||
export class SettingsJsonControls extends SettingsControls {
|
||||
private registeredView: string | null = null;
|
||||
|
||||
register() {
|
||||
if (this.settings.get("enableJSONViewer")) {
|
||||
const view = checkTypeViewAvaiability(this.app, JSON_VIEW_EXTENSIONS[0]);
|
||||
// FIXME: also allow for json5 checks
|
||||
if (view) {
|
||||
this.registeredView = view;
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.registerView(JSON_VIEW_TYPE, (leaf) => new JsonView(leaf));
|
||||
this.plugin.registerExtensions(JSON_VIEW_EXTENSIONS, JSON_VIEW_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.app.workspace.detachLeavesOfType(JSON_VIEW_TYPE);
|
||||
(this.app as any).viewRegistry.unregisterExtensions([
|
||||
...JSON_VIEW_EXTENSIONS,
|
||||
]);
|
||||
(this.app as any).viewRegistry.unregisterView(JSON_VIEW_TYPE);
|
||||
}
|
||||
|
||||
display(el: HTMLDivElement) {
|
||||
el.empty();
|
||||
|
||||
el.createEl("h2", { text: "JSON File Viewer" });
|
||||
|
||||
const view = checkTypeViewAvaiability(this.app, "json");
|
||||
|
||||
if (view && view !== JSON_VIEW_TYPE) {
|
||||
el.createDiv({
|
||||
text: "JSON files are already handled by different plugin. To enable SQLSeal JSON preview, disable other plugin that handles it",
|
||||
cls: 'sqlseal-settings-warn'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
new Setting(el)
|
||||
.setName("Enable JSON Viewer")
|
||||
.setDesc(
|
||||
"Enables JSON and JSON5 files in your files explorer and allows to preview them.",
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.settings.get("enableJSONViewer"))
|
||||
.onChange(async (value) => {
|
||||
this.settings.set("enableJSONViewer", !!value);
|
||||
if (!!value) {
|
||||
// Enabled
|
||||
this.register();
|
||||
} else {
|
||||
// Disabled
|
||||
this.unregister();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
12
src/modules/settings/utils/viewInspector.ts
Normal file
12
src/modules/settings/utils/viewInspector.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { App } from "obsidian";
|
||||
|
||||
export const checkTypeViewAvaiability = (app: App, extension: string) => {
|
||||
const viewRegistry = (app as any).viewRegistry;
|
||||
const csvHandler = viewRegistry.typeByExtension[extension];
|
||||
|
||||
if (csvHandler) {
|
||||
return csvHandler as string
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
};
|
||||
465
src/modules/settings/view/CSVView.ts
Normal file
465
src/modules/settings/view/CSVView.ts
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
import { WorkspaceLeaf, TextFileView, Menu, MenuItem } from "obsidian";
|
||||
import { parse, unparse } from "papaparse";
|
||||
import {
|
||||
GridRenderer,
|
||||
GridRendererCommunicator,
|
||||
} from "../../editor/renderer/GridRenderer";
|
||||
import { errorNotice } from "../../../utils/notice";
|
||||
import { ConfigObject, loadConfig, saveConfig } from "src/utils/csvConfig";
|
||||
import { ColumnType } from "../../../utils/types";
|
||||
import { Settings } from "../Settings";
|
||||
import { RenameColumnModal } from "../modal/renameColumnModal";
|
||||
import { CodeSampleModal } from "../modal/showCodeSample";
|
||||
import { DeleteConfirmationModal } from "../modal/deleteConfirmationModal";
|
||||
|
||||
const delay = (n: number) => new Promise((resolve) => setTimeout(resolve, n));
|
||||
|
||||
export const CSV_VIEW_TYPE = "csv-viewer" as const;
|
||||
export const CSV_VIEW_EXTENSIONS = ["csv"];
|
||||
|
||||
export class CSVView extends TextFileView {
|
||||
private content: string;
|
||||
private table: HTMLTableElement;
|
||||
private config: ConfigObject;
|
||||
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
private readonly settings: Settings,
|
||||
) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return CSV_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return this.file?.basename || "CSV Viewer";
|
||||
}
|
||||
|
||||
async onload(): Promise<void> {
|
||||
super.onload();
|
||||
this.contentEl.addClass("csv-viewer-container");
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.renderCSV();
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
// Cleanup if needed
|
||||
}
|
||||
|
||||
async setViewData(data: string, clear: boolean): Promise<void> {
|
||||
this.content = data;
|
||||
await this.renderCSV();
|
||||
}
|
||||
|
||||
getViewData(): string {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.content = "";
|
||||
this.table.empty();
|
||||
}
|
||||
|
||||
private result: any;
|
||||
updateRow(newRowData: Record<string, any>) {
|
||||
this.result.data[parseInt(newRowData.__index, 10)] = newRowData;
|
||||
this.saveData(true);
|
||||
}
|
||||
|
||||
deleteRow(idx: number) {
|
||||
this.result.data.splice(idx, 1);
|
||||
this.saveData();
|
||||
}
|
||||
|
||||
deleteColumn(column: string) {
|
||||
this.result.fields = this.result.fields.filter((c: string) => c !== column);
|
||||
this.saveData();
|
||||
}
|
||||
|
||||
addNewColumn(columnName: string) {
|
||||
if (this.result.fields.indexOf(columnName) > -1) {
|
||||
throw new Error("Column already exists");
|
||||
}
|
||||
this.result.fields.push(columnName);
|
||||
this.result.data = this.result.data.map((d: any) => ({
|
||||
[columnName]: "",
|
||||
...d,
|
||||
}));
|
||||
this.saveData();
|
||||
}
|
||||
|
||||
renameColumn(oldName: string, newName: string) {
|
||||
if (!newName) {
|
||||
return;
|
||||
}
|
||||
if (this.result.fields.contains(newName)) {
|
||||
errorNotice("Column already exists");
|
||||
return;
|
||||
}
|
||||
const oldNameIdx = this.result.fields.indexOf(oldName);
|
||||
if (oldNameIdx < 0) {
|
||||
errorNotice("Old column does not exist");
|
||||
return;
|
||||
}
|
||||
this.result.fields[oldNameIdx] = newName;
|
||||
this.result.data = this.result.data.map((d: Record<string, unknown>) => {
|
||||
d[newName] = d[oldName];
|
||||
delete d[oldName];
|
||||
return d;
|
||||
});
|
||||
this.saveData();
|
||||
this.loadDataIntoGrid();
|
||||
}
|
||||
|
||||
setIsEditable(newValue: boolean) {
|
||||
this.settings.set("enableEditing", newValue);
|
||||
// FIXME: if there already rendered view, use this to rerender it?
|
||||
}
|
||||
|
||||
saveData(noRefresh: boolean = false) {
|
||||
if (noRefresh) {
|
||||
this.refreshSkip = Date.now() + 500;
|
||||
}
|
||||
|
||||
// Map results
|
||||
const res = [...this.result.data].map((r) => {
|
||||
const r2 = { ...r };
|
||||
Object.keys(r).forEach((k) => {
|
||||
if (typeof r[k] === "boolean") {
|
||||
r2[k] = r[k] ? 1 : 0;
|
||||
}
|
||||
});
|
||||
return r2;
|
||||
});
|
||||
|
||||
const output = unparse({ ...this.result, data: res });
|
||||
this.app.vault.modify(this.file!, output);
|
||||
this.refreshTypes();
|
||||
}
|
||||
|
||||
createRow() {
|
||||
this.result.data.push(
|
||||
this.result.fields.reduce(
|
||||
(acc: Record<string, any>, f: string) => ({ ...acc, [f]: "" }),
|
||||
{},
|
||||
),
|
||||
);
|
||||
this.saveData();
|
||||
}
|
||||
|
||||
getColumnType(columnName: string) {
|
||||
if (this.config.columnDefinitions[columnName]) {
|
||||
return this.config.columnDefinitions[columnName].type;
|
||||
}
|
||||
return "auto";
|
||||
}
|
||||
|
||||
async changeColumnType(columnName: string, type: ColumnType) {
|
||||
const prev = this.config.columnDefinitions[columnName] ?? {};
|
||||
this.config.columnDefinitions[columnName] = {
|
||||
...prev,
|
||||
type: type,
|
||||
};
|
||||
await this.saveConfig();
|
||||
}
|
||||
|
||||
async loadConfig() {
|
||||
while (!this.file) {
|
||||
await delay(100);
|
||||
}
|
||||
this.config = await loadConfig(this.file, this.app.vault);
|
||||
}
|
||||
|
||||
async saveConfig() {
|
||||
await saveConfig(this.file!, this.config, this.app.vault);
|
||||
}
|
||||
|
||||
private getColumnConfigurations(columns: string[]) {
|
||||
return columns.map((f) => {
|
||||
if (!this.config) {
|
||||
return {
|
||||
field: f,
|
||||
};
|
||||
}
|
||||
const def = this.config.columnDefinitions[f]?.type;
|
||||
if (!def || def === "auto") {
|
||||
return { field: f };
|
||||
}
|
||||
if (def === "date") {
|
||||
return {
|
||||
field: f,
|
||||
cellDataType: "dateString",
|
||||
};
|
||||
}
|
||||
return {
|
||||
field: f,
|
||||
cellDataType: def,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
refreshTypes() {
|
||||
if (this.gridCommunicator) {
|
||||
const columns = this.result.fields;
|
||||
if (columns && columns.length) {
|
||||
this.gridCommunicator.gridApi.setGridOption(
|
||||
"columnDefs",
|
||||
this.getColumnConfigurations(columns),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveColumn(name: string, toIndex: number) {
|
||||
let fields = this.result.fields as Array<string>;
|
||||
fields = fields.filter((f) => f !== name);
|
||||
fields = [...fields.slice(0, toIndex), name, ...fields.slice(toIndex)];
|
||||
this.result.fields = fields;
|
||||
this.saveData();
|
||||
}
|
||||
|
||||
api: any = null;
|
||||
gridCommunicator: GridRendererCommunicator | null = null;
|
||||
refreshSkip: number = 0;
|
||||
|
||||
formatWithTypes(d: Record<string, string | boolean | number>) {
|
||||
Object.entries(this.config.columnDefinitions).forEach(([key, value]) => {
|
||||
if (!d[key]) {
|
||||
return;
|
||||
}
|
||||
if (value.type === "boolean") {
|
||||
if (d[key] === "false" || d[key] === "0") {
|
||||
d[key] = false;
|
||||
}
|
||||
d[key] = !!d[key];
|
||||
}
|
||||
if (value.type === "number") {
|
||||
if (d[key] === null || d[key] === "") {
|
||||
d[key] = "";
|
||||
} else {
|
||||
d[key] = parseFloat(d[key] as string);
|
||||
}
|
||||
}
|
||||
if (value.type === "date") {
|
||||
// try parsing
|
||||
const val = d[key] as string;
|
||||
const date = new Date(val);
|
||||
d[key] = date.toISOString().split("T")[0];
|
||||
}
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
prepareData() {
|
||||
const result = parse(this.content, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
});
|
||||
const data = result.data.map((d: any, i) => ({
|
||||
...this.formatWithTypes(d),
|
||||
__index: i.toString(),
|
||||
}));
|
||||
return {
|
||||
data: data,
|
||||
fields: result.meta.fields,
|
||||
};
|
||||
}
|
||||
|
||||
loadDataIntoGrid() {
|
||||
if (this.refreshSkip > Date.now()) {
|
||||
return;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
const result = this.prepareData();
|
||||
this.result = result;
|
||||
this.refreshTypes();
|
||||
this.api!.render(result);
|
||||
});
|
||||
}
|
||||
|
||||
isLoading: boolean = false;
|
||||
|
||||
private async renderCSV() {
|
||||
if (this.isLoading) {
|
||||
if (this.api) {
|
||||
this.loadDataIntoGrid();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.isLoading = true;
|
||||
|
||||
this.contentEl.empty();
|
||||
const csvEditorDiv = this.contentEl.createDiv({
|
||||
cls: "sql-seal-csv-editor",
|
||||
});
|
||||
|
||||
const buttonsRow = csvEditorDiv.createDiv({
|
||||
cls: "sql-seal-csv-viewer-buttons",
|
||||
});
|
||||
await this.loadConfig();
|
||||
if (this.settings.get("enableEditing")) {
|
||||
const createColumn = buttonsRow.createEl("button", {
|
||||
text: "Add Column",
|
||||
});
|
||||
const createRow = buttonsRow.createEl("button", { text: "Add Row" });
|
||||
|
||||
createColumn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const modal = new RenameColumnModal(this.app, (res) => {
|
||||
this.addNewColumn(res);
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
|
||||
createRow.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.createRow();
|
||||
});
|
||||
}
|
||||
const generateSqlCode = buttonsRow.createEl("button", {
|
||||
text: "Generate SQLSeal code",
|
||||
});
|
||||
const gridEl = csvEditorDiv.createDiv({ cls: "sql-seal-csv-viewer" });
|
||||
|
||||
generateSqlCode.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (!this.file) {
|
||||
return;
|
||||
}
|
||||
const modal = new CodeSampleModal(this.app, this.file);
|
||||
modal.open();
|
||||
});
|
||||
|
||||
const grid = new GridRenderer(this.settings, null, this.app);
|
||||
const csvView = this;
|
||||
const data = this.prepareData();
|
||||
this.result = data;
|
||||
const api = grid.render(
|
||||
{
|
||||
columnDefs: this.getColumnConfigurations(data.fields ?? []),
|
||||
defaultColDef: {
|
||||
editable: this.settings.get("enableEditing"),
|
||||
headerComponentParams: {
|
||||
enableMenu: this.settings.get("enableEditing"),
|
||||
showColumnMenu: function (e: any) {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("Rename Column");
|
||||
item.onClick(() => {
|
||||
const modal = new RenameColumnModal(csvView.app, (res) => {
|
||||
csvView.renameColumn(
|
||||
this.column.userProvidedColDef.field,
|
||||
res,
|
||||
);
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("Delete Column");
|
||||
item.onClick(() => {
|
||||
const colName = this.column.userProvidedColDef.field;
|
||||
const modal = new DeleteConfirmationModal(
|
||||
csvView.app,
|
||||
`column ${colName}`,
|
||||
() => {
|
||||
csvView.deleteColumn(colName);
|
||||
},
|
||||
);
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME: rework it to submenus.
|
||||
menu.addSeparator();
|
||||
menu.addItem((item) => {
|
||||
// item.setDisabled(true)
|
||||
item.setTitle("Data Type");
|
||||
// item.setIsLabel(true)
|
||||
const ipfSubmenu = (item as any).setSubmenu();
|
||||
const types = [
|
||||
"auto",
|
||||
"text",
|
||||
"number",
|
||||
"boolean",
|
||||
"date",
|
||||
] as ColumnType[];
|
||||
|
||||
const colName = this.column.userProvidedColDef.field;
|
||||
|
||||
const current = csvView.getColumnType(colName);
|
||||
types.forEach((type) => {
|
||||
ipfSubmenu.addItem((subItem: MenuItem) => {
|
||||
const checkbox = type === current ? "✓ " : "";
|
||||
subItem.setTitle(checkbox + type);
|
||||
subItem.onClick(() => {
|
||||
csvView.changeColumnType(colName, type);
|
||||
csvView.refreshTypes();
|
||||
csvView.loadDataIntoGrid();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const pos = e.getBoundingClientRect();
|
||||
menu.showAtPosition({ x: pos.x, y: pos.y + 20 });
|
||||
},
|
||||
},
|
||||
},
|
||||
enableCellTextSelection: false,
|
||||
ensureDomOrder: false,
|
||||
paginationAutoPageSize: true,
|
||||
suppressMovableColumns: false,
|
||||
onColumnMoved: (e) => {
|
||||
if (!e.finished) {
|
||||
return;
|
||||
}
|
||||
const columnName = e.column?.getUserProvidedColDef();
|
||||
if (!columnName) {
|
||||
return;
|
||||
}
|
||||
csvView.moveColumn(columnName?.field!, e.toIndex!);
|
||||
},
|
||||
domLayout: "normal",
|
||||
getRowId: (p) => p.data.__index,
|
||||
onCellValueChanged: (event) => {
|
||||
if (event.rowIndex === null) {
|
||||
return;
|
||||
}
|
||||
this.updateRow(event.data);
|
||||
},
|
||||
onCellContextMenu: (e) => {
|
||||
if (!this.settings.get("enableEditing")) {
|
||||
return;
|
||||
}
|
||||
const menu = new Menu();
|
||||
menu.addItem((item) => {
|
||||
item.setTitle("Delete Row");
|
||||
item.onClick(() => {
|
||||
const modal = new DeleteConfirmationModal(
|
||||
csvView.app,
|
||||
`row`,
|
||||
() => {
|
||||
csvView.deleteRow(e.data.__index);
|
||||
},
|
||||
);
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
menu.showAtMouseEvent(e.event as any);
|
||||
},
|
||||
},
|
||||
gridEl,
|
||||
{ sourcePath: this.file?.path || "" },
|
||||
);
|
||||
this.api = api;
|
||||
this.gridCommunicator = api.communicator;
|
||||
api.render(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { App, Plugin, TFile } from "obsidian";
|
||||
import { AFileSyncTable } from "./tables/abstractFileSyncTable";
|
||||
import { AFileSyncTable } from "../sync/tables/abstractFileSyncTable";
|
||||
|
||||
export class SealFileSync {
|
||||
private tablePlugins: Array<AFileSyncTable> = []
|
||||
29
src/modules/sync/fileSyncController/fileSyncFactory.ts
Normal file
29
src/modules/sync/fileSyncController/fileSyncFactory.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { App, Plugin } from "obsidian";
|
||||
import { SealFileSync } from "./FileSync";
|
||||
import { Sync } from "../sync/sync";
|
||||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { SyncModule } from "../module";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { FilesFileSyncTable } from "../sync/tables/filesTable";
|
||||
import { TagsFileSyncTable } from "../sync/tables/tagsTable";
|
||||
import { TasksFileSyncTable } from "../sync/tables/tasksTable";
|
||||
import { LinksFileSyncTable } from "../sync/tables/linksTable";
|
||||
|
||||
@(makeInjector<SyncModule>()(["app", "plugin", "db", "syncBus"]))
|
||||
export class FileSyncFactory {
|
||||
async make(app: App, plugin: Plugin, db: SqlSealDatabase, sync: Sync) {
|
||||
return async () => {
|
||||
const fileSync = new SealFileSync(app, plugin, (name) =>
|
||||
sync.triggerGlobalTableChange(name),
|
||||
);
|
||||
|
||||
fileSync.addTablePlugin(new FilesFileSyncTable(db, app, plugin));
|
||||
fileSync.addTablePlugin(new TagsFileSyncTable(db, app));
|
||||
fileSync.addTablePlugin(new TasksFileSyncTable(db, app));
|
||||
fileSync.addTablePlugin(new LinksFileSyncTable(db, app));
|
||||
|
||||
await fileSync.init();
|
||||
return fileSync;
|
||||
};
|
||||
}
|
||||
}
|
||||
22
src/modules/sync/module.ts
Normal file
22
src/modules/sync/module.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { asFactory, buildContainer } from "@hypersphere/dity"
|
||||
import { FileSyncFactory } from "./fileSyncController/fileSyncFactory"
|
||||
import { App, Plugin, Vault } from "obsidian"
|
||||
import { SyncFactory } from "./sync/syncFactory"
|
||||
import { SqlSealDatabase } from "../database/database"
|
||||
import { SyncInit } from "./sync/init"
|
||||
|
||||
export const sync = buildContainer(c => c
|
||||
.register({
|
||||
fileSync: asFactory(FileSyncFactory),
|
||||
syncBus: asFactory(SyncFactory),
|
||||
init: asFactory(SyncInit)
|
||||
})
|
||||
.externals<{
|
||||
app: App,
|
||||
plugin: Plugin,
|
||||
db: SqlSealDatabase,
|
||||
vault: Vault
|
||||
}>()
|
||||
.exports('init', 'syncBus')
|
||||
)
|
||||
export type SyncModule = typeof sync
|
||||
|
|
@ -11,7 +11,7 @@ export class ConfigurationRepository extends Repository {
|
|||
|
||||
public async getConfig(key: string) {
|
||||
try {
|
||||
const config = await this.db.select('SELECT value FROM configuration WHERE id = @id', { id: key })
|
||||
const config = (await this.db.select('SELECT value FROM configuration WHERE id = @id', { id: key }))!
|
||||
if (config.data && config.data.length) {
|
||||
return config.data[0].value
|
||||
}
|
||||
|
|
@ -42,17 +42,17 @@ export class TableAliasesRepository extends Repository {
|
|||
}
|
||||
|
||||
async getAll() {
|
||||
const { data } = await this.db.select('SELECT * FROM TABLE_ALIASES', {})
|
||||
const { data } = (await this.db.select('SELECT * FROM TABLE_ALIASES', {}))!
|
||||
return data as unknown as TableAlias[]
|
||||
}
|
||||
|
||||
async getByAlias(sourceFileName: string, aliasName: string) {
|
||||
const { data } = await this.db.select(`SELECT * FROM TABLE_ALIASES
|
||||
const { data } = (await this.db.select(`SELECT * FROM TABLE_ALIASES
|
||||
WHERE source_file_name=@source_file_name
|
||||
AND alias_name=@alias_name`, {
|
||||
'source_file_name': sourceFileName,
|
||||
'alias_name': aliasName
|
||||
})
|
||||
}))!
|
||||
if (!data || data.length < 0) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -60,10 +60,10 @@ export class TableAliasesRepository extends Repository {
|
|||
}
|
||||
|
||||
async getByTableName(tableName: string) {
|
||||
const { data } = await this.db.select(`SELECT * FROM TABLE_ALIASES
|
||||
const { data } = (await this.db.select(`SELECT * FROM TABLE_ALIASES
|
||||
WHERE table_name = @table_name`, {
|
||||
table_name: tableName
|
||||
})
|
||||
}))!
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
|
|
@ -72,9 +72,9 @@ export class TableAliasesRepository extends Repository {
|
|||
|
||||
|
||||
async getByContext(sourceFileName: string) {
|
||||
const { data } = await this.db.select(`SELECT * FROM TABLE_ALIASES
|
||||
const { data } = (await this.db.select(`SELECT * FROM TABLE_ALIASES
|
||||
WHERE source_file_name=@source_file_name
|
||||
`, { source_file_name: sourceFileName })
|
||||
`, { source_file_name: sourceFileName }))!
|
||||
if (!data) {
|
||||
return []
|
||||
}
|
||||
|
|
@ -51,10 +51,10 @@ export class TableDefinitionsRepository extends Repository {
|
|||
}
|
||||
|
||||
async getBySourceFile(sourceFile: string) {
|
||||
const { data } = await this.db.select(
|
||||
const { data } = (await this.db.select(
|
||||
`SELECT * FROM ${this.TABLE_NAME} WHERE source_file = @sourceFile`,
|
||||
{ sourceFile }
|
||||
)
|
||||
))!
|
||||
|
||||
if (!data.length) {
|
||||
return null
|
||||
|
|
@ -63,10 +63,10 @@ export class TableDefinitionsRepository extends Repository {
|
|||
}
|
||||
|
||||
async getByRefreshId(refreshId: string) {
|
||||
const { data } = await this.db.select(
|
||||
const { data } = (await this.db.select(
|
||||
`SELECT * FROM ${this.TABLE_NAME} WHERE refresh_id= @refreshId`,
|
||||
{ refreshId }
|
||||
)
|
||||
))!
|
||||
|
||||
if (!data.length) {
|
||||
return null
|
||||
|
|
@ -79,7 +79,7 @@ export class TableDefinitionsRepository extends Repository {
|
|||
}
|
||||
|
||||
async getAll() {
|
||||
const { data } = await this.db.select(`SELECT * FROM ${this.TABLE_NAME}`, {})
|
||||
const { data } = (await this.db.select(`SELECT * FROM ${this.TABLE_NAME}`, {}))!
|
||||
return data.map(d => ({
|
||||
...d,
|
||||
type: d.type ?? 'file',
|
||||
|
|
@ -98,7 +98,7 @@ export class TableDefinitionsRepository extends Repository {
|
|||
updateData.arguments = JSON.stringify(fields.arguments);
|
||||
}
|
||||
|
||||
await this.db.db.updateData(this.TABLE_NAME, [{
|
||||
await this.db.db!.updateData(this.TABLE_NAME, [{
|
||||
id,
|
||||
...updateData
|
||||
}], 'id')
|
||||
17
src/modules/sync/sync/init.ts
Normal file
17
src/modules/sync/sync/init.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { SyncModule } from "../module";
|
||||
import { App } from "obsidian";
|
||||
import { SealFileSync } from "../fileSyncController/FileSync";
|
||||
|
||||
@(makeInjector<SyncModule, 'factory'>()([
|
||||
'app', 'fileSync'
|
||||
]))
|
||||
export class SyncInit {
|
||||
make(app: App, fileSync: () => Promise<SealFileSync>) {
|
||||
return () => {
|
||||
app.workspace.onLayoutReady(async () => {
|
||||
await fileSync()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { SqlSealDatabase } from "../database/database";
|
||||
import { TableAliasesRepository } from "./repository/tableAliases";
|
||||
import { App, TAbstractFile, TFile, Vault } from "obsidian";
|
||||
import { FilepathHasher } from "../utils/hasher";
|
||||
import { FilepathHasher } from "../../../utils/hasher";
|
||||
import { Omnibus } from "@hypersphere/omnibus";
|
||||
import { SyncStrategyFactory } from "./syncStrategy/SyncStrategyFactory";
|
||||
import { ConfigurationRepository } from "./repository/configuration";
|
||||
import { TableDefinitionsRepository, TableDefinition } from "./repository/tableDefinitions";
|
||||
import { ParserTableDefinition } from "./syncStrategy/types";
|
||||
import { uniq } from "lodash";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { TableDefinition, TableDefinitionsRepository } from "../repository/tableDefinitions";
|
||||
import { TableAliasesRepository } from "../repository/tableAliases";
|
||||
import { ConfigurationRepository } from "../repository/configuration";
|
||||
import { ParserTableDefinition } from "../syncStrategy/types";
|
||||
import { SyncStrategyFactory } from "../syncStrategy/SyncStrategyFactory";
|
||||
|
||||
|
||||
const SQLSEAL_DATABASE_VERSION = 2;
|
||||
|
|
@ -114,7 +114,7 @@ export class Sync {
|
|||
}
|
||||
|
||||
async getTablesMappingForContext(sourceFileName: string) {
|
||||
const tables = await this.tableMapLog.getByContext(sourceFileName)
|
||||
const tables = await this.tableMapLog.getByContext(sourceFileName) as { alias_name: string, table_name: string }[]
|
||||
const map = tables.reduce((acc, t) => ({
|
||||
...acc,
|
||||
[t.alias_name as string]: t.table_name
|
||||
19
src/modules/sync/sync/syncFactory.ts
Normal file
19
src/modules/sync/sync/syncFactory.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { App, Vault } from "obsidian";
|
||||
import { makeInjector } from "@hypersphere/dity";
|
||||
import { Sync } from "./sync";
|
||||
import { SyncModule } from "../module";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
|
||||
@(makeInjector<SyncModule>()([
|
||||
'db', 'vault', 'app'
|
||||
]))
|
||||
export class SyncFactory {
|
||||
async make(
|
||||
db: SqlSealDatabase,
|
||||
vault: Vault,
|
||||
app: App) {
|
||||
const sync = new Sync(db, vault, app)
|
||||
await sync.init()
|
||||
return sync
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { App, TFile } from "obsidian";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { SqlSealDatabase } from "../../../database/database";
|
||||
|
||||
export abstract class AFileSyncTable {
|
||||
constructor(
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { App, Plugin, TFile } from "obsidian";
|
||||
import { AFileSyncTable } from "./abstractFileSyncTable";
|
||||
import { sanitise } from "../../utils/sanitiseColumn";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { difference } from "lodash";
|
||||
import { sanitise } from "../../../../utils/sanitiseColumn";
|
||||
import { SqlSealDatabase } from "../../../database/database";
|
||||
|
||||
|
||||
export const FILES_TABLE_NAME = 'files'
|
||||
|
|
@ -56,7 +56,7 @@ export class FilesFileSyncTable extends AFileSyncTable {
|
|||
const newColumns = difference(newSetOfColumns, this.columns)
|
||||
if (newColumns.length) {
|
||||
await this.db.addColumns(FILES_TABLE_NAME, newColumns)
|
||||
this.columns = await this.db.getColumns(FILES_TABLE_NAME)
|
||||
this.columns = (await this.db.getColumns(FILES_TABLE_NAME)) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ export class FilesFileSyncTable extends AFileSyncTable {
|
|||
}
|
||||
|
||||
async onFileCreateBulk(files: Array<TFile>) {
|
||||
this.columns = await this.db.getColumns(FILES_TABLE_NAME)
|
||||
this.columns = (await this.db.getColumns(FILES_TABLE_NAME)) ?? []
|
||||
|
||||
// One by one
|
||||
for (const file of files) {
|
||||
|
|
@ -81,7 +81,7 @@ export class FilesFileSyncTable extends AFileSyncTable {
|
|||
|
||||
async onInit(): Promise<void> {
|
||||
this.db.createTableNoTypes(FILES_TABLE_NAME, ['id', 'name', 'path', 'created_at', 'modified_at', 'file_size'])
|
||||
this.columns = await this.db.getColumns(FILES_TABLE_NAME)
|
||||
this.columns = (await this.db.getColumns(FILES_TABLE_NAME)) ?? []
|
||||
|
||||
// Indexes
|
||||
const toIndex = ['id', 'name', 'path']
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { ISyncStrategy } from "./abstractSyncStrategy";
|
||||
import { sanitise } from "../../utils/sanitiseColumn";
|
||||
import { parse } from "papaparse";
|
||||
import { FilepathHasher } from "../../utils/hasher";
|
||||
import { TableDefinitionExternal } from "../repository/tableDefinitions";
|
||||
import { ParserTableDefinition } from "./types";
|
||||
import { App } from "obsidian";
|
||||
import { loadConfig } from "../../utils/csvConfig";
|
||||
import { FilepathHasher } from "../../../utils/hasher";
|
||||
import { sanitise } from "../../../utils/sanitiseColumn";
|
||||
import { loadConfig } from "../../../utils/csvConfig";
|
||||
|
||||
const DEFAULT_FILE_HASH = ''
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { App } from "obsidian";
|
||||
import { ISyncStrategy } from "./abstractSyncStrategy";
|
||||
import { FilepathHasher } from "../../utils/hasher";
|
||||
import { ParserTableDefinition } from "./types";
|
||||
import { parse } from 'json5';
|
||||
import { uniq } from "lodash";
|
||||
import * as jsonpath from 'jsonpath'
|
||||
import { FilepathHasher } from "../../../utils/hasher";
|
||||
|
||||
const DEFAULT_FILE_HASH = ''
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { FilepathHasher } from "../../utils/hasher";
|
||||
import { ISyncStrategy } from "./abstractSyncStrategy";
|
||||
import type { App } from "obsidian";
|
||||
import { Component, MarkdownRenderer, normalizePath } from "obsidian";
|
||||
import { ParserTableDefinition } from "./types";
|
||||
import { dirname, join } from "path";
|
||||
import { sanitise } from "../../utils/sanitiseColumn";
|
||||
import { FilepathHasher } from "../../../utils/hasher";
|
||||
import { sanitise } from "../../../utils/sanitiseColumn";
|
||||
|
||||
const DEFAULT_FILE_HASH = ''
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ import { MarkdownTableSyncStrategy } from "./MarkdownTableSyncStrategy";
|
|||
import { JsonFileSyncStrategy } from "./JSONFileSyncStrategy";
|
||||
import { ParserTableDefinition } from "./types";
|
||||
import { TableDefinitionExternal } from "../repository/tableDefinitions";
|
||||
import { getFileExtension } from "../../utils/extractExtension";
|
||||
import { getFileExtension } from "../../../utils/extractExtension";
|
||||
|
||||
|
||||
const resolveFileStrategy = (filename: string) => {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { App } from "obsidian";
|
||||
import { TableDefinitionExternal } from "../repository/tableDefinitions";
|
||||
import { ParserTableDefinition } from "./types";
|
||||
import { ColumnDefinition } from "../../utils/types";
|
||||
import { ColumnDefinition } from "../../../utils/types";
|
||||
|
||||
export abstract class ISyncStrategy {
|
||||
constructor(protected def: TableDefinitionExternal, protected app: App) {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { isStringifiedArray } from "../../../utils/ui";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { CellFunction } from "./CellFunction";
|
||||
import { parse } from 'json5'
|
||||
import { isStringifiedArray, renderStringifiedArray } from '../utils/ui'
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
|
||||
export type UnregisterCallback = () => void;
|
||||
|
||||
32
src/modules/syntaxHighlight/cellParser/factory.ts
Normal file
32
src/modules/syntaxHighlight/cellParser/factory.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { App } from "obsidian"
|
||||
import { ModernCellParser } from "./ModernCellParser"
|
||||
import { LinkParser } from "./parser/link"
|
||||
import { ImageParser } from "./parser/image"
|
||||
import { CheckboxParser } from "./parser/checkbox"
|
||||
import { makeInjector } from "@hypersphere/dity"
|
||||
import { MainModule } from "../../main/module"
|
||||
import { SqlSealDatabase } from "../../database/database"
|
||||
import { SyntaxHighlightModule } from "../module"
|
||||
|
||||
export const getCellParser = (app: App, create = createEl) => {
|
||||
const cellParser = new ModernCellParser()
|
||||
cellParser.register(new LinkParser(app, create))
|
||||
cellParser.register(new ImageParser(app, create))
|
||||
cellParser.register(new CheckboxParser(app, create))
|
||||
return cellParser
|
||||
}
|
||||
|
||||
@(makeInjector<SyntaxHighlightModule>()([
|
||||
'app',
|
||||
'db'
|
||||
]))
|
||||
export class CellParserFactory {
|
||||
make(app: App, db: SqlSealDatabase, create: typeof createEl = createEl) {
|
||||
const cellParser = new ModernCellParser()
|
||||
cellParser.register(new LinkParser(app, create))
|
||||
cellParser.register(new ImageParser(app, create))
|
||||
cellParser.register(new CheckboxParser(app, create))
|
||||
cellParser.registerDbFunctions(db)
|
||||
return cellParser
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { isLinkLocal } from "../../utils/ui/helperFunctions";
|
||||
import { isLinkLocal } from "../../../../utils/ui/helperFunctions";
|
||||
import { CellFunction } from "../CellFunction";
|
||||
import { CellParserResult } from "../ModernCellParser";
|
||||
import { App } from "obsidian";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { isStringifiedArray, renderStringifiedArray } from "../../utils/ui";
|
||||
import { isLinkLocal, isLinktext, removeExtension } from "../../utils/ui/helperFunctions";
|
||||
import { isStringifiedArray, renderStringifiedArray } from "../../../../utils/ui";
|
||||
import { isLinkLocal, isLinktext, removeExtension } from "../../../../utils/ui/helperFunctions";
|
||||
import { CellFunction } from "../CellFunction";
|
||||
import { App } from "obsidian";
|
||||
|
||||
143
src/modules/syntaxHighlight/editorExtension/inlineCodeBlock.ts
Normal file
143
src/modules/syntaxHighlight/editorExtension/inlineCodeBlock.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import {
|
||||
EditorView,
|
||||
ViewPlugin,
|
||||
ViewUpdate,
|
||||
DecorationSet,
|
||||
Decoration,
|
||||
WidgetType,
|
||||
} from "@codemirror/view";
|
||||
import { RangeSetBuilder } from "@codemirror/state";
|
||||
import { syntaxTree } from "@codemirror/language";
|
||||
import { App } from "obsidian";
|
||||
import { SqlSealDatabase } from "../../database/database";
|
||||
import { Settings } from "../../settings/Settings";
|
||||
import { Sync } from "../../sync/sync/sync";
|
||||
import { SqlSealInlineHandler } from "../../editor/codeblockHandler/inline/InlineCodeHandler";
|
||||
import { InlineProcessor } from "../../editor/codeblockHandler/inline/InlineProcessor";
|
||||
|
||||
export function createSqlSealEditorExtension(
|
||||
app: App,
|
||||
db: SqlSealDatabase,
|
||||
settings: Settings,
|
||||
sync: Sync,
|
||||
) {
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet;
|
||||
inlineHandler: SqlSealInlineHandler;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.inlineHandler = new SqlSealInlineHandler(app, db, settings, sync);
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (
|
||||
update.docChanged ||
|
||||
update.viewportChanged ||
|
||||
update.selectionSet
|
||||
) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView) {
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
const tree = syntaxTree(view.state);
|
||||
|
||||
tree.iterate({
|
||||
enter: ({ type, from, to }) => {
|
||||
if (type.name.includes("inline-code")) {
|
||||
const text = view.state.doc.sliceString(from, to);
|
||||
if (text.startsWith("S>")) {
|
||||
// Check if we're currently editing this specific inline code
|
||||
const isEditing =
|
||||
view.hasFocus &&
|
||||
view.state.selection.ranges.some(
|
||||
(range) => range.from >= from && range.to <= to,
|
||||
);
|
||||
|
||||
if (!isEditing) {
|
||||
// Create a replacement decoration
|
||||
builder.add(
|
||||
from,
|
||||
to,
|
||||
Decoration.replace({
|
||||
widget: new SqlSealInlineWidget(
|
||||
text,
|
||||
this.inlineHandler,
|
||||
view.state.doc.lineAt(from).number,
|
||||
app,
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class SqlSealInlineWidget extends WidgetType {
|
||||
constructor(
|
||||
private query: string,
|
||||
private handler: SqlSealInlineHandler,
|
||||
private line: number,
|
||||
private app: App,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: SqlSealInlineWidget): boolean {
|
||||
return other.query === this.query && other.line === this.line;
|
||||
}
|
||||
|
||||
processor: InlineProcessor;
|
||||
|
||||
toDOM(): HTMLElement {
|
||||
const container = document.createElement("span");
|
||||
container.classList.add("sqlseal-inline-result");
|
||||
|
||||
// Create a new context for the inline query
|
||||
const ctx = {
|
||||
sourcePath: this.app.workspace.getActiveFile()?.path ?? "",
|
||||
addChild: () => {},
|
||||
getSectionInfo: () => ({
|
||||
lineStart: this.line,
|
||||
lineEnd: this.line,
|
||||
}),
|
||||
};
|
||||
|
||||
// Show the original query on hover
|
||||
container.setAttribute("aria-label", this.query);
|
||||
container.classList.add("has-tooltip");
|
||||
|
||||
this.processor = this.handler.instantiateProcessor(
|
||||
this.query,
|
||||
container,
|
||||
ctx.sourcePath,
|
||||
);
|
||||
|
||||
this.processor.load();
|
||||
return container;
|
||||
}
|
||||
|
||||
destroy(_dom: HTMLElement): void {
|
||||
if (this.processor) {
|
||||
this.processor.unload();
|
||||
}
|
||||
}
|
||||
|
||||
ignoreEvent(_event: Event): boolean {
|
||||
// Return false to allow events to propagate (important for editing)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,11 @@ import {
|
|||
DecorationSet
|
||||
} from '@codemirror/view';
|
||||
|
||||
import { SQLSealLangDefinition } from '../grammar/parser';
|
||||
import { RendererRegistry } from '../renderer/rendererRegistry';
|
||||
import { Range } from '@codemirror/state';
|
||||
import { Decorator, highlighterOperation } from '../grammar/highlighterOperation';
|
||||
import { FilePathWidget } from './widgets/FilePathWidget';
|
||||
import { RendererRegistry } from '../../editor/renderer/rendererRegistry';
|
||||
import { SQLSealLangDefinition } from '../../editor/parser';
|
||||
|
||||
const markDecorations = {
|
||||
blockFlag: Decoration.mark({ class: 'cm-sqlseal-block-flag' }),
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { WidgetType } from "@codemirror/view";
|
||||
import { App, Notice } from "obsidian";
|
||||
import { App } from "obsidian";
|
||||
|
||||
export class FilePathWidget extends WidgetType {
|
||||
constructor(private filePath: string, private app: App) {
|
||||
23
src/modules/syntaxHighlight/init.ts
Normal file
23
src/modules/syntaxHighlight/init.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { makeInjector } from "@hypersphere/dity"
|
||||
import { SyntaxHighlightModule } from "./module"
|
||||
import { EditorView, ViewPlugin } from "@codemirror/view";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { RendererRegistry } from "../editor/renderer/rendererRegistry";
|
||||
import { SQLSealViewPlugin } from "./editorExtension/syntaxHighlight";
|
||||
|
||||
@(makeInjector<SyntaxHighlightModule, 'factory'>()([
|
||||
'app', 'rendererRegistry', 'plugin'
|
||||
]))
|
||||
export class SyntaxHighlightInit {
|
||||
make(app: App, rendererRegistry: RendererRegistry, plugin: Plugin) {
|
||||
return () => {
|
||||
// FIXME: settings here.
|
||||
plugin.registerEditorExtension([
|
||||
ViewPlugin.define(
|
||||
(view: EditorView) => new SQLSealViewPlugin(view, app, rendererRegistry),
|
||||
{ decorations: v => v.decorations }
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/modules/syntaxHighlight/module.ts
Normal file
23
src/modules/syntaxHighlight/module.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { asFactory, buildContainer } from "@hypersphere/dity";
|
||||
import { SyntaxHighlightInit } from "./init";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { RendererRegistry } from "../editor/renderer/rendererRegistry";
|
||||
import { CellParserFactory } from "./cellParser/factory";
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
|
||||
export const syntaxHighlight = buildContainer((c) =>
|
||||
c
|
||||
.externals<{
|
||||
app: App;
|
||||
db: SqlSealDatabase;
|
||||
rendererRegistry: RendererRegistry;
|
||||
plugin: Plugin;
|
||||
}>()
|
||||
.register({
|
||||
init: asFactory(SyntaxHighlightInit),
|
||||
cellParser: asFactory(CellParserFactory),
|
||||
})
|
||||
.exports("init", "cellParser"),
|
||||
);
|
||||
|
||||
export type SyntaxHighlightModule = typeof syntaxHighlight;
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import { Plugin } from "obsidian"
|
||||
import SqlSealPlugin from "../main"
|
||||
import { version } from '../../package.json'
|
||||
import { RendererConfig } from "../renderer/rendererRegistry";
|
||||
import { CellFunction } from "../cellParser/CellFunction";
|
||||
|
||||
const API_VERSION = 4;
|
||||
|
||||
export class SQLSealRegisterApi {
|
||||
registeredApis: Array<SQLSealApi> = []
|
||||
constructor(private sqlSealPlugin: SqlSealPlugin) {
|
||||
sqlSealPlugin.register(() => {
|
||||
this.registeredApis.forEach(p => {
|
||||
p.unregister()
|
||||
})
|
||||
this.registeredApis = []
|
||||
})
|
||||
}
|
||||
|
||||
get sqlSealVersion() {
|
||||
return version
|
||||
}
|
||||
|
||||
get apiVersion() {
|
||||
return API_VERSION
|
||||
}
|
||||
|
||||
registerForPlugin(plugin: Plugin) {
|
||||
const api = new SQLSealApi(plugin, this.sqlSealPlugin)
|
||||
this.registeredApis.push(api)
|
||||
return api
|
||||
}
|
||||
}
|
||||
|
||||
interface RegisteredView {
|
||||
name: string;
|
||||
viewClass: any;
|
||||
}
|
||||
|
||||
// TODO: use the type from registrator
|
||||
export interface RegisteredFlag {
|
||||
name: string,
|
||||
key: string
|
||||
}
|
||||
|
||||
export class SQLSealApi {
|
||||
private views: Array<RegisteredView> = []
|
||||
private functions: Array<CellFunction> = []
|
||||
private flags: Array<RegisteredFlag> = []
|
||||
|
||||
constructor(private readonly plugin: Plugin, private sqlSealPlugin: SqlSealPlugin) {
|
||||
plugin.register(() => {
|
||||
this.unregister()
|
||||
})
|
||||
}
|
||||
|
||||
registerView(name: string, viewClass: RendererConfig) {
|
||||
this.views.push({
|
||||
name,
|
||||
viewClass
|
||||
})
|
||||
this.sqlSealPlugin.registerSQLSealView(name, viewClass)
|
||||
}
|
||||
|
||||
registerCustomFunction(fn: CellFunction) {
|
||||
this.functions.push(fn)
|
||||
this.sqlSealPlugin.registerSQLSealFunction(fn)
|
||||
}
|
||||
|
||||
registerTable<const columns extends string[]>(tableName: string, columns: columns) {
|
||||
return this.sqlSealPlugin.registerTable(this.plugin, tableName, columns)
|
||||
}
|
||||
|
||||
registerFlag(flag: RegisteredFlag) {
|
||||
this.flags.push(flag)
|
||||
this.sqlSealPlugin.registerSQLSealFlag(flag)
|
||||
}
|
||||
|
||||
unregister() {
|
||||
for(const view of this.views) {
|
||||
this.sqlSealPlugin.unregisterSQLSealView(view.name)
|
||||
}
|
||||
this.views = []
|
||||
|
||||
for(const fn of this.functions) {
|
||||
this.sqlSealPlugin.unregisterSQLSealFunction(fn.name)
|
||||
}
|
||||
|
||||
for(const flag of this.flags) {
|
||||
this.sqlSealPlugin.unregisterSQLSealFlag(flag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
import { App, PluginSettingTab, Setting, Plugin } from 'obsidian';
|
||||
|
||||
export interface SQLSealSettings {
|
||||
enableViewer: boolean;
|
||||
enableEditing: boolean;
|
||||
enableJSONViewer: boolean;
|
||||
enableDynamicUpdates: boolean;
|
||||
enableSyntaxHighlighting: boolean;
|
||||
defaultView: 'grid' | 'markdown' | 'html';
|
||||
gridItemsPerPage: number
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: SQLSealSettings = {
|
||||
enableViewer: true,
|
||||
enableEditing: true,
|
||||
enableJSONViewer: true,
|
||||
enableDynamicUpdates: true,
|
||||
enableSyntaxHighlighting: true,
|
||||
defaultView: 'grid',
|
||||
gridItemsPerPage: 20
|
||||
};
|
||||
|
||||
|
||||
export class SQLSealSettingsTab extends PluginSettingTab {
|
||||
plugin: Plugin;
|
||||
settings: SQLSealSettings;
|
||||
private onChangeFns: Array<(setting: SQLSealSettings) => void> = []
|
||||
|
||||
constructor(app: App, plugin: Plugin, settings: SQLSealSettings) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'CSV File Viewer' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Enable CSV Viewer')
|
||||
.setDesc('Enables CSV files in your vault and adds ability to display them in a grid.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.settings.enableViewer)
|
||||
.onChange(async (value) => {
|
||||
this.settings.enableViewer = value;
|
||||
if (!value) {
|
||||
this.settings.enableEditing = false;
|
||||
}
|
||||
await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
this.callChanges()
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Enable CSV Editing')
|
||||
.setDesc('Enables Editing functions in the CSV Viewer. This will add buttons to add columns, remove individual rows and columns; and edit each entry.')
|
||||
.setDisabled(!this.settings.enableViewer)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.settings.enableEditing)
|
||||
.onChange(async (value) => {
|
||||
this.settings.enableEditing = value;
|
||||
await this.plugin.saveData(this.settings);
|
||||
this.callChanges()
|
||||
}));
|
||||
|
||||
containerEl.createEl('h3', { text: 'JSON File Viewer' });
|
||||
new Setting(containerEl)
|
||||
.setName('Enable JSON Viewer')
|
||||
.setDesc('Enables JSON and JSON5 files in your files explorer and allows to preview them.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.settings.enableJSONViewer)
|
||||
.onChange(async (value) => {
|
||||
this.settings.enableJSONViewer = value;
|
||||
if (!value) {
|
||||
this.settings.enableJSONViewer = false;
|
||||
}
|
||||
await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
this.callChanges()
|
||||
}));
|
||||
|
||||
containerEl.createEl('h3', { text: 'Behavior' });
|
||||
new Setting(containerEl)
|
||||
.setName('Enable Dynamic Updates')
|
||||
.setDesc('SQLSeal will refresh your tables when underlying data changes.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.settings.enableDynamicUpdates)
|
||||
.onChange(async (value) => {
|
||||
this.settings.enableDynamicUpdates = value;
|
||||
if (!value) {
|
||||
this.settings.enableDynamicUpdates = false;
|
||||
}
|
||||
await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
this.callChanges()
|
||||
}));
|
||||
new Setting(containerEl)
|
||||
.setName('Enable Syntax Highlighting')
|
||||
.setDesc('Syntax will get highlighted when editing SQLSeal code')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.settings.enableSyntaxHighlighting)
|
||||
.onChange(async (value) => {
|
||||
this.settings.enableSyntaxHighlighting = value;
|
||||
if (!value) {
|
||||
this.settings.enableSyntaxHighlighting = false;
|
||||
}
|
||||
await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
this.callChanges()
|
||||
}));
|
||||
|
||||
|
||||
containerEl.createEl('h3', { text: 'Views' });
|
||||
new Setting(containerEl)
|
||||
.setName('Default View')
|
||||
.setDesc('This view will be used by default when you don\'t provide any view definition in your query')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('grid', 'Grid')
|
||||
.addOption('html', 'HTML Table')
|
||||
.addOption('markdown', 'Markdown Table')
|
||||
.setValue(this.settings.defaultView)
|
||||
.onChange(async (value) => {
|
||||
this.settings.defaultView = value as 'grid' | 'html' | 'markdown';
|
||||
if (!value) {
|
||||
this.settings.defaultView = DEFAULT_SETTINGS.defaultView
|
||||
}
|
||||
await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
this.callChanges()
|
||||
}));
|
||||
containerEl.createEl('h4', { text: 'Grid View' });
|
||||
new Setting(containerEl)
|
||||
.setName('Items per page ')
|
||||
.setDesc('How many items should display for each page of the Grid view')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('20', '20')
|
||||
.addOption('50', '50')
|
||||
.addOption('100', '100')
|
||||
.setValue(this.settings.gridItemsPerPage + '')
|
||||
.onChange(async (value) => {
|
||||
this.settings.gridItemsPerPage = parseInt(value, 10);
|
||||
if (!value) {
|
||||
this.settings.gridItemsPerPage = DEFAULT_SETTINGS.gridItemsPerPage
|
||||
}
|
||||
await this.plugin.saveData(this.settings);
|
||||
this.display();
|
||||
this.callChanges()
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private callChanges() {
|
||||
this.onChangeFns.forEach(f => f(this.settings))
|
||||
}
|
||||
|
||||
onChange(fn: (settings: SQLSealSettings) => void) {
|
||||
this.onChangeFns.push(fn)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import { SqlSealDatabase } from "./database/database";
|
||||
import { App, Plugin } from "obsidian";
|
||||
import { SqlSealCodeblockHandler } from "./codeblockHandler/SqlSealCodeblockHandler";
|
||||
import { Logger } from "./utils/logger";
|
||||
import { RendererRegistry } from "./renderer/rendererRegistry";
|
||||
import { Sync } from "./datamodel/sync";
|
||||
import { SqlSealInlineHandler } from "./codeblockHandler/inline/InlineCodeHandler";
|
||||
import { SealFileSync } from "./vaultSync/SealFileSync";
|
||||
import { FilesFileSyncTable } from "./vaultSync/tables/filesTable";
|
||||
import { TagsFileSyncTable } from "./vaultSync/tables/tagsTable";
|
||||
import { TasksFileSyncTable } from "./vaultSync/tables/tasksTable";
|
||||
import { LinksFileSyncTable } from "./vaultSync/tables/linksTable";
|
||||
import SqlSealPlugin from "./main";
|
||||
|
||||
export class SqlSeal {
|
||||
public db: SqlSealDatabase
|
||||
private codeBlockHandler: SqlSealCodeblockHandler
|
||||
private inlineCodeBlock: SqlSealInlineHandler
|
||||
public sync: Sync
|
||||
public fileSync: SealFileSync
|
||||
constructor(private readonly app: App, verbose = false, rendererRegistry: RendererRegistry, plugin: SqlSealPlugin) {
|
||||
this.db = new SqlSealDatabase(app, verbose)
|
||||
const logger = new Logger(verbose)
|
||||
|
||||
this.sync = new Sync(this.db, this.app.vault, this.app)
|
||||
this.sync.init()
|
||||
|
||||
this.codeBlockHandler = new SqlSealCodeblockHandler(app, this.db, plugin, this.sync, rendererRegistry)
|
||||
this.inlineCodeBlock = new SqlSealInlineHandler(app, this.db, plugin, this.sync)
|
||||
}
|
||||
|
||||
getHandler() {
|
||||
return this.codeBlockHandler.getHandler()
|
||||
}
|
||||
|
||||
getInlineHandler() {
|
||||
return this.inlineCodeBlock.getHandler()
|
||||
}
|
||||
|
||||
async startFileSync(plugin: Plugin) {
|
||||
this.fileSync = new SealFileSync(this.app, plugin, (name) => this.sync.triggerGlobalTableChange(name))
|
||||
|
||||
this.fileSync.addTablePlugin(new FilesFileSyncTable(this.db, this.app, plugin))
|
||||
this.fileSync.addTablePlugin(new TagsFileSyncTable(this.db, this.app))
|
||||
this.fileSync.addTablePlugin(new TasksFileSyncTable(this.db, this.app))
|
||||
this.fileSync.addTablePlugin(new LinksFileSyncTable(this.db, this.app))
|
||||
|
||||
await this.fileSync.init()
|
||||
}
|
||||
}
|
||||
4
src/styles/canvas.scss
Normal file
4
src/styles/canvas.scss
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Canvas specific styles
|
||||
.sqlseal-renderer-on-canvas {
|
||||
|
||||
}
|
||||
|
|
@ -4,4 +4,6 @@
|
|||
@use 'listView';
|
||||
@use 'syntaxHighlight';
|
||||
@use 'obsidianMinimal';
|
||||
@use 'agGrid';
|
||||
@use 'agGrid';
|
||||
@use 'settings';
|
||||
@use 'canvas';
|
||||
7
src/styles/settings.scss
Normal file
7
src/styles/settings.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.sqlseal-settings-warn {
|
||||
background: rgb(255, 166, 0);
|
||||
padding: 16px;
|
||||
border: 2px solid rgb(134, 87, 0);
|
||||
border-radius: 5px;
|
||||
margin: 0.5em 2em;
|
||||
}
|
||||
|
|
@ -1,435 +0,0 @@
|
|||
import { WorkspaceLeaf, TextFileView, Menu, Notice, MenuItem } from 'obsidian';
|
||||
import { parse, unparse } from 'papaparse';
|
||||
import { DeleteConfirmationModal } from '../modal/deleteConfirmationModal';
|
||||
import { RenameColumnModal } from '../modal/renameColumnModal';
|
||||
import { CodeSampleModal } from '../modal/showCodeSample';
|
||||
import { GridRenderer, GridRendererCommunicator } from '../renderer/GridRenderer';
|
||||
import { errorNotice } from '../utils/notice';
|
||||
import { ModernCellParser } from '../cellParser/ModernCellParser';
|
||||
import { ConfigObject, loadConfig, saveConfig } from 'src/utils/csvConfig';
|
||||
import { ColumnType } from '../utils/types';
|
||||
|
||||
const delay = (n: number) => new Promise(resolve => setTimeout(resolve, n))
|
||||
|
||||
export const CSV_VIEW_TYPE = "csv-viewer" as const;
|
||||
export const CSV_VIEW_EXTENSIONS = ['csv'];
|
||||
|
||||
export class CSVView extends TextFileView {
|
||||
private content: string;
|
||||
private table: HTMLTableElement;
|
||||
private config: ConfigObject;
|
||||
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
private enableEditing: boolean,
|
||||
private cellParser: ModernCellParser
|
||||
) {
|
||||
super(leaf);
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return CSV_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return this.file?.basename || 'CSV Viewer';
|
||||
}
|
||||
|
||||
async onload(): Promise<void> {
|
||||
super.onload();
|
||||
this.contentEl.addClass('csv-viewer-container');
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
this.renderCSV();
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
// Cleanup if needed
|
||||
}
|
||||
|
||||
async setViewData(data: string, clear: boolean): Promise<void> {
|
||||
this.content = data;
|
||||
await this.renderCSV();
|
||||
}
|
||||
|
||||
getViewData(): string {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.content = '';
|
||||
this.table.empty();
|
||||
}
|
||||
|
||||
private result: any;
|
||||
updateRow(newRowData: Record<string, any>) {
|
||||
this.result.data[parseInt(newRowData.__index, 10)] = newRowData;
|
||||
this.saveData(true)
|
||||
}
|
||||
|
||||
deleteRow(idx: number) {
|
||||
this.result.data.splice(idx, 1)
|
||||
this.saveData()
|
||||
}
|
||||
|
||||
deleteColumn(column: string) {
|
||||
this.result.fields = this.result.fields.filter((c: string) => c !== column)
|
||||
this.saveData()
|
||||
}
|
||||
|
||||
addNewColumn(columnName: string) {
|
||||
if (this.result.fields.indexOf(columnName) > -1) {
|
||||
throw new Error('Column already exists')
|
||||
}
|
||||
this.result.fields.push(columnName)
|
||||
this.result.data = this.result.data.map((d: any) => ({ [columnName]: '', ...d }))
|
||||
this.saveData()
|
||||
}
|
||||
|
||||
renameColumn(oldName: string, newName: string) {
|
||||
if (!newName) {
|
||||
return
|
||||
}
|
||||
if (this.result.fields.contains(newName)) {
|
||||
errorNotice('Column already exists')
|
||||
return
|
||||
}
|
||||
const oldNameIdx = this.result.fields.indexOf(oldName)
|
||||
if (oldNameIdx < 0) {
|
||||
errorNotice('Old column does not exist')
|
||||
return
|
||||
}
|
||||
this.result.fields[oldNameIdx] = newName
|
||||
this.result.data = this.result.data.map((d: Record<string, unknown>) => {
|
||||
d[newName] = d[oldName]
|
||||
delete d[oldName]
|
||||
return d
|
||||
})
|
||||
this.saveData()
|
||||
this.loadDataIntoGrid()
|
||||
}
|
||||
|
||||
setIsEditable(newValue: boolean) {
|
||||
this.enableEditing = newValue
|
||||
// FIXME: if there already rendered view, use this to rerender it?
|
||||
}
|
||||
|
||||
saveData(noRefresh: boolean = false) {
|
||||
if (noRefresh) {
|
||||
this.refreshSkip = Date.now() + 500
|
||||
}
|
||||
|
||||
// Map results
|
||||
const res = [...this.result.data].map(r => {
|
||||
const r2 = {...r}
|
||||
Object.keys(r).forEach(k => {
|
||||
if (typeof r[k] === 'boolean') {
|
||||
r2[k] = r[k] ? 1 : 0
|
||||
}
|
||||
})
|
||||
return r2
|
||||
})
|
||||
|
||||
const output = unparse({ ...this.result, data: res })
|
||||
this.app.vault.modify(this.file!, output)
|
||||
this.refreshTypes()
|
||||
}
|
||||
|
||||
createRow() {
|
||||
this.result.data.push(this.result.fields.reduce((acc: Record<string, any>, f: string) => ({ ...acc, [f]: '' }), {}))
|
||||
this.saveData()
|
||||
|
||||
}
|
||||
|
||||
getColumnType(columnName: string) {
|
||||
if (this.config.columnDefinitions[columnName]) {
|
||||
return this.config.columnDefinitions[columnName].type
|
||||
}
|
||||
return 'auto'
|
||||
}
|
||||
|
||||
async changeColumnType(columnName: string, type: ColumnType) {
|
||||
const prev = this.config.columnDefinitions[columnName] ?? {}
|
||||
this.config.columnDefinitions[columnName] = {
|
||||
...prev,
|
||||
type: type
|
||||
}
|
||||
await this.saveConfig()
|
||||
}
|
||||
|
||||
async loadConfig() {
|
||||
while (!this.file) {
|
||||
await delay(100)
|
||||
}
|
||||
this.config = await loadConfig(this.file, this.app.vault)
|
||||
}
|
||||
|
||||
async saveConfig() {
|
||||
await saveConfig(this.file!, this.config, this.app.vault)
|
||||
}
|
||||
|
||||
private getColumnConfigurations(columns: string[]) {
|
||||
return columns.map(f => {
|
||||
if (!this.config) {
|
||||
return {
|
||||
field: f
|
||||
}
|
||||
}
|
||||
const def = this.config.columnDefinitions[f]?.type
|
||||
if (!def || def === 'auto') {
|
||||
return { field: f }
|
||||
}
|
||||
if (def === 'date') {
|
||||
return {
|
||||
field: f,
|
||||
cellDataType: 'dateString'
|
||||
}
|
||||
}
|
||||
return {
|
||||
field: f,
|
||||
cellDataType: def
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
refreshTypes() {
|
||||
if (this.gridCommunicator) {
|
||||
const columns = this.result.fields
|
||||
if (columns && columns.length) {
|
||||
this.gridCommunicator.gridApi.setGridOption('columnDefs', this.getColumnConfigurations(columns))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveColumn(name: string, toIndex: number) {
|
||||
let fields = this.result.fields as Array<string>
|
||||
fields = fields.filter(f => f !== name)
|
||||
fields = [
|
||||
...fields.slice(0, toIndex),
|
||||
name,
|
||||
...fields.slice(toIndex)
|
||||
]
|
||||
this.result.fields = fields
|
||||
this.saveData()
|
||||
}
|
||||
|
||||
api: any = null;
|
||||
gridCommunicator: GridRendererCommunicator | null = null
|
||||
refreshSkip: number = 0
|
||||
|
||||
formatWithTypes(d: Record<string, string | boolean | number>) {
|
||||
Object.entries(this.config.columnDefinitions).forEach(([key, value]) => {
|
||||
if (!d[key]) {
|
||||
return
|
||||
}
|
||||
if (value.type === 'boolean') {
|
||||
if (d[key] === 'false' || d[key] === '0') {
|
||||
d[key] = false
|
||||
}
|
||||
d[key] = !!d[key]
|
||||
}
|
||||
if (value.type === 'number') {
|
||||
if (d[key] === null || d[key] === '') {
|
||||
d[key] = ''
|
||||
} else {
|
||||
d[key] = parseFloat(d[key] as string)
|
||||
}
|
||||
}
|
||||
if (value.type === 'date') {
|
||||
// try parsing
|
||||
const val = d[key] as string
|
||||
const date = new Date(val)
|
||||
d[key] = date.toISOString().split('T')[0]
|
||||
}
|
||||
})
|
||||
return d
|
||||
}
|
||||
|
||||
prepareData() {
|
||||
const result = parse(this.content, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
});
|
||||
const data = result.data.map((d: any, i) => ({
|
||||
...this.formatWithTypes(d),
|
||||
__index: i.toString()
|
||||
}))
|
||||
return {
|
||||
data: data,
|
||||
fields: result.meta.fields
|
||||
}
|
||||
}
|
||||
|
||||
loadDataIntoGrid() {
|
||||
if (this.refreshSkip > Date.now()) {
|
||||
return
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
const result = this.prepareData()
|
||||
this.result = result
|
||||
this.refreshTypes()
|
||||
this.api!.render(result)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
isLoading: boolean = false
|
||||
|
||||
private async renderCSV() {
|
||||
|
||||
if (this.isLoading) {
|
||||
if (this.api) {
|
||||
this.loadDataIntoGrid()
|
||||
}
|
||||
return
|
||||
}
|
||||
this.isLoading = true
|
||||
|
||||
this.contentEl.empty()
|
||||
const csvEditorDiv = this.contentEl.createDiv({ cls: 'sql-seal-csv-editor' })
|
||||
|
||||
const buttonsRow = csvEditorDiv.createDiv({ cls: 'sql-seal-csv-viewer-buttons' })
|
||||
await this.loadConfig()
|
||||
if (this.enableEditing) {
|
||||
const createColumn = buttonsRow.createEl('button', { text: 'Add Column' })
|
||||
const createRow = buttonsRow.createEl('button', { text: 'Add Row' })
|
||||
|
||||
createColumn.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
const modal = new RenameColumnModal(this.app, (res) => {
|
||||
this.addNewColumn(res)
|
||||
})
|
||||
modal.open()
|
||||
})
|
||||
|
||||
createRow.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
this.createRow()
|
||||
})
|
||||
}
|
||||
const generateSqlCode = buttonsRow.createEl('button', { text: 'Generate SQLSeal code' })
|
||||
const gridEl = csvEditorDiv.createDiv({ cls: 'sql-seal-csv-viewer' })
|
||||
|
||||
|
||||
generateSqlCode.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
if (!this.file) {
|
||||
return
|
||||
}
|
||||
const modal = new CodeSampleModal(this.app, this.file)
|
||||
modal.open()
|
||||
})
|
||||
|
||||
const grid = new GridRenderer(this.app, null)
|
||||
const csvView = this;
|
||||
const data = this.prepareData()
|
||||
this.result = data
|
||||
const api = grid.render({
|
||||
columnDefs: this.getColumnConfigurations(data.fields ?? []),
|
||||
defaultColDef: {
|
||||
editable: this.enableEditing,
|
||||
headerComponentParams: {
|
||||
enableMenu: this.enableEditing,
|
||||
showColumnMenu: function (e: any) {
|
||||
const menu = new Menu()
|
||||
|
||||
menu.addItem(item => {
|
||||
item.setTitle('Rename Column')
|
||||
item.onClick(() => {
|
||||
const modal = new RenameColumnModal(csvView.app, (res) => {
|
||||
csvView.renameColumn(
|
||||
this.column.userProvidedColDef.field, res)
|
||||
})
|
||||
modal.open()
|
||||
})
|
||||
})
|
||||
|
||||
menu.addItem(item => {
|
||||
item.setTitle('Delete Column')
|
||||
item.onClick(() => {
|
||||
const colName = this.column.userProvidedColDef.field
|
||||
const modal = new DeleteConfirmationModal(csvView.app, `column ${colName}`, () => {
|
||||
csvView.deleteColumn(colName)
|
||||
})
|
||||
modal.open()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// FIXME: rework it to submenus.
|
||||
menu.addSeparator()
|
||||
menu.addItem(item => {
|
||||
// item.setDisabled(true)
|
||||
item.setTitle('Data Type')
|
||||
// item.setIsLabel(true)
|
||||
const ipfSubmenu = (item as any).setSubmenu();
|
||||
const types = ['auto', 'text', 'number', 'boolean', 'date'] as ColumnType[]
|
||||
|
||||
|
||||
|
||||
const colName = this.column.userProvidedColDef.field
|
||||
|
||||
const current = csvView.getColumnType(colName)
|
||||
types.forEach(type => {
|
||||
ipfSubmenu.addItem((subItem: MenuItem) => {
|
||||
const checkbox = type === current ? '✓ ' : ''
|
||||
subItem.setTitle(checkbox + type)
|
||||
subItem.onClick(() => {
|
||||
csvView.changeColumnType(colName, type)
|
||||
csvView.refreshTypes()
|
||||
csvView.loadDataIntoGrid()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const pos = e.getBoundingClientRect();
|
||||
menu.showAtPosition({ x: pos.x, y: pos.y + 20 })
|
||||
}
|
||||
}
|
||||
},
|
||||
enableCellTextSelection: false,
|
||||
ensureDomOrder: false,
|
||||
paginationAutoPageSize: true,
|
||||
suppressMovableColumns: false,
|
||||
onColumnMoved: (e) => {
|
||||
if (!e.finished) {
|
||||
return
|
||||
}
|
||||
const columnName = e.column?.getUserProvidedColDef()
|
||||
if (!columnName) {
|
||||
return
|
||||
}
|
||||
csvView.moveColumn(columnName?.field!, e.toIndex!)
|
||||
},
|
||||
domLayout: 'normal',
|
||||
getRowId: (p) => p.data.__index,
|
||||
onCellValueChanged: (event) => {
|
||||
if (event.rowIndex === null) {
|
||||
return;
|
||||
}
|
||||
this.updateRow(event.data)
|
||||
},
|
||||
onCellContextMenu: (e) => {
|
||||
if (!this.enableEditing) {
|
||||
return
|
||||
}
|
||||
const menu = new Menu()
|
||||
menu.addItem(item => {
|
||||
item.setTitle('Delete Row')
|
||||
item.onClick(() => {
|
||||
const modal = new DeleteConfirmationModal(csvView.app, `row`, () => {
|
||||
csvView.deleteRow(e.data.__index)
|
||||
})
|
||||
modal.open()
|
||||
})
|
||||
})
|
||||
menu.showAtMouseEvent(e.event as any)
|
||||
}
|
||||
}, gridEl, { sourcePath: this.file?.path || '' })
|
||||
this.api = api;
|
||||
this.gridCommunicator = api.communicator
|
||||
api.render(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
import { ItemView, TFile, WorkspaceLeaf } from 'obsidian';
|
||||
import SqlSealPlugin from '../main';
|
||||
|
||||
export const SIDEBAR_VIEW_TYPE = 'sql-seal-sidebar-view';
|
||||
|
||||
export class SidebarView extends ItemView {
|
||||
private currentFile: TFile | null = null;
|
||||
public contentEl: HTMLElement;
|
||||
private plugin: SqlSealPlugin;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: SqlSealPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return SIDEBAR_VIEW_TYPE;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return 'SQL Seal Sidebar';
|
||||
}
|
||||
|
||||
async onload(): Promise<void> {
|
||||
super.onload();
|
||||
|
||||
// Create container for the sidebar content
|
||||
this.contentEl = this.containerEl.createDiv('sql-seal-sidebar-container');
|
||||
|
||||
// Register event handler for file changes
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-open', (file: TFile | null) => {
|
||||
this.currentFile = file;
|
||||
this.updateView();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async updateView(): Promise<void> {
|
||||
this.contentEl.empty();
|
||||
|
||||
if (!this.currentFile) {
|
||||
this.contentEl.createEl('div', {
|
||||
text: 'No file is currently open',
|
||||
cls: 'sql-seal-sidebar-empty'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get file metadata or frontmatter
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.currentFile);
|
||||
const frontmatter = fileCache?.frontmatter || {};
|
||||
|
||||
// Add file-specific properties to frontmatter
|
||||
const enhancedFrontmatter = {
|
||||
...frontmatter,
|
||||
'@file': {
|
||||
name: this.currentFile.name,
|
||||
path: this.currentFile.path,
|
||||
basename: this.currentFile.basename,
|
||||
extension: this.currentFile.extension,
|
||||
stat: {
|
||||
ctime: this.currentFile.stat.ctime,
|
||||
mtime: this.currentFile.stat.mtime,
|
||||
size: this.currentFile.stat.size
|
||||
}
|
||||
},
|
||||
'@tags': fileCache?.tags?.map(tag => tag.tag) || [],
|
||||
'@links': fileCache?.links?.map(link => link.link) || [],
|
||||
'@headings': fileCache?.headings?.map(h => ({
|
||||
text: h.heading,
|
||||
level: h.level
|
||||
})) || []
|
||||
};
|
||||
|
||||
// Check if there's a specific SQL query defined in frontmatter
|
||||
const query = frontmatter?.['sql-seal-query'];
|
||||
const view = frontmatter?.['sql-seal-view'] || 'table'; // Default to table view
|
||||
|
||||
if (query) {
|
||||
const result = await this.plugin.sqlSeal.db.select(query, enhancedFrontmatter);
|
||||
this.renderQueryResults(result.data, view);
|
||||
} else {
|
||||
this.contentEl.createEl('div', {
|
||||
text: 'No SQL query defined for this file',
|
||||
cls: 'sql-seal-sidebar-empty'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.contentEl.createEl('div', {
|
||||
text: `Error: ${error.message}`,
|
||||
cls: 'sql-seal-sidebar-error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private renderQueryResults(results: any[], view: string): void {
|
||||
if (!results || results.length === 0) {
|
||||
this.contentEl.createEl('div', {
|
||||
text: 'No results found',
|
||||
cls: 'sql-seal-sidebar-empty'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (view === 'list') {
|
||||
this.renderListView(results);
|
||||
} else {
|
||||
this.renderTableView(results);
|
||||
}
|
||||
}
|
||||
|
||||
private renderTableView(results: any[]): void {
|
||||
const table = this.contentEl.createEl('table', {
|
||||
cls: 'sql-seal-sidebar-table'
|
||||
});
|
||||
|
||||
// Create header row
|
||||
const headerRow = table.createEl('tr');
|
||||
Object.keys(results[0]).forEach(key => {
|
||||
headerRow.createEl('th', { text: key });
|
||||
});
|
||||
|
||||
// Create data rows
|
||||
results.forEach(row => {
|
||||
const tr = table.createEl('tr');
|
||||
Object.values(row).forEach(value => {
|
||||
tr.createEl('td', { text: String(value) });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private renderListView(results: any[]): void {
|
||||
const list = this.contentEl.createEl('ul', {
|
||||
cls: 'sql-seal-sidebar-list'
|
||||
});
|
||||
|
||||
results.forEach(row => {
|
||||
const li = list.createEl('li');
|
||||
Object.entries(row).forEach(([key, value]) => {
|
||||
const span = li.createEl('span', {
|
||||
cls: 'sql-seal-sidebar-list-item'
|
||||
});
|
||||
span.createEl('span', {
|
||||
text: key,
|
||||
cls: 'sql-seal-sidebar-list-key'
|
||||
});
|
||||
span.createEl('span', {
|
||||
text: ': '
|
||||
});
|
||||
span.createEl('span', {
|
||||
text: String(value),
|
||||
cls: 'sql-seal-sidebar-list-value'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue