From 285684dfc4ed87c3fa48a8189b58592e489c38ae Mon Sep 17 00:00:00 2001 From: Kacper Kula Date: Sun, 10 Aug 2025 10:34:54 +0200 Subject: [PATCH] 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 --- .changeset/mean-paths-fold.md | 5 + .changeset/rare-sloths-train.md | 5 + .changeset/twenty-colts-roll.md | 5 + esbuild.config.mjs | 2 +- package.json | 2 + pnpm-lock.yaml | 510 +++++++++++++++++- src/cellParser/RenderContext.ts | 62 --- src/cellParser/factory.ts | 13 - src/codeblockHandler/CodeblockProcessor.ts | 144 ----- src/editorExtension/inlineCodeBlock.ts | 125 ----- src/main.ts | 265 +-------- src/modules/api/init.ts | 55 ++ src/modules/api/module.ts | 21 + src/modules/api/pluginApi/sqlSealApi.ts | 134 +++++ .../api/pluginApi}/table.ts | 8 +- src/modules/contextMenu/init.ts | 68 +++ src/modules/contextMenu/module.ts | 16 + src/{ => modules}/database/database.ts | 40 +- src/modules/database/factory.ts | 16 + src/modules/database/module.ts | 13 + src/{ => modules}/database/worker/database.ts | 8 +- src/modules/debug/DityGraphView.ts | 43 ++ src/modules/debug/dityGraph.ts | 39 ++ src/modules/debug/module.ts | 16 + .../codeblockHandler/CodeblockProcessor.ts | 210 ++++++++ .../SqlSealCodeblockHandler.ts | 21 +- .../inline/InlineCodeHandler.ts | 18 +- .../inline/InlineProcessor.ts | 19 +- src/modules/editor/init.ts | 85 +++ src/modules/editor/module.ts | 30 ++ .../editor}/parser.test.ts | 0 src/{grammar => modules/editor}/parser.ts | 2 +- .../editor}/renderer/GridRenderer.ts | 19 +- .../editor}/renderer/ListRenderer.ts | 6 +- .../editor}/renderer/MarkdownRenderer.ts | 8 +- .../editor}/renderer/TableRenderer.ts | 6 +- .../editor}/renderer/TemplateRenderer.ts | 6 +- .../editor}/renderer/rendererRegistry.ts | 4 +- .../editor}/sql/sqlTransformer.ts | 0 .../editor}/sql/transformer.test.ts | 0 src/modules/main/init.ts | 36 ++ src/modules/main/module.ts | 82 +++ src/modules/settings/SQLSealSettingsTab.ts | 124 +++++ src/modules/settings/Settings.ts | 25 + src/modules/settings/init.ts | 32 ++ .../modal/deleteConfirmationModal.ts | 0 .../settings}/modal/renameColumnModal.ts | 0 .../settings}/modal/showCodeSample.ts | 2 +- src/modules/settings/module.ts | 22 + src/modules/settings/settingsFactory.ts | 19 + .../settingsTabSection/SettingsCSVControls.ts | 89 +++ .../settingsTabSection/SettingsControls.ts | 12 + .../SettingsJsonControls.ts | 71 +++ src/modules/settings/utils/viewInspector.ts | 12 + src/modules/settings/view/CSVView.ts | 465 ++++++++++++++++ src/{ => modules/settings}/view/JsonView.ts | 0 .../sync/fileSyncController/FileSync.ts} | 2 +- .../fileSyncController/fileSyncFactory.ts | 29 + src/modules/sync/module.ts | 22 + .../sync}/repository/abstractRepository.ts | 0 .../sync}/repository/configuration.ts | 2 +- .../sync}/repository/tableAliases.ts | 14 +- .../sync}/repository/tableDefinitions.ts | 12 +- src/modules/sync/sync/init.ts | 17 + src/{datamodel => modules/sync/sync}/sync.ts | 16 +- src/modules/sync/sync/syncFactory.ts | 19 + .../sync}/tables/abstractFileSyncTable.ts | 2 +- .../sync/sync}/tables/filesTable.ts | 10 +- .../sync/sync}/tables/linksTable.ts | 0 .../sync/sync}/tables/tagsTable.ts | 0 .../sync/sync}/tables/tasksTable.ts | 0 .../sync}/syncStrategy/CsvFileSyncStrategy.ts | 6 +- .../syncStrategy/JSONFileSyncStrategy.ts | 2 +- .../syncStrategy/MarkdownTableSyncStrategy.ts | 4 +- .../sync}/syncStrategy/SyncStrategyFactory.ts | 2 +- .../syncStrategy/abstractSyncStrategy.ts | 2 +- .../sync}/syncStrategy/types.ts | 0 .../cellParser/CellFunction.ts | 0 .../cellParser/ModernCellParser.ts | 4 +- .../syntaxHighlight/cellParser/factory.ts | 32 ++ .../cellParser/parseResults.ts | 0 .../cellParser/parser/checkbox.ts | 0 .../cellParser/parser/image.ts | 2 +- .../cellParser/parser/link.ts | 4 +- .../editorExtension/inlineCodeBlock.ts | 143 +++++ .../editorExtension/syntaxHighlight.ts | 4 +- .../editorExtension/widgets/FilePathWidget.ts | 2 +- .../highlighter/handlebarsHighlighter.ts | 0 .../grammar/highlighter/jsHighlighter.ts | 0 .../grammar/highlighterOperation.ts | 0 src/modules/syntaxHighlight/init.ts | 23 + src/modules/syntaxHighlight/module.ts | 23 + src/pluginApi/sqlSealApi.ts | 93 ---- src/settings/SQLSealSettingsTab.ts | 162 ------ src/sqlSeal.ts | 50 -- src/styles/canvas.scss | 4 + src/styles/main.scss | 4 +- src/styles/settings.scss | 7 + src/view/CSVView.ts | 435 --------------- src/view/SidebarView.ts | 159 ------ styles.css | 2 +- tsconfig.json | 3 +- types-package/src/index.ts | 4 +- 103 files changed, 2722 insertions(+), 1639 deletions(-) create mode 100644 .changeset/mean-paths-fold.md create mode 100644 .changeset/rare-sloths-train.md create mode 100644 .changeset/twenty-colts-roll.md delete mode 100644 src/cellParser/RenderContext.ts delete mode 100644 src/cellParser/factory.ts delete mode 100644 src/codeblockHandler/CodeblockProcessor.ts delete mode 100644 src/editorExtension/inlineCodeBlock.ts create mode 100644 src/modules/api/init.ts create mode 100644 src/modules/api/module.ts create mode 100644 src/modules/api/pluginApi/sqlSealApi.ts rename src/{database => modules/api/pluginApi}/table.ts (88%) create mode 100644 src/modules/contextMenu/init.ts create mode 100644 src/modules/contextMenu/module.ts rename src/{ => modules}/database/database.ts (73%) create mode 100644 src/modules/database/factory.ts create mode 100644 src/modules/database/module.ts rename src/{ => modules}/database/worker/database.ts (96%) create mode 100644 src/modules/debug/DityGraphView.ts create mode 100644 src/modules/debug/dityGraph.ts create mode 100644 src/modules/debug/module.ts create mode 100644 src/modules/editor/codeblockHandler/CodeblockProcessor.ts rename src/{ => modules/editor}/codeblockHandler/SqlSealCodeblockHandler.ts (54%) rename src/{ => modules/editor}/codeblockHandler/inline/InlineCodeHandler.ts (61%) rename src/{ => modules/editor}/codeblockHandler/inline/InlineProcessor.ts (80%) create mode 100644 src/modules/editor/init.ts create mode 100644 src/modules/editor/module.ts rename src/{grammar => modules/editor}/parser.test.ts (100%) rename src/{grammar => modules/editor}/parser.ts (99%) rename src/{ => modules/editor}/renderer/GridRenderer.ts (91%) rename src/{ => modules/editor}/renderer/ListRenderer.ts (92%) rename src/{ => modules/editor}/renderer/MarkdownRenderer.ts (85%) rename src/{ => modules/editor}/renderer/TableRenderer.ts (94%) rename src/{ => modules/editor}/renderer/TemplateRenderer.ts (90%) rename src/{ => modules/editor}/renderer/rendererRegistry.ts (95%) rename src/{ => modules/editor}/sql/sqlTransformer.ts (100%) rename src/{ => modules/editor}/sql/transformer.test.ts (100%) create mode 100644 src/modules/main/init.ts create mode 100644 src/modules/main/module.ts create mode 100644 src/modules/settings/SQLSealSettingsTab.ts create mode 100644 src/modules/settings/Settings.ts create mode 100644 src/modules/settings/init.ts rename src/{ => modules/settings}/modal/deleteConfirmationModal.ts (100%) rename src/{ => modules/settings}/modal/renameColumnModal.ts (100%) rename src/{ => modules/settings}/modal/showCodeSample.ts (95%) create mode 100644 src/modules/settings/module.ts create mode 100644 src/modules/settings/settingsFactory.ts create mode 100644 src/modules/settings/settingsTabSection/SettingsCSVControls.ts create mode 100644 src/modules/settings/settingsTabSection/SettingsControls.ts create mode 100644 src/modules/settings/settingsTabSection/SettingsJsonControls.ts create mode 100644 src/modules/settings/utils/viewInspector.ts create mode 100644 src/modules/settings/view/CSVView.ts rename src/{ => modules/settings}/view/JsonView.ts (100%) rename src/{vaultSync/SealFileSync.ts => modules/sync/fileSyncController/FileSync.ts} (97%) create mode 100644 src/modules/sync/fileSyncController/fileSyncFactory.ts create mode 100644 src/modules/sync/module.ts rename src/{datamodel => modules/sync}/repository/abstractRepository.ts (100%) rename src/{datamodel => modules/sync}/repository/configuration.ts (87%) rename src/{datamodel => modules/sync}/repository/tableAliases.ts (84%) rename src/{datamodel => modules/sync}/repository/tableDefinitions.ts (91%) create mode 100644 src/modules/sync/sync/init.ts rename src/{datamodel => modules/sync/sync}/sync.ts (91%) create mode 100644 src/modules/sync/sync/syncFactory.ts rename src/{vaultSync => modules/sync/sync}/tables/abstractFileSyncTable.ts (90%) rename src/{vaultSync => modules/sync/sync}/tables/filesTable.ts (89%) rename src/{vaultSync => modules/sync/sync}/tables/linksTable.ts (100%) rename src/{vaultSync => modules/sync/sync}/tables/tagsTable.ts (100%) rename src/{vaultSync => modules/sync/sync}/tables/tasksTable.ts (100%) rename src/{datamodel => modules/sync}/syncStrategy/CsvFileSyncStrategy.ts (93%) rename src/{datamodel => modules/sync}/syncStrategy/JSONFileSyncStrategy.ts (97%) rename src/{datamodel => modules/sync}/syncStrategy/MarkdownTableSyncStrategy.ts (98%) rename src/{datamodel => modules/sync}/syncStrategy/SyncStrategyFactory.ts (95%) rename src/{datamodel => modules/sync}/syncStrategy/abstractSyncStrategy.ts (91%) rename src/{datamodel => modules/sync}/syncStrategy/types.ts (100%) rename src/{ => modules/syntaxHighlight}/cellParser/CellFunction.ts (100%) rename src/{ => modules/syntaxHighlight}/cellParser/ModernCellParser.ts (98%) create mode 100644 src/modules/syntaxHighlight/cellParser/factory.ts rename src/{ => modules/syntaxHighlight}/cellParser/parseResults.ts (100%) rename src/{ => modules/syntaxHighlight}/cellParser/parser/checkbox.ts (100%) rename src/{ => modules/syntaxHighlight}/cellParser/parser/image.ts (96%) rename src/{ => modules/syntaxHighlight}/cellParser/parser/link.ts (97%) create mode 100644 src/modules/syntaxHighlight/editorExtension/inlineCodeBlock.ts rename src/{ => modules/syntaxHighlight}/editorExtension/syntaxHighlight.ts (96%) rename src/{ => modules/syntaxHighlight}/editorExtension/widgets/FilePathWidget.ts (90%) rename src/{ => modules/syntaxHighlight}/grammar/highlighter/handlebarsHighlighter.ts (100%) rename src/{ => modules/syntaxHighlight}/grammar/highlighter/jsHighlighter.ts (100%) rename src/{ => modules/syntaxHighlight}/grammar/highlighterOperation.ts (100%) create mode 100644 src/modules/syntaxHighlight/init.ts create mode 100644 src/modules/syntaxHighlight/module.ts delete mode 100644 src/pluginApi/sqlSealApi.ts delete mode 100644 src/settings/SQLSealSettingsTab.ts delete mode 100644 src/sqlSeal.ts create mode 100644 src/styles/canvas.scss create mode 100644 src/styles/settings.scss delete mode 100644 src/view/CSVView.ts delete mode 100644 src/view/SidebarView.ts diff --git a/.changeset/mean-paths-fold.md b/.changeset/mean-paths-fold.md new file mode 100644 index 0000000..000454e --- /dev/null +++ b/.changeset/mean-paths-fold.md @@ -0,0 +1,5 @@ +--- +"sqlseal": minor +--- + +tables and charts now render better on canvas and work when you use external files too diff --git a/.changeset/rare-sloths-train.md b/.changeset/rare-sloths-train.md new file mode 100644 index 0000000..5e5f9f2 --- /dev/null +++ b/.changeset/rare-sloths-train.md @@ -0,0 +1,5 @@ +--- +"sqlseal": patch +--- + +csv and json views are now only registered when not colliding with other existing plugins diff --git a/.changeset/twenty-colts-roll.md b/.changeset/twenty-colts-roll.md new file mode 100644 index 0000000..d354490 --- /dev/null +++ b/.changeset/twenty-colts-roll.md @@ -0,0 +1,5 @@ +--- +"sqlseal": minor +--- + +reworking plugin internals to organise code into modules diff --git a/esbuild.config.mjs b/esbuild.config.mjs index a73226b..9402e78 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -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', diff --git a/package.json b/package.json index 5c703a3..5e8e197 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19f8d0a..e0226d9 100644 --- a/pnpm-lock.yaml +++ b/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' diff --git a/src/cellParser/RenderContext.ts b/src/cellParser/RenderContext.ts deleted file mode 100644 index 43bf8e5..0000000 --- a/src/cellParser/RenderContext.ts +++ /dev/null @@ -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)[] = [] - 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 = [] - } -} \ No newline at end of file diff --git a/src/cellParser/factory.ts b/src/cellParser/factory.ts deleted file mode 100644 index 7a2e869..0000000 --- a/src/cellParser/factory.ts +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/codeblockHandler/CodeblockProcessor.ts b/src/codeblockHandler/CodeblockProcessor.ts deleted file mode 100644 index 8179d10..0000000 --- a/src/codeblockHandler/CodeblockProcessor.ts +++ /dev/null @@ -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 - }))) - } -} \ No newline at end of file diff --git a/src/editorExtension/inlineCodeBlock.ts b/src/editorExtension/inlineCodeBlock.ts deleted file mode 100644 index a2fe96f..0000000 --- a/src/editorExtension/inlineCodeBlock.ts +++ /dev/null @@ -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(); - 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; - } -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 3718839..2b6009c 100644 --- a/src/main.ts +++ b/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(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() {} } - diff --git a/src/modules/api/init.ts b/src/modules/api/init.ts new file mode 100644 index 0000000..8de5362 --- /dev/null +++ b/src/modules/api/init.ts @@ -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()([ + "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] = []; + }; + } +} diff --git a/src/modules/api/module.ts b/src/modules/api/module.ts new file mode 100644 index 0000000..1136a3b --- /dev/null +++ b/src/modules/api/module.ts @@ -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 \ No newline at end of file diff --git a/src/modules/api/pluginApi/sqlSealApi.ts b/src/modules/api/pluginApi/sqlSealApi.ts new file mode 100644 index 0000000..0c0055a --- /dev/null +++ b/src/modules/api/pluginApi/sqlSealApi.ts @@ -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 = [] + 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 = [] + private functions: Array = [] + private flags: Array = [] + + 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(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) + } + } +} diff --git a/src/database/table.ts b/src/modules/api/pluginApi/table.ts similarity index 88% rename from src/database/table.ts rename to src/modules/api/pluginApi/table.ts index ac09ca5..83bc9aa 100644 --- a/src/database/table.ts +++ b/src/modules/api/pluginApi/table.ts @@ -1,4 +1,4 @@ -import { SqlSealDatabase } from "./database"; +import { SqlSealDatabase } from "../../database/database" type Item = Record @@ -14,11 +14,17 @@ export class DatabaseTable { async getAll() { const data = await this.db.select(`SELECT * FROM ${this.tableName}`, {}) + if (!data) { + return [] + } return data.data as Item[] } 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[] } diff --git a/src/modules/contextMenu/init.ts b/src/modules/contextMenu/init.ts new file mode 100644 index 0000000..0873e32 --- /dev/null +++ b/src/modules/contextMenu/init.ts @@ -0,0 +1,68 @@ +import { makeInjector } from "@hypersphere/dity" +import { ContextMenuModule } from "./module" +import { App, Menu, Plugin, TAbstractFile, TFile, TFolder } from "obsidian" + +@(makeInjector()([ + '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) + ); + } + } +} \ No newline at end of file diff --git a/src/modules/contextMenu/module.ts b/src/modules/contextMenu/module.ts new file mode 100644 index 0000000..f44ef27 --- /dev/null +++ b/src/modules/contextMenu/module.ts @@ -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 diff --git a/src/database/database.ts b/src/modules/database/database.ts similarity index 73% rename from src/database/database.ts rename to src/modules/database/database.ts index 987b695..0c6af1b 100644 --- a/src/database/database.ts +++ b/src/modules/database/database.ts @@ -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 + db?: Comlink.Remote private isConnected = false - private connectingPromise: Promise; - constructor(private readonly app: App, private readonly verbose = false) { + private connectingPromise?: Promise; + 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>, key: string = 'id') { - return this.db.updateData(name, data, key) + return this.db?.updateData(name, data, key) } async deleteData(name: string, data: Array>, key: string = 'id') { - return this.db.deleteData(name, data, key) + return this.db?.deleteData(name, data, key) } async insertData(name: string, inData: Array>) { - 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) { - const result = await this.db.select(statement, frontmatter) + const result = await this.db?.select(statement, frontmatter) return result } async explain(statement: string, frontmatter: Record) { - const explainResults = await this.db.explainQuery(statement, frontmatter) + const explainResults = await this.db?.explainQuery(statement, frontmatter) let strResult = ''; const map = new Map() 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()([ + 'app' +])) +export class DatabaseFactory { + async make(app: App) { + const db = new SqlSealDatabase(app) + await db.connect() + return db + } +} \ No newline at end of file diff --git a/src/modules/database/module.ts b/src/modules/database/module.ts new file mode 100644 index 0000000..8b483ec --- /dev/null +++ b/src/modules/database/module.ts @@ -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 diff --git a/src/database/worker/database.ts b/src/modules/database/worker/database.ts similarity index 96% rename from src/database/worker/database.ts rename to src/modules/database/worker/database.ts index e5915d4..2475588 100644 --- a/src/database/worker/database.ts +++ b/src/modules/database/worker/database.ts @@ -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) { diff --git a/src/modules/debug/DityGraphView.ts b/src/modules/debug/DityGraphView.ts new file mode 100644 index 0000000..27e522c --- /dev/null +++ b/src/modules/debug/DityGraphView.ts @@ -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 + } +} \ No newline at end of file diff --git a/src/modules/debug/dityGraph.ts b/src/modules/debug/dityGraph.ts new file mode 100644 index 0000000..f83f77a --- /dev/null +++ b/src/modules/debug/dityGraph.ts @@ -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()( + ['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) + + } + } +} \ No newline at end of file diff --git a/src/modules/debug/module.ts b/src/modules/debug/module.ts new file mode 100644 index 0000000..ed01a98 --- /dev/null +++ b/src/modules/debug/module.ts @@ -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 diff --git a/src/modules/editor/codeblockHandler/CodeblockProcessor.ts b/src/modules/editor/codeblockHandler/CodeblockProcessor.ts new file mode 100644 index 0000000..dcbb8f3 --- /dev/null +++ b/src/modules/editor/codeblockHandler/CodeblockProcessor.ts @@ -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, + }), + ), + ); + } +} diff --git a/src/codeblockHandler/SqlSealCodeblockHandler.ts b/src/modules/editor/codeblockHandler/SqlSealCodeblockHandler.ts similarity index 54% rename from src/codeblockHandler/SqlSealCodeblockHandler.ts rename to src/modules/editor/codeblockHandler/SqlSealCodeblockHandler.ts index 34eab8a..5dbf1a0 100644 --- a/src/codeblockHandler/SqlSealCodeblockHandler.ts +++ b/src/modules/editor/codeblockHandler/SqlSealCodeblockHandler.ts @@ -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()( + ['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 ) diff --git a/src/codeblockHandler/inline/InlineCodeHandler.ts b/src/modules/editor/codeblockHandler/inline/InlineCodeHandler.ts similarity index 61% rename from src/codeblockHandler/inline/InlineCodeHandler.ts rename to src/modules/editor/codeblockHandler/inline/InlineCodeHandler.ts index d33d245..7331f73 100644 --- a/src/codeblockHandler/inline/InlineCodeHandler.ts +++ b/src/modules/editor/codeblockHandler/inline/InlineCodeHandler.ts @@ -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()( + ['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 ); diff --git a/src/codeblockHandler/inline/InlineProcessor.ts b/src/modules/editor/codeblockHandler/inline/InlineProcessor.ts similarity index 80% rename from src/codeblockHandler/inline/InlineProcessor.ts rename to src/modules/editor/codeblockHandler/inline/InlineProcessor.ts index 1cef51f..5fab118 100644 --- a/src/codeblockHandler/inline/InlineProcessor.ts +++ b/src/modules/editor/codeblockHandler/inline/InlineProcessor.ts @@ -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]] ?? '' diff --git a/src/modules/editor/init.ts b/src/modules/editor/init.ts new file mode 100644 index 0000000..24d4c1b --- /dev/null +++ b/src/modules/editor/init.ts @@ -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()([ + '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 + } + } +} \ No newline at end of file diff --git a/src/modules/editor/module.ts b/src/modules/editor/module.ts new file mode 100644 index 0000000..981e8ab --- /dev/null +++ b/src/modules/editor/module.ts @@ -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 diff --git a/src/grammar/parser.test.ts b/src/modules/editor/parser.test.ts similarity index 100% rename from src/grammar/parser.test.ts rename to src/modules/editor/parser.test.ts diff --git a/src/grammar/parser.ts b/src/modules/editor/parser.ts similarity index 99% rename from src/grammar/parser.ts rename to src/modules/editor/parser.ts index 76640ab..9d5f08a 100644 --- a/src/grammar/parser.ts +++ b/src/modules/editor/parser.ts @@ -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, diff --git a/src/renderer/GridRenderer.ts b/src/modules/editor/renderer/GridRenderer.ts similarity index 91% rename from src/renderer/GridRenderer.ts rename to src/modules/editor/renderer/GridRenderer.ts index 77e8aca..30aa333 100644 --- a/src/renderer/GridRenderer.ts +++ b/src/modules/editor/renderer/GridRenderer.ts @@ -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[], @@ -35,7 +35,8 @@ export class GridRendererCommunicator { constructor( private el: HTMLElement, private config: Partial, - 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, 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) diff --git a/src/renderer/ListRenderer.ts b/src/modules/editor/renderer/ListRenderer.ts similarity index 92% rename from src/renderer/ListRenderer.ts rename to src/modules/editor/renderer/ListRenderer.ts index 4c72949..c41dc88 100644 --- a/src/renderer/ListRenderer.ts +++ b/src/modules/editor/renderer/ListRenderer.ts @@ -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[] diff --git a/src/renderer/MarkdownRenderer.ts b/src/modules/editor/renderer/MarkdownRenderer.ts similarity index 85% rename from src/renderer/MarkdownRenderer.ts rename to src/modules/editor/renderer/MarkdownRenderer.ts index 725a9c1..c735436 100644 --- a/src/renderer/MarkdownRenderer.ts +++ b/src/modules/editor/renderer/MarkdownRenderer.ts @@ -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[]) => { return data.map(d => columns.map(c => String(d[c]))) diff --git a/src/renderer/TableRenderer.ts b/src/modules/editor/renderer/TableRenderer.ts similarity index 94% rename from src/renderer/TableRenderer.ts rename to src/modules/editor/renderer/TableRenderer.ts index 05eb9c4..ee053e5 100644 --- a/src/renderer/TableRenderer.ts +++ b/src/modules/editor/renderer/TableRenderer.ts @@ -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[] diff --git a/src/renderer/TemplateRenderer.ts b/src/modules/editor/renderer/TemplateRenderer.ts similarity index 90% rename from src/renderer/TemplateRenderer.ts rename to src/modules/editor/renderer/TemplateRenderer.ts index 157b1ef..c3927a4 100644 --- a/src/renderer/TemplateRenderer.ts +++ b/src/modules/editor/renderer/TemplateRenderer.ts @@ -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 diff --git a/src/renderer/rendererRegistry.ts b/src/modules/editor/renderer/rendererRegistry.ts similarity index 95% rename from src/renderer/rendererRegistry.ts rename to src/modules/editor/renderer/rendererRegistry.ts index f853c8b..5691054 100644 --- a/src/renderer/rendererRegistry.ts +++ b/src/modules/editor/renderer/rendererRegistry.ts @@ -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[], diff --git a/src/sql/sqlTransformer.ts b/src/modules/editor/sql/sqlTransformer.ts similarity index 100% rename from src/sql/sqlTransformer.ts rename to src/modules/editor/sql/sqlTransformer.ts diff --git a/src/sql/transformer.test.ts b/src/modules/editor/sql/transformer.test.ts similarity index 100% rename from src/sql/transformer.test.ts rename to src/modules/editor/sql/transformer.test.ts diff --git a/src/modules/main/init.ts b/src/modules/main/init.ts new file mode 100644 index 0000000..65115c0 --- /dev/null +++ b/src/modules/main/init.ts @@ -0,0 +1,36 @@ +import { makeInjector } from "@hypersphere/dity"; +import { MainModule } from "./module"; + +type InitFn = () => void + + +@(makeInjector()([ + '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() + } + } +} \ No newline at end of file diff --git a/src/modules/main/module.ts b/src/modules/main/module.ts new file mode 100644 index 0000000..ac21979 --- /dev/null +++ b/src/modules/main/module.ts @@ -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 diff --git a/src/modules/settings/SQLSealSettingsTab.ts b/src/modules/settings/SQLSealSettingsTab.ts new file mode 100644 index 0000000..6cfee73 --- /dev/null +++ b/src/modules/settings/SQLSealSettingsTab.ts @@ -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()(['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) + } +} \ No newline at end of file diff --git a/src/modules/settings/Settings.ts b/src/modules/settings/Settings.ts new file mode 100644 index 0000000..72061a5 --- /dev/null +++ b/src/modules/settings/Settings.ts @@ -0,0 +1,25 @@ +import { args, BusBuilder, CallbackType } from "@hypersphere/omnibus"; +import { SQLSealSettings } from "./SQLSealSettingsTab"; + +export class Settings { + bus = BusBuilder + .init() + .register('change', args()) + .build() + + constructor(private settings: SQLSealSettings) { + } + + get(key: K): SQLSealSettings[K] { + return this.settings[key] + } + + set(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) + } +} \ No newline at end of file diff --git a/src/modules/settings/init.ts b/src/modules/settings/init.ts new file mode 100644 index 0000000..d961177 --- /dev/null +++ b/src/modules/settings/init.ts @@ -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()(["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()); + }); + }; + } +} diff --git a/src/modal/deleteConfirmationModal.ts b/src/modules/settings/modal/deleteConfirmationModal.ts similarity index 100% rename from src/modal/deleteConfirmationModal.ts rename to src/modules/settings/modal/deleteConfirmationModal.ts diff --git a/src/modal/renameColumnModal.ts b/src/modules/settings/modal/renameColumnModal.ts similarity index 100% rename from src/modal/renameColumnModal.ts rename to src/modules/settings/modal/renameColumnModal.ts diff --git a/src/modal/showCodeSample.ts b/src/modules/settings/modal/showCodeSample.ts similarity index 95% rename from src/modal/showCodeSample.ts rename to src/modules/settings/modal/showCodeSample.ts index c92bc3d..d51e1c5 100644 --- a/src/modal/showCodeSample.ts +++ b/src/modules/settings/modal/showCodeSample.ts @@ -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) { diff --git a/src/modules/settings/module.ts b/src/modules/settings/module.ts new file mode 100644 index 0000000..3980c52 --- /dev/null +++ b/src/modules/settings/module.ts @@ -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 \ No newline at end of file diff --git a/src/modules/settings/settingsFactory.ts b/src/modules/settings/settingsFactory.ts new file mode 100644 index 0000000..2d2872b --- /dev/null +++ b/src/modules/settings/settingsFactory.ts @@ -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()(['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 + } +} \ No newline at end of file diff --git a/src/modules/settings/settingsTabSection/SettingsCSVControls.ts b/src/modules/settings/settingsTabSection/SettingsCSVControls.ts new file mode 100644 index 0000000..96dbe83 --- /dev/null +++ b/src/modules/settings/settingsTabSection/SettingsCSVControls.ts @@ -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() + }), + ); + } +} diff --git a/src/modules/settings/settingsTabSection/SettingsControls.ts b/src/modules/settings/settingsTabSection/SettingsControls.ts new file mode 100644 index 0000000..f0a387e --- /dev/null +++ b/src/modules/settings/settingsTabSection/SettingsControls.ts @@ -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 +} diff --git a/src/modules/settings/settingsTabSection/SettingsJsonControls.ts b/src/modules/settings/settingsTabSection/SettingsJsonControls.ts new file mode 100644 index 0000000..b3965aa --- /dev/null +++ b/src/modules/settings/settingsTabSection/SettingsJsonControls.ts @@ -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(); + } + }), + ); + } +} diff --git a/src/modules/settings/utils/viewInspector.ts b/src/modules/settings/utils/viewInspector.ts new file mode 100644 index 0000000..13c58d1 --- /dev/null +++ b/src/modules/settings/utils/viewInspector.ts @@ -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 + } +}; diff --git a/src/modules/settings/view/CSVView.ts b/src/modules/settings/view/CSVView.ts new file mode 100644 index 0000000..fc657e5 --- /dev/null +++ b/src/modules/settings/view/CSVView.ts @@ -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 { + super.onload(); + this.contentEl.addClass("csv-viewer-container"); + } + + async onOpen() { + this.renderCSV(); + } + + async onClose() { + // Cleanup if needed + } + + async setViewData(data: string, clear: boolean): Promise { + this.content = data; + await this.renderCSV(); + } + + getViewData(): string { + return this.content; + } + + clear(): void { + this.content = ""; + this.table.empty(); + } + + private result: any; + updateRow(newRowData: Record) { + 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) => { + 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, 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; + 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) { + 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); + } +} diff --git a/src/view/JsonView.ts b/src/modules/settings/view/JsonView.ts similarity index 100% rename from src/view/JsonView.ts rename to src/modules/settings/view/JsonView.ts diff --git a/src/vaultSync/SealFileSync.ts b/src/modules/sync/fileSyncController/FileSync.ts similarity index 97% rename from src/vaultSync/SealFileSync.ts rename to src/modules/sync/fileSyncController/FileSync.ts index 18219ce..116aae6 100644 --- a/src/vaultSync/SealFileSync.ts +++ b/src/modules/sync/fileSyncController/FileSync.ts @@ -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 = [] diff --git a/src/modules/sync/fileSyncController/fileSyncFactory.ts b/src/modules/sync/fileSyncController/fileSyncFactory.ts new file mode 100644 index 0000000..0ba5edf --- /dev/null +++ b/src/modules/sync/fileSyncController/fileSyncFactory.ts @@ -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()(["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; + }; + } +} diff --git a/src/modules/sync/module.ts b/src/modules/sync/module.ts new file mode 100644 index 0000000..cf6dd95 --- /dev/null +++ b/src/modules/sync/module.ts @@ -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 \ No newline at end of file diff --git a/src/datamodel/repository/abstractRepository.ts b/src/modules/sync/repository/abstractRepository.ts similarity index 100% rename from src/datamodel/repository/abstractRepository.ts rename to src/modules/sync/repository/abstractRepository.ts diff --git a/src/datamodel/repository/configuration.ts b/src/modules/sync/repository/configuration.ts similarity index 87% rename from src/datamodel/repository/configuration.ts rename to src/modules/sync/repository/configuration.ts index f49a0c3..5c132b3 100644 --- a/src/datamodel/repository/configuration.ts +++ b/src/modules/sync/repository/configuration.ts @@ -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 } diff --git a/src/datamodel/repository/tableAliases.ts b/src/modules/sync/repository/tableAliases.ts similarity index 84% rename from src/datamodel/repository/tableAliases.ts rename to src/modules/sync/repository/tableAliases.ts index 6f22266..47ec107 100644 --- a/src/datamodel/repository/tableAliases.ts +++ b/src/modules/sync/repository/tableAliases.ts @@ -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 [] } diff --git a/src/datamodel/repository/tableDefinitions.ts b/src/modules/sync/repository/tableDefinitions.ts similarity index 91% rename from src/datamodel/repository/tableDefinitions.ts rename to src/modules/sync/repository/tableDefinitions.ts index 7ba23aa..195dd60 100644 --- a/src/datamodel/repository/tableDefinitions.ts +++ b/src/modules/sync/repository/tableDefinitions.ts @@ -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') diff --git a/src/modules/sync/sync/init.ts b/src/modules/sync/sync/init.ts new file mode 100644 index 0000000..6fa7894 --- /dev/null +++ b/src/modules/sync/sync/init.ts @@ -0,0 +1,17 @@ +import { makeInjector } from "@hypersphere/dity"; +import { SyncModule } from "../module"; +import { App } from "obsidian"; +import { SealFileSync } from "../fileSyncController/FileSync"; + +@(makeInjector()([ + 'app', 'fileSync' +])) +export class SyncInit { + make(app: App, fileSync: () => Promise) { + return () => { + app.workspace.onLayoutReady(async () => { + await fileSync() + }) + } + } +} \ No newline at end of file diff --git a/src/datamodel/sync.ts b/src/modules/sync/sync/sync.ts similarity index 91% rename from src/datamodel/sync.ts rename to src/modules/sync/sync/sync.ts index d7422ee..cf7a36d 100644 --- a/src/datamodel/sync.ts +++ b/src/modules/sync/sync/sync.ts @@ -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 diff --git a/src/modules/sync/sync/syncFactory.ts b/src/modules/sync/sync/syncFactory.ts new file mode 100644 index 0000000..aaedd97 --- /dev/null +++ b/src/modules/sync/sync/syncFactory.ts @@ -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()([ + '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 + } +} \ No newline at end of file diff --git a/src/vaultSync/tables/abstractFileSyncTable.ts b/src/modules/sync/sync/tables/abstractFileSyncTable.ts similarity index 90% rename from src/vaultSync/tables/abstractFileSyncTable.ts rename to src/modules/sync/sync/tables/abstractFileSyncTable.ts index 5b0a976..dc77bde 100644 --- a/src/vaultSync/tables/abstractFileSyncTable.ts +++ b/src/modules/sync/sync/tables/abstractFileSyncTable.ts @@ -1,5 +1,5 @@ import { App, TFile } from "obsidian"; -import { SqlSealDatabase } from "../../database/database"; +import { SqlSealDatabase } from "../../../database/database"; export abstract class AFileSyncTable { constructor( diff --git a/src/vaultSync/tables/filesTable.ts b/src/modules/sync/sync/tables/filesTable.ts similarity index 89% rename from src/vaultSync/tables/filesTable.ts rename to src/modules/sync/sync/tables/filesTable.ts index 9e99a4d..a28f549 100644 --- a/src/vaultSync/tables/filesTable.ts +++ b/src/modules/sync/sync/tables/filesTable.ts @@ -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) { - 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 { 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'] diff --git a/src/vaultSync/tables/linksTable.ts b/src/modules/sync/sync/tables/linksTable.ts similarity index 100% rename from src/vaultSync/tables/linksTable.ts rename to src/modules/sync/sync/tables/linksTable.ts diff --git a/src/vaultSync/tables/tagsTable.ts b/src/modules/sync/sync/tables/tagsTable.ts similarity index 100% rename from src/vaultSync/tables/tagsTable.ts rename to src/modules/sync/sync/tables/tagsTable.ts diff --git a/src/vaultSync/tables/tasksTable.ts b/src/modules/sync/sync/tables/tasksTable.ts similarity index 100% rename from src/vaultSync/tables/tasksTable.ts rename to src/modules/sync/sync/tables/tasksTable.ts diff --git a/src/datamodel/syncStrategy/CsvFileSyncStrategy.ts b/src/modules/sync/syncStrategy/CsvFileSyncStrategy.ts similarity index 93% rename from src/datamodel/syncStrategy/CsvFileSyncStrategy.ts rename to src/modules/sync/syncStrategy/CsvFileSyncStrategy.ts index 186e41b..b45b886 100644 --- a/src/datamodel/syncStrategy/CsvFileSyncStrategy.ts +++ b/src/modules/sync/syncStrategy/CsvFileSyncStrategy.ts @@ -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 = '' diff --git a/src/datamodel/syncStrategy/JSONFileSyncStrategy.ts b/src/modules/sync/syncStrategy/JSONFileSyncStrategy.ts similarity index 97% rename from src/datamodel/syncStrategy/JSONFileSyncStrategy.ts rename to src/modules/sync/syncStrategy/JSONFileSyncStrategy.ts index b11fc1e..37cd436 100644 --- a/src/datamodel/syncStrategy/JSONFileSyncStrategy.ts +++ b/src/modules/sync/syncStrategy/JSONFileSyncStrategy.ts @@ -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 = '' diff --git a/src/datamodel/syncStrategy/MarkdownTableSyncStrategy.ts b/src/modules/sync/syncStrategy/MarkdownTableSyncStrategy.ts similarity index 98% rename from src/datamodel/syncStrategy/MarkdownTableSyncStrategy.ts rename to src/modules/sync/syncStrategy/MarkdownTableSyncStrategy.ts index 6316d5c..1692a5c 100644 --- a/src/datamodel/syncStrategy/MarkdownTableSyncStrategy.ts +++ b/src/modules/sync/syncStrategy/MarkdownTableSyncStrategy.ts @@ -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 = '' diff --git a/src/datamodel/syncStrategy/SyncStrategyFactory.ts b/src/modules/sync/syncStrategy/SyncStrategyFactory.ts similarity index 95% rename from src/datamodel/syncStrategy/SyncStrategyFactory.ts rename to src/modules/sync/syncStrategy/SyncStrategyFactory.ts index c8ca925..f748ce0 100644 --- a/src/datamodel/syncStrategy/SyncStrategyFactory.ts +++ b/src/modules/sync/syncStrategy/SyncStrategyFactory.ts @@ -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) => { diff --git a/src/datamodel/syncStrategy/abstractSyncStrategy.ts b/src/modules/sync/syncStrategy/abstractSyncStrategy.ts similarity index 91% rename from src/datamodel/syncStrategy/abstractSyncStrategy.ts rename to src/modules/sync/syncStrategy/abstractSyncStrategy.ts index d54172e..444cb5f 100644 --- a/src/datamodel/syncStrategy/abstractSyncStrategy.ts +++ b/src/modules/sync/syncStrategy/abstractSyncStrategy.ts @@ -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) { diff --git a/src/datamodel/syncStrategy/types.ts b/src/modules/sync/syncStrategy/types.ts similarity index 100% rename from src/datamodel/syncStrategy/types.ts rename to src/modules/sync/syncStrategy/types.ts diff --git a/src/cellParser/CellFunction.ts b/src/modules/syntaxHighlight/cellParser/CellFunction.ts similarity index 100% rename from src/cellParser/CellFunction.ts rename to src/modules/syntaxHighlight/cellParser/CellFunction.ts diff --git a/src/cellParser/ModernCellParser.ts b/src/modules/syntaxHighlight/cellParser/ModernCellParser.ts similarity index 98% rename from src/cellParser/ModernCellParser.ts rename to src/modules/syntaxHighlight/cellParser/ModernCellParser.ts index 5772a3f..2b8f6a7 100644 --- a/src/cellParser/ModernCellParser.ts +++ b/src/modules/syntaxHighlight/cellParser/ModernCellParser.ts @@ -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; diff --git a/src/modules/syntaxHighlight/cellParser/factory.ts b/src/modules/syntaxHighlight/cellParser/factory.ts new file mode 100644 index 0000000..004113c --- /dev/null +++ b/src/modules/syntaxHighlight/cellParser/factory.ts @@ -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()([ + '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 + } +} \ No newline at end of file diff --git a/src/cellParser/parseResults.ts b/src/modules/syntaxHighlight/cellParser/parseResults.ts similarity index 100% rename from src/cellParser/parseResults.ts rename to src/modules/syntaxHighlight/cellParser/parseResults.ts diff --git a/src/cellParser/parser/checkbox.ts b/src/modules/syntaxHighlight/cellParser/parser/checkbox.ts similarity index 100% rename from src/cellParser/parser/checkbox.ts rename to src/modules/syntaxHighlight/cellParser/parser/checkbox.ts diff --git a/src/cellParser/parser/image.ts b/src/modules/syntaxHighlight/cellParser/parser/image.ts similarity index 96% rename from src/cellParser/parser/image.ts rename to src/modules/syntaxHighlight/cellParser/parser/image.ts index 5bbf0b7..a96456d 100644 --- a/src/cellParser/parser/image.ts +++ b/src/modules/syntaxHighlight/cellParser/parser/image.ts @@ -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"; diff --git a/src/cellParser/parser/link.ts b/src/modules/syntaxHighlight/cellParser/parser/link.ts similarity index 97% rename from src/cellParser/parser/link.ts rename to src/modules/syntaxHighlight/cellParser/parser/link.ts index 2e5745e..a843a8a 100644 --- a/src/cellParser/parser/link.ts +++ b/src/modules/syntaxHighlight/cellParser/parser/link.ts @@ -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"; diff --git a/src/modules/syntaxHighlight/editorExtension/inlineCodeBlock.ts b/src/modules/syntaxHighlight/editorExtension/inlineCodeBlock.ts new file mode 100644 index 0000000..aa8c387 --- /dev/null +++ b/src/modules/syntaxHighlight/editorExtension/inlineCodeBlock.ts @@ -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(); + 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; + } +} diff --git a/src/editorExtension/syntaxHighlight.ts b/src/modules/syntaxHighlight/editorExtension/syntaxHighlight.ts similarity index 96% rename from src/editorExtension/syntaxHighlight.ts rename to src/modules/syntaxHighlight/editorExtension/syntaxHighlight.ts index 8b337dd..b6c23f4 100644 --- a/src/editorExtension/syntaxHighlight.ts +++ b/src/modules/syntaxHighlight/editorExtension/syntaxHighlight.ts @@ -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' }), diff --git a/src/editorExtension/widgets/FilePathWidget.ts b/src/modules/syntaxHighlight/editorExtension/widgets/FilePathWidget.ts similarity index 90% rename from src/editorExtension/widgets/FilePathWidget.ts rename to src/modules/syntaxHighlight/editorExtension/widgets/FilePathWidget.ts index 97f3add..6d4a61d 100644 --- a/src/editorExtension/widgets/FilePathWidget.ts +++ b/src/modules/syntaxHighlight/editorExtension/widgets/FilePathWidget.ts @@ -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) { diff --git a/src/grammar/highlighter/handlebarsHighlighter.ts b/src/modules/syntaxHighlight/grammar/highlighter/handlebarsHighlighter.ts similarity index 100% rename from src/grammar/highlighter/handlebarsHighlighter.ts rename to src/modules/syntaxHighlight/grammar/highlighter/handlebarsHighlighter.ts diff --git a/src/grammar/highlighter/jsHighlighter.ts b/src/modules/syntaxHighlight/grammar/highlighter/jsHighlighter.ts similarity index 100% rename from src/grammar/highlighter/jsHighlighter.ts rename to src/modules/syntaxHighlight/grammar/highlighter/jsHighlighter.ts diff --git a/src/grammar/highlighterOperation.ts b/src/modules/syntaxHighlight/grammar/highlighterOperation.ts similarity index 100% rename from src/grammar/highlighterOperation.ts rename to src/modules/syntaxHighlight/grammar/highlighterOperation.ts diff --git a/src/modules/syntaxHighlight/init.ts b/src/modules/syntaxHighlight/init.ts new file mode 100644 index 0000000..a85ed11 --- /dev/null +++ b/src/modules/syntaxHighlight/init.ts @@ -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()([ + '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 } + ) + ]); + } + } +} \ No newline at end of file diff --git a/src/modules/syntaxHighlight/module.ts b/src/modules/syntaxHighlight/module.ts new file mode 100644 index 0000000..6fad2f5 --- /dev/null +++ b/src/modules/syntaxHighlight/module.ts @@ -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; diff --git a/src/pluginApi/sqlSealApi.ts b/src/pluginApi/sqlSealApi.ts deleted file mode 100644 index 47347b0..0000000 --- a/src/pluginApi/sqlSealApi.ts +++ /dev/null @@ -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 = [] - 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 = [] - private functions: Array = [] - private flags: Array = [] - - 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(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) - } - } -} diff --git a/src/settings/SQLSealSettingsTab.ts b/src/settings/SQLSealSettingsTab.ts deleted file mode 100644 index ff52170..0000000 --- a/src/settings/SQLSealSettingsTab.ts +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/src/sqlSeal.ts b/src/sqlSeal.ts deleted file mode 100644 index 558885c..0000000 --- a/src/sqlSeal.ts +++ /dev/null @@ -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() - } -} diff --git a/src/styles/canvas.scss b/src/styles/canvas.scss new file mode 100644 index 0000000..0444395 --- /dev/null +++ b/src/styles/canvas.scss @@ -0,0 +1,4 @@ +// Canvas specific styles +.sqlseal-renderer-on-canvas { + +} \ No newline at end of file diff --git a/src/styles/main.scss b/src/styles/main.scss index cde0305..3944e15 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -4,4 +4,6 @@ @use 'listView'; @use 'syntaxHighlight'; @use 'obsidianMinimal'; -@use 'agGrid'; \ No newline at end of file +@use 'agGrid'; +@use 'settings'; +@use 'canvas'; \ No newline at end of file diff --git a/src/styles/settings.scss b/src/styles/settings.scss new file mode 100644 index 0000000..4db602f --- /dev/null +++ b/src/styles/settings.scss @@ -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; +} \ No newline at end of file diff --git a/src/view/CSVView.ts b/src/view/CSVView.ts deleted file mode 100644 index e1576b3..0000000 --- a/src/view/CSVView.ts +++ /dev/null @@ -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 { - super.onload(); - this.contentEl.addClass('csv-viewer-container'); - } - - async onOpen() { - this.renderCSV(); - } - - async onClose() { - // Cleanup if needed - } - - async setViewData(data: string, clear: boolean): Promise { - this.content = data; - await this.renderCSV(); - } - - getViewData(): string { - return this.content; - } - - clear(): void { - this.content = ''; - this.table.empty(); - } - - private result: any; - updateRow(newRowData: Record) { - 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) => { - 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, 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 - 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) { - 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) - } -} \ No newline at end of file diff --git a/src/view/SidebarView.ts b/src/view/SidebarView.ts deleted file mode 100644 index e77f999..0000000 --- a/src/view/SidebarView.ts +++ /dev/null @@ -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 { - 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 { - 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' - }); - }); - }); - } -} \ No newline at end of file diff --git a/styles.css b/styles.css index e6a7e2b..db7a30b 100644 --- a/styles.css +++ b/styles.css @@ -1,2 +1,2 @@ -.sqlseal-table-container{overflow-y:scroll}.sqlseal-table-container table{width:100%;border-collapse:collapse}.sqlseal-grid-wrapper{height:auto;position:relative}.block-language-sqlseal{overflow-y:auto}.sqlseal-error{padding:1em;background:#b80f0f;color:#fff}.sqlseal-notice{padding:1em;background:#1f3f98;color:#fff}.sqlseal-grid-error-message{box-sizing:border-box;width:80%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;background:#b80f0f;font-size:.8em;color:#fff;padding:.5em 2em}.sqlseal-grid-error-message-overlay{content:"";position:absolute;inset:0;background:#fffc;z-index:5}.sqlseal-grid-error-message-overlay.hidden{display:none}.edit-block-button{z-index:1000}.sqlseal-markdown-table{font-family:monospace;white-space:pre-wrap}.sqlseal-markdown-table.no-wrap{display:inline-block;overflow-x:scroll;width:fit-content}.sqlseal-parse-error{background:#b80f0f;color:#fff;padding:.2em;border-radius:2px}.sqlseal-notice-error{color:#f26464}.sqlseal-notice-error:before{content:"";width:16px;height:16px;display:inline-block;margin-right:8px;transform:translateY(3px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ff0000' d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'/%3E%3C/svg%3E")}.sql-seal-csv-editor{display:flex;flex-direction:column;height:100%;gap:var(--size-4-4)}.sql-seal-csv-viewer{flex:1;display:flex;flex-direction:column}.sqlseal-grid-wrapper{height:100%;flex:1;display:flex;flex-direction:column}.ag-theme-quartz{flex:1}.sql-seal-csv-viewer-buttons{display:flex;gap:var(--size-4-4)}.sql-seal-modal-code{width:100%;font-family:monospace;padding:8px}.sqlseal-inline-result{display:inline-block;padding:0 4px;margin:0 2px;background-color:var(--background-secondary);border-radius:4px;font-size:.9em;cursor:pointer}.sqlseal-inline-result .error{color:var(--text-error);font-style:italic}.sqlseal-list-element-single .sqlseal-column-name{display:none}.sqlseal-list-element-single .sqlseal-column-name:after{content:": "}.sqlseal-list.show-column-names .sqlseal-list-element-single .sqlseal-column-name{display:inline}.sqlseal-element-inline-list{display:inline-block}.cm-sqlseal-block-query{--color: #297f30}.cm-sqlseal-block-view{--color: #c834dd}.cm-sqlseal-block-flag{--color: #d08306;font-weight:700;color:var(--color)}.cm-sqlseal-block-table{--color: #4985af}.cm-sqlseal-identifier{color:#2d7b33}.cm-sqlseal-function,.cm-sqlseal-parameter{color:#2c0e9b}.cm-sqlseal-comment{color:#7a7a7a;font-style:italic}.cm-sqlseal-keyword{color:var(--color);font-weight:700}.cm-sqlseal-literal{color:#03f}.cm-sqlseal-error{color:#eb2f2f;font-style:italic}.cm-sqlseal-template-keyword{color:#e3ab53}.theme-dark .cm-sqlseal-template-keyword{color:#dd8e10}.theme-dark .cm-sqlseal-block-error{color:red}.theme-dark .cm-sqlseal-block-query{--color: #5eff6a}.theme-dark .cm-sqlseal-comment{color:#898787}.theme-dark .cm-sqlseal-parameter{color:#95ed22}.theme-dark .cm-sqlseal-literal{color:#ff0}.theme-dark .cm-sqlseal-block-view{--color: #e945ff}.theme-dark .cm-sqlseal-block-flag{--color: #e3ab53}.theme-dark .cm-sqlseal-block-table{--color: #56b5fa}.theme-dark .cm-sqlseal-function{color:#c4b8ef}.theme-dark .cm-sqlseal-identifier{color:#6ff77a}:is(.cm-sqlseal-block-query,.cm-sqlseal-block-view,.cm-sqlseal-block-flag,.cm-sqlseal-block-table):before{background-color:var(--color);width:5px;top:-3px;bottom:-3px;left:0;content:"";display:block;position:absolute}.cm-indent+:is(.cm-sqlseal-block-query,.cm-sqlseal-block-view,.cm-sqlseal-block-flag,.cm-sqlseal-block-table):before{display:none}.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>:has(>.block-language-sqlseal table.dataview){width:var(--container-dataview-table-width);max-width:var(--container-table-max-width)}.ag-checkbox-input{opacity:1!important} +.sqlseal-table-container{overflow-y:scroll}.sqlseal-table-container table{width:100%;border-collapse:collapse}.sqlseal-grid-wrapper{height:auto;position:relative}.block-language-sqlseal{overflow-y:auto}.sqlseal-error{padding:1em;background:#b80f0f;color:#fff}.sqlseal-notice{padding:1em;background:#1f3f98;color:#fff}.sqlseal-grid-error-message{box-sizing:border-box;width:80%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;background:#b80f0f;font-size:.8em;color:#fff;padding:.5em 2em}.sqlseal-grid-error-message-overlay{content:"";position:absolute;inset:0;background:#fffc;z-index:5}.sqlseal-grid-error-message-overlay.hidden{display:none}.edit-block-button{z-index:1000}.sqlseal-markdown-table{font-family:monospace;white-space:pre-wrap}.sqlseal-markdown-table.no-wrap{display:inline-block;overflow-x:scroll;width:fit-content}.sqlseal-parse-error{background:#b80f0f;color:#fff;padding:.2em;border-radius:2px}.sqlseal-notice-error{color:#f26464}.sqlseal-notice-error:before{content:"";width:16px;height:16px;display:inline-block;margin-right:8px;transform:translateY(3px);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ff0000' d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'/%3E%3C/svg%3E")}.sql-seal-csv-editor{display:flex;flex-direction:column;height:100%;gap:var(--size-4-4)}.sql-seal-csv-viewer{flex:1;display:flex;flex-direction:column}.sqlseal-grid-wrapper{height:100%;flex:1;display:flex;flex-direction:column}.ag-theme-quartz{flex:1}.sql-seal-csv-viewer-buttons{display:flex;gap:var(--size-4-4)}.sql-seal-modal-code{width:100%;font-family:monospace;padding:8px}.sqlseal-inline-result{display:inline-block;padding:0 4px;margin:0 2px;background-color:var(--background-secondary);border-radius:4px;font-size:.9em;cursor:pointer}.sqlseal-inline-result .error{color:var(--text-error);font-style:italic}.sqlseal-list-element-single .sqlseal-column-name{display:none}.sqlseal-list-element-single .sqlseal-column-name:after{content:": "}.sqlseal-list.show-column-names .sqlseal-list-element-single .sqlseal-column-name{display:inline}.sqlseal-element-inline-list{display:inline-block}.cm-sqlseal-block-query{--color: #297f30}.cm-sqlseal-block-view{--color: #c834dd}.cm-sqlseal-block-flag{--color: #d08306;font-weight:700;color:var(--color)}.cm-sqlseal-block-table{--color: #4985af}.cm-sqlseal-identifier{color:#2d7b33}.cm-sqlseal-function,.cm-sqlseal-parameter{color:#2c0e9b}.cm-sqlseal-comment{color:#7a7a7a;font-style:italic}.cm-sqlseal-keyword{color:var(--color);font-weight:700}.cm-sqlseal-literal{color:#03f}.cm-sqlseal-error{color:#eb2f2f;font-style:italic}.cm-sqlseal-template-keyword{color:#e3ab53}.theme-dark .cm-sqlseal-template-keyword{color:#dd8e10}.theme-dark .cm-sqlseal-block-error{color:red}.theme-dark .cm-sqlseal-block-query{--color: #5eff6a}.theme-dark .cm-sqlseal-comment{color:#898787}.theme-dark .cm-sqlseal-parameter{color:#95ed22}.theme-dark .cm-sqlseal-literal{color:#ff0}.theme-dark .cm-sqlseal-block-view{--color: #e945ff}.theme-dark .cm-sqlseal-block-flag{--color: #e3ab53}.theme-dark .cm-sqlseal-block-table{--color: #56b5fa}.theme-dark .cm-sqlseal-function{color:#c4b8ef}.theme-dark .cm-sqlseal-identifier{color:#6ff77a}:is(.cm-sqlseal-block-query,.cm-sqlseal-block-view,.cm-sqlseal-block-flag,.cm-sqlseal-block-table):before{background-color:var(--color);width:5px;top:-3px;bottom:-3px;left:0;content:"";display:block;position:absolute}.cm-indent+:is(.cm-sqlseal-block-query,.cm-sqlseal-block-view,.cm-sqlseal-block-flag,.cm-sqlseal-block-table):before{display:none}.markdown-source-view.mod-cm6.is-readable-line-width .cm-contentContainer.cm-contentContainer>.cm-content>:has(>.block-language-sqlseal table.dataview){width:var(--container-dataview-table-width);max-width:var(--container-table-max-width)}.ag-checkbox-input{opacity:1!important}.sqlseal-settings-warn{background:#ffa600;padding:16px;border:2px solid rgb(134,87,0);border-radius:5px;margin:.5em 2em} /*# sourceMappingURL=styles.css.map */ diff --git a/tsconfig.json b/tsconfig.json index 62f3483..a88d262 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,8 @@ "ES6", "ES7" ], - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + // "strict": true }, "include": [ "**/*.ts"] diff --git a/types-package/src/index.ts b/types-package/src/index.ts index ef9a365..34589ca 100644 --- a/types-package/src/index.ts +++ b/types-package/src/index.ts @@ -1,5 +1,5 @@ -import type { SQLSealRegisterApi, SQLSealApi } from '../../src/pluginApi/sqlSealApi' -import { RendererConfig } from '../../src/renderer/rendererRegistry' +import type { SQLSealRegisterApi, SQLSealApi } from '../../src/modules/api/pluginApi/sqlSealApi' +import { RendererConfig } from '../../src/modules/editor/renderer/rendererRegistry' export type {