Compare commits

..

339 Commits

Author SHA1 Message Date
Elio Struyf 4fb32bbf9e Merge pull request #560 from estruyf/dev 2023-04-03 16:48:35 +02:00
Elio Struyf 11103ad52f Updated the changelog 2023-04-03 15:57:03 +02:00
Elio Struyf 398691bace #557 - Fix for dropdown of the tag picker 2023-04-03 12:50:58 +02:00
Elio Struyf 7df731b55f Merge branch 'issue/548-projects' into dev 2023-04-02 21:29:34 +02:00
Elio Struyf 61d8d606a7 #556 #555 - better field type detection 2023-04-02 21:22:39 +02:00
Elio Struyf f65060fc7c #523 - add default step 2023-04-01 19:18:16 +02:00
Elio Struyf 7bd77e436d #535 - fix when no image is selected 2023-04-01 15:46:49 +02:00
Elio Struyf 4b37c4c9a4 Changelog update 2023-03-31 17:01:42 +02:00
Elio Struyf 6f51e6bcd3 Update changelog 2023-03-31 16:43:13 +02:00
Elio Struyf 744e4a8fa5 Merge branch 'issue/548-projects' into dev 2023-03-31 09:16:50 +02:00
Elio Struyf fe2a1158e2 Update chanelog 2023-03-31 09:14:52 +02:00
Elio Struyf ecab542a65 #554 - hide media snippets 2023-03-31 09:07:41 +02:00
Elio Struyf 78f0e7f56a #548 - Project selection support 2023-03-30 21:06:04 +02:00
Elio Struyf 6bb9831542 Implementation of the project selector 2023-03-30 18:28:04 +02:00
Elio Struyf 30df5c6233 #549 - Added the folder name 2023-03-30 11:50:22 +02:00
Elio Struyf 3086ca67ce #552 - Fix for content retrieval in multi-root workspaces 2023-03-29 17:30:04 +02:00
Elio Struyf 8054a964b4 #549 - check changes in submodule 2023-03-29 08:39:41 +02:00
Elio Struyf 5336f3c9c2 #550 - Taxonomy suggestions 2023-03-28 22:41:21 +02:00
Elio Struyf d52f51fec7 #549 - sync submodule changes 2023-03-28 20:19:32 +02:00
Elio Struyf 29479a8c05 Fix for extending array settings + card Image check 2023-03-25 11:26:45 +01:00
Elio Struyf 5d1ed08658 #513 - Add support for card image extensibility 2023-03-23 14:49:29 +01:00
Elio Struyf e23960d9ee #547 - check for parent field 2023-03-23 12:20:52 +01:00
Elio Struyf 623878049a #547 - fix default value 2023-03-22 10:43:52 +01:00
Elio Struyf fafe64d116 #538 - update stringify 2023-03-21 09:52:30 +01:00
Elio Struyf 9c5b8aa445 Updated name 2023-03-21 09:39:46 +01:00
Elio Struyf 5011c1c95d #538 - encodeEmoji property support 2023-03-20 13:19:32 +01:00
Elio Struyf 898f59ed6a #543 - fix json schema 2023-03-20 10:22:47 +01:00
Elio Struyf 781db743dc #528 - fix for astro files 2023-03-19 19:56:06 +01:00
Elio Struyf a66bf12a24 #541 - rewrite the ai logic 2023-03-19 16:29:10 +01:00
Elio Struyf 5f1c835842 #541 - silent account retrieval 2023-03-19 12:13:29 +01:00
Elio Struyf a274a34194 Update changelog 2023-03-18 21:30:11 +01:00
Elio Struyf a958ee6fdc update changelog 2023-03-18 21:28:57 +01:00
Elio Struyf 3a5d57e03a #541 - Title suggestion AI support 2023-03-18 21:25:40 +01:00
Elio Struyf d29d0cfc44 #512 - added frontmatter config folder support 2023-03-17 16:00:20 +01:00
Elio Struyf f45affbd2e Merge branches 'dev' and 'dev' of github.com:estruyf/vscode-front-matter into dev 2023-03-17 13:20:31 +01:00
Elio Struyf 6058a02b70 #512 - added jsonc file association for frontmatter.json 2023-03-17 13:12:17 +01:00
Elio Struyf e84bf8336f #529 - fix yaml parsing in Windows 2023-03-17 12:26:31 +01:00
Elio Struyf ea19c61b4e update settings link 2023-03-17 11:14:34 +01:00
Elio Struyf 7a901e6eba update changelog 2023-03-16 15:19:19 +01:00
Elio Struyf 48e1cd882a Added an unknown field for uniforms 2023-03-16 15:18:46 +01:00
Elio Struyf 42c44ca3a5 #539 - fix file prefix 2023-03-16 09:51:57 +01:00
Elio Struyf b48008eb41 #537 - added support for ./ in public folder 2023-03-16 09:30:05 +01:00
Elio Struyf bc3407fd7b #534 - move to database folder 2023-03-15 13:13:18 +01:00
Elio Struyf 143f8d799a Hover effect on images 2023-03-15 13:12:08 +01:00
Elio Struyf 73e1f21117 #535 - retain scroll position 2023-03-15 10:11:47 +01:00
Elio Struyf 92d507364e #536 - Update relative path 2023-03-15 09:02:14 +01:00
Elio Struyf 623c340f49 Removed logging 2023-03-15 08:51:15 +01:00
Elio Struyf e661753d51 Update activation message 2023-03-15 08:51:04 +01:00
Elio Struyf 265db6f5dd #537 - Allow root path 2023-03-14 22:47:10 +01:00
Elio Struyf 1e3b4c8eaf #536 - Set the script root folder 2023-03-14 22:32:16 +01:00
Elio Struyf f583ab2f97 #363 - Update labelfield 2023-03-14 17:21:19 +01:00
Elio Struyf 35d87226f6 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2023-03-14 08:38:59 +01:00
Elio Struyf fccffa4ea2 #363 - Multiline support 2023-03-14 08:38:37 +01:00
Elio Struyf 22dbf4681f Removed app insights + custom metrics 2023-03-13 12:45:05 +01:00
Elio Struyf 56deb432fd Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2023-03-13 08:56:43 +01:00
Elio Struyf b9e46d551e #531 - fix list items 2023-03-13 08:56:37 +01:00
Elio Struyf 605ebc6a37 Set chatbot to experimental for better styling 2023-03-11 11:32:37 +01:00
Elio Struyf c159d8037c #530 - Loading message added 2023-03-10 16:37:11 +01:00
Elio Struyf b3a1628b77 #530 - Chatbot API update 2023-03-10 11:54:11 +01:00
Elio Struyf 5a56577695 Merge branch 'poc/ai' into dev 2023-03-09 21:06:25 +01:00
Elio Struyf 36a903c7b6 #530 - Front Matter AI 2023-03-09 21:06:13 +01:00
Elio Struyf ecacba53a7 Added chatbot integration 2023-03-09 18:09:18 +01:00
Elio Struyf 94819cef62 #521 - fix snippets placeholder 2023-03-07 15:32:08 +01:00
Elio Struyf 14515e4f59 #526 - Fix content menu 2023-03-07 15:14:15 +01:00
Elio Struyf 53dd1303dc #518 - revert and update yaml parser + config 2023-03-06 10:46:23 +01:00
Elio Struyf 0ee4ed9e3c Merge branches 'dev' and 'dev' of github.com:estruyf/vscode-front-matter into dev 2023-03-06 10:19:03 +01:00
Elio Struyf 9a2b0cb005 Update changelog date 2023-03-06 10:15:42 +01:00
Elio Struyf 9f11b94c60 #520 - Add url protocol 2023-03-04 12:11:42 +01:00
Elio Struyf 34f5e9c41b #523 - number options 2023-03-04 11:59:39 +01:00
Elio Struyf ced176adad #524 - Remove global settings 2023-03-04 11:13:41 +01:00
Elio Struyf f2c8c0a8d6 Added default servers for frameworks 2023-03-04 10:48:36 +01:00
Elio Struyf 6f45277b49 #522 - Add Astro support 2023-03-04 10:45:18 +01:00
Elio Struyf 541e02e72c Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2023-03-02 21:23:02 +01:00
Elio Struyf cae62232c6 Fix panel alignment 2023-03-02 21:22:41 +01:00
Elio Struyf 8a2ee4c82c Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2023-03-02 11:51:18 +01:00
Elio Struyf 4ffcd76aac Remove log 2023-03-02 11:51:05 +01:00
Elio Struyf 3b0ea3132c #518: Fix YAML parser 2023-03-02 11:50:24 +01:00
Elio Struyf cf29121bc0 Fix isDevelopment check 2023-02-28 19:33:57 +01:00
Elio Struyf cbeda07d25 #513 - Added devtools command 2023-02-28 14:45:18 +01:00
Elio Struyf 8bc63a48a0 #513 - devmode for ext 2023-02-28 12:49:03 +01:00
Elio Struyf 026bcd0a84 #513 - support for custom field + panel view 2023-02-27 17:37:16 +01:00
Elio Struyf cf6133b143 8.4.0 2023-02-26 15:38:50 +01:00
Elio Struyf 872487db4a Added the UI scripts to experimental features 2023-02-26 15:38:38 +01:00
Elio Struyf 386bf0218d Added external script loading 2023-02-26 15:18:57 +01:00
Elio Struyf ecca570e30 Extensibility script solution 2023-02-24 19:11:31 +01:00
Elio Struyf 7e398d206a POC of rendering custom HTML 2023-02-24 18:21:12 +01:00
Elio Struyf 75af200108 Update submenu 2023-02-19 14:56:31 +01:00
Elio Struyf d364bde59d Merge pull request #507 from estruyf/dev 2023-02-14 16:37:29 +01:00
Elio Struyf 8ae105fd6e Fixes in preview paths 2023-02-14 16:12:39 +01:00
Elio Struyf 3a8854b060 update changelog 2023-02-13 19:58:07 +01:00
Elio Struyf af393bbfc5 #425 - fix for global file path 2023-02-12 13:23:20 +01:00
Elio Struyf 76707b9eee Update changelog 2023-02-11 15:55:00 +01:00
Elio Struyf 8042a25fbb Update changelog 2023-02-11 13:18:40 +01:00
Elio Struyf 1a3520833b #506 - added default filename 2023-02-11 12:08:58 +01:00
Elio Struyf 1b1eac0a43 Merge branch 'furkanb-issue/#504' into dev 2023-02-11 12:08:04 +01:00
furkanb 6b19e69806 sanitize defaultFileName 2023-02-11 01:55:07 +03:00
furkanb 04960b7fdb issue/#504 2023-02-11 01:47:03 +03:00
Elio Struyf 3ca979bea9 updated changelog 2023-02-10 14:58:12 +01:00
Elio Struyf 12ecee8c49 Fix address bar of preview 2023-02-10 14:53:23 +01:00
Elio Struyf 680ea99f05 #425 - Added content type check 2023-02-10 14:44:25 +01:00
Elio Struyf 39cee294af #425 - Preview questions when folder is not found 2023-02-10 14:24:14 +01:00
Elio Struyf 558845a8d2 #425 - added new placeholders for previews 2023-02-10 13:24:59 +01:00
Elio Struyf 327559fdd7 #425 - support added for placeholders in the page folders path 2023-02-09 22:30:36 +01:00
Elio Struyf 2efbfa7820 Add button border to align with VSCode 2023-02-09 21:27:30 +01:00
Elio Struyf cdeaa87321 #480 - Updating add missing fields label 2023-02-09 21:14:40 +01:00
Elio Struyf 483cfcd761 #505 - Small theming fixes 2023-02-09 17:35:16 +01:00
Elio Struyf 74bc8d78a0 #505 - Enhancements to theming of the data fields 2023-02-09 17:24:42 +01:00
Elio Struyf bc88df98ee #505 - Data view theming 2023-02-09 13:32:34 +01:00
Elio Struyf d48e63ba32 Restructuring components 2023-02-09 11:28:22 +01:00
Elio Struyf 20ee0e1ca5 #505 - Step + welcome view colors 2023-02-08 19:27:48 +01:00
Elio Struyf 716f7d1f0c #505 - Preview theming 2023-02-07 20:32:23 +01:00
Elio Struyf 5119f631f3 Enhancement: VS Code theme support for the dashboards #505 2023-02-07 19:59:46 +01:00
Elio Struyf 4eea855d0f #505 - Header theming 2023-02-07 17:47:07 +01:00
Elio Struyf 5354619283 #505 - Theming for snippets view 2023-02-07 13:49:05 +01:00
Elio Struyf dd939f4b7a #505 - Media folder items styling 2023-02-07 10:48:19 +01:00
Elio Struyf 2635cc9b6d #505 - Media dashboard theming WIP 2023-02-07 10:32:01 +01:00
Elio Struyf c843e74077 #505 - Align to theme colors WIP 2023-02-06 18:26:11 +01:00
Elio Struyf baed139cc6 Implement new scoped groups from tailwind 2023-02-05 14:39:28 +01:00
Elio Struyf 38839470ac #502 - Keyboard bindings added 2023-02-05 14:31:25 +01:00
Elio Struyf 8a4b24396c #503: Allow making changes to preview URL + tailwind v3 update 2023-02-05 14:13:43 +01:00
Elio Struyf 4a50cf70a0 #473 - Allow setting the SEO title name 2023-02-04 10:08:25 +01:00
Elio Struyf db38b9da68 #488 - Create the .frontmatter folder on initialization, not before 2023-02-02 21:06:20 +01:00
Elio Struyf 2b7f124582 #496 - Move to file storage for larger states 2023-02-02 18:46:50 +01:00
Elio Struyf 089f0a643d Merge pull request #500 from pongstr/dev 2023-02-02 12:11:15 +01:00
Pongstr 9c9102f6f5 chore(devx-improvements): Deps upgrades + Prettier 2023-02-02 11:13:55 +02:00
Elio Struyf c95d79a218 Merge pull request #499 from pongstr/devx-improvements 2023-02-01 15:17:16 +01:00
Pongstr c0b1e4383f init: sync with dev 2023-02-01 16:13:57 +02:00
Pongstr ddf2873794 init 2023-02-01 11:58:31 +02:00
Pongstr faf629cca5 init 2023-02-01 11:40:12 +02:00
Elio Struyf bf4b66564c #497 - Support for movie media added 2023-01-30 10:19:05 +01:00
Elio Struyf 5c62255605 #494 - Support added for remote images as previews 2023-01-30 10:11:06 +01:00
Elio Struyf 4f91aeb80b Update wf name 2023-01-24 10:52:00 +01:00
Elio Struyf e19b4d7d6c #493 - fix issue with custom placeholders 2023-01-24 10:44:16 +01:00
Elio Struyf 7f02fe34ba Update labels flow 2023-01-09 13:41:21 +01:00
Elio Struyf 5dec859849 Merge branch 'main' into dev 2023-01-07 20:18:57 +01:00
Elio Struyf 5e1558ecfa Update labeling 2023-01-07 20:18:45 +01:00
Elio Struyf 8a099de859 Updated vsce dependency 2022-12-24 09:47:13 +01:00
Elio Struyf ddef00726b update badges 2022-12-24 09:42:40 +01:00
Elio Struyf 70316c4c28 #474 - Allow to define the file prefix on content types 2022-12-23 15:00:49 +01:00
Elio Struyf 50f2e7ea72 Update readme 2022-12-23 12:12:27 +01:00
Elio Struyf 663d346e2e Temporarily remove badges 2022-12-23 12:07:26 +01:00
Elio Struyf d42561fbf5 #469 - Fix for using the root folder as content folder 2022-12-23 12:02:49 +01:00
Elio Struyf 0c5224b5f9 Update changelog 2022-12-23 10:27:39 +01:00
Elio Struyf cfa805add9 #407 - support for external config files 2022-12-23 10:26:20 +01:00
Elio Struyf 86a8ef68b6 Settings override 2022-12-22 21:04:24 +01:00
Elio Struyf cc2c6dc217 Allow external configurations - start 2022-12-22 18:03:04 +01:00
Elio Struyf 1764965aa7 8.3.0 2022-12-14 10:18:22 +01:00
Elio Struyf 12559bae4a #482 - Default content type description update 2022-12-14 10:17:47 +01:00
Elio Struyf fc1d750c5e #470 - Fix initialize project dashboard description 2022-12-14 09:45:17 +01:00
Elio Struyf 85c4a869e3 #484 - Workspace path update 2022-12-14 09:40:32 +01:00
Elio Struyf 8b3889f997 #484 - validate if command is available 2022-12-14 09:00:53 +01:00
Elio Struyf 75b01cde7b #484 - Script support for different environments 2022-12-13 12:00:01 +01:00
Elio Struyf c75ab2a07b Update sponsor 2022-12-12 09:02:14 +01:00
Elio Struyf a12cf70a80 Merge pull request #477 from estruyf/dev 2022-12-08 18:12:53 +01:00
Elio Struyf 4b1d80f04b Prepare v8.2.0 release 2022-12-08 18:08:58 +01:00
Elio Struyf a84fecaf96 Update json schema + changelog 2022-12-08 16:46:45 +01:00
Elio Struyf 5b9c279fa2 #471 - Fix typo 2022-12-02 11:49:56 +01:00
Elio Struyf f67be9efb9 update changelog 2022-11-24 17:26:22 +01:00
Elio Struyf 4c50100230 Merge pull request #466 from albbus-stack/git-deleted-files-fix
Thank you @albbus-stack!
2022-11-24 17:25:01 +01:00
albbus-stack 82ace03692 Added git deleted files in the push method 2022-11-24 14:24:09 +01:00
Elio Struyf 576d07fdef Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-11-14 19:40:42 +01:00
Elio Struyf f6bc4fb630 #365 - conditional support 2022-11-14 19:40:38 +01:00
Elio Struyf c35f4ab070 Merge branch 'issue/362-ui' into dev 2022-11-14 19:39:11 +01:00
Elio Struyf 59cbc03b0c #458 - Add prefix to page bundles 2022-11-14 10:48:15 +01:00
Elio Struyf 0ea06a841e #412 - script splitting fix 2022-11-14 10:40:52 +01:00
Elio Struyf b54eb5a360 #462 - Fix issue in script error notification 2022-11-14 10:40:30 +01:00
Elio Struyf 16b6fff6dc #362 - Case sensitive + insensitive option 2022-11-13 20:21:47 +01:00
Elio Struyf 7a46729a46 #362 - additional operators 2022-11-12 18:11:24 +01:00
Elio Struyf 32182c3df0 #362 - Start on conditional ui 2022-11-10 17:21:16 +01:00
Elio Struyf 78587509b3 Update readme 2022-11-09 13:59:19 +01:00
Elio Struyf e3bd7eebbe #360 - Define which content types can be used on your page folders 2022-11-09 13:59:14 +01:00
Elio Struyf f1a8e0d425 #458 - Ability to configure the file prefix on folder level 2022-11-09 10:31:49 +01:00
Elio Struyf 33e294d702 #455 - Show a description for the SEO section 2022-11-08 11:51:41 +01:00
Elio Struyf e098442eaa #412 - Allow title on snippets 2022-11-07 17:28:40 +01:00
Elio Struyf 1de14122c5 #440 - Filter on description 2022-11-07 15:59:20 +01:00
Elio Struyf c0838fffd4 #448 - Full workspace path replacement 2022-11-07 10:50:01 +01:00
Elio Struyf 082c25144f #448 - Fix file retrieval 2022-11-05 18:29:47 +01:00
Elio Struyf d701651a05 #440 - Type to search/filter in the snippets dashboard 2022-11-04 15:25:00 +01:00
Elio Struyf 5205b2d079 #450 - Additional date placeholders 2022-11-04 10:34:46 +01:00
Elio Struyf e864d56081 #447 - Allow to use placeholders on git commit messages 2022-11-04 10:33:23 +01:00
Elio Struyf c6a4c239a0 #449 - Show filename if the title is not set 2022-11-04 09:58:54 +01:00
Elio Struyf 42fbdf9708 #412 - Support for sub-folders 2022-10-31 09:59:33 +01:00
Elio Struyf 8d53990aea #430 - support for post_asset_folder folder 2022-10-08 17:33:42 +02:00
Elio Struyf b9a0c656d3 async updates for settings 2022-10-07 13:32:49 +02:00
Elio Struyf 8a8db67e82 #427 - Add hexo as SSG option 2022-10-07 13:32:42 +02:00
Elio Struyf 0ac4571859 Merge branch 'issue/431' into dev 2022-10-07 10:38:55 +02:00
Elio Struyf a072957793 Updated exists to async 2022-10-06 21:49:53 +02:00
Elio Struyf fad5ad7243 Moving away from fs sync methods 2022-10-06 21:36:51 +02:00
Elio Struyf b248ee7184 Merge branch 'release/v8.1.2' into dev 2022-10-06 14:49:17 +02:00
Elio Struyf cf2d170d6f Merge pull request #437 from estruyf/release/v8.1.2 2022-10-06 14:47:13 +02:00
Elio Struyf 8d577ceb79 #435 #436 - fixes 2022-10-06 14:42:43 +02:00
Elio Struyf 5748aa0540 8.1.2 2022-10-06 14:38:37 +02:00
Elio Struyf 4e850e5cb9 Fix field error message color 2022-10-06 14:23:49 +02:00
Elio Struyf f89d4fce3f #431 - Update changelog 2022-10-04 17:05:04 +02:00
Elio Struyf 1ecf75ae9c Merge branch 'issue/431' into dev 2022-10-04 17:00:48 +02:00
Elio Struyf 888e5c5229 Get webview URI for Windows 2022-10-04 13:47:41 +02:00
Elio Struyf 45eb542619 #431 - Allow pagination page nr 2022-10-03 21:31:23 +02:00
Elio Struyf 5a565f1154 #412 - Override duplicates in config split 2022-10-03 14:07:14 +02:00
Elio Struyf 78002563be Clear cache command 2022-10-03 13:22:40 +02:00
Elio Struyf be3071dc18 Merge branch 'dev' into issue/431 2022-10-02 20:18:46 +02:00
Elio Struyf 5c9d7eda17 #433 - fix title and description rendering if not string 2022-10-02 20:01:59 +02:00
Elio Struyf 9f3cfd9d3a #434 - Webview errors are logged in the extension output 2022-10-02 14:26:22 +02:00
Elio Struyf 0c6ae47a7b #434 - Webview errors are logged in the extension output 2022-10-02 14:25:11 +02:00
Elio Struyf 726a26850d #431 - Cache changes + Tab navigation 2022-10-02 14:23:51 +02:00
Elio Struyf 5fbb05f083 #431 - Performance improvements for first load 2022-10-01 20:30:18 +02:00
Elio Struyf afca99b53a Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-10-01 10:36:34 +02:00
Elio Struyf a8d2c428bc #428 - Image inserting UX enhancement 2022-10-01 10:36:29 +02:00
Elio Struyf 5254f2b7f9 #412 Support added for data files and custom scripts 2022-09-30 14:44:07 +02:00
Elio Struyf 13a71cfd82 #412 - Update setting casing 2022-09-30 10:44:14 +02:00
Elio Struyf 07d67bf881 #412 - allow config folders to use lowercase 2022-09-30 09:54:55 +02:00
Elio Struyf 27887bedef #412 - splitting configuration files 2022-09-29 20:36:02 +02:00
Elio Struyf 2b8f08c03c #406 - Single data entries 2022-09-27 13:16:44 +02:00
Elio Struyf cb2194bc48 8.2.0 2022-09-27 12:04:50 +02:00
Elio Struyf 46872f81ac Include CMS in the display name 2022-09-27 09:00:14 +02:00
Elio Struyf eb9984396b Merge pull request #423 from estruyf/dev 2022-09-23 12:47:54 +02:00
Elio Struyf b7b79024e1 8.1.1 2022-09-23 12:44:18 +02:00
Elio Struyf d17cc901ff #422 - Fix panel initialization logic 2022-09-23 09:20:59 +02:00
Elio Struyf 1fe03197e3 Merge pull request #421 from estruyf/dev 2022-09-22 13:43:18 +02:00
Elio Struyf a1eaa5baca Updated changelog for v8.1.0 2022-09-22 11:49:15 +02:00
Elio Struyf b83b2205c0 Fix issue in image field with required message 2022-09-21 09:55:57 +02:00
Elio Struyf 989d20c474 #417 - New hyperlink wysiwyg option 2022-09-19 10:40:15 +02:00
Elio Struyf 2cf3ff93c5 #418 - Heading and divider fields 2022-09-19 10:18:41 +02:00
Elio Struyf 67b44dce42 #369 - taxonomy fix 2022-09-19 09:52:02 +02:00
Elio Struyf c182a67daa Remove logging 2022-09-19 09:45:02 +02:00
Elio Struyf 2494e4c6c5 #369 - Notification fix 2022-09-19 09:43:57 +02:00
Elio Struyf efc230f81e #391 - New description property 2022-09-16 16:47:32 +02:00
Elio Struyf e455fa764b #369 - required fields 2022-09-15 11:13:26 +02:00
Elio Struyf c6273fa9c1 #408 - add missing view mode 2022-09-12 10:38:53 +02:00
Elio Struyf 9f37ff773e #395: Media snippet enhancements 2022-09-09 18:01:55 +02:00
Elio Struyf 9b21e15c63 #402: Custom sorting of content now supports number fields 2022-09-09 16:44:37 +02:00
Elio Struyf fe41d9a751 Remove bold version 2022-09-09 13:32:55 +02:00
Elio Struyf 5e91a0e7af Update version 2022-09-09 13:26:39 +02:00
Elio Struyf e00186890c Update beta script 2022-09-09 13:22:36 +02:00
Elio Struyf b2b017efc0 #404: Ordering of snippet fields is based on their field definition 2022-09-08 21:08:46 +02:00
Elio Struyf 51b11b66ab #403: Fix for media files with spaces on importing in article content 2022-09-08 20:59:50 +02:00
Elio Struyf 2275c1b9cc #401 - Enable paging on the content dashboard 2022-09-08 11:38:45 +02:00
Elio Struyf bf98ff9a1d Disable popper 2022-09-08 11:01:29 +02:00
Elio Struyf 23c5a7bc18 #400: Fix for draft/published grouping 2022-09-08 10:44:07 +02:00
Elio 4d05c660c8 #398: Fix Windows folder path parsing in data folder retrieval 2022-09-08 10:00:38 +02:00
Elio Struyf 83d4427c09 New titles for inputs 2022-09-07 14:50:21 +02:00
Elio Struyf 45285d3cf2 8.1.0 2022-09-07 12:29:02 +02:00
Elio Struyf f46fdb9fb0 Preview tab title 2022-09-07 10:03:25 +02:00
Elio Struyf 3557360297 #396 - Fix for index and _index previews 2022-09-07 09:59:27 +02:00
Elio Struyf 600c225265 Added divider between action buttons and custom ones 2022-09-07 08:48:14 +02:00
Elio Struyf 7fa814ca1b Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-09-05 15:00:33 +02:00
Elio e4f44def47 Update changelog 2022-09-05 14:58:06 +02:00
Elio 08aa73f9c3 Merge branch 'dev' of https://github.com/estruyf/vscode-front-matter into dev 2022-09-05 14:57:27 +02:00
Elio fcb564b054 #393 - Fix Windows file path 2022-09-05 14:55:34 +02:00
Elio Struyf ac4aea68eb #352: Schema updates 2022-09-05 14:25:59 +02:00
Elio Struyf ad6c37f62d Audit fix for dependencies 2022-09-05 10:55:38 +02:00
Elio Struyf bc3d5cb6b2 Added contact links for issues 2022-09-05 10:53:57 +02:00
Elio Struyf 88c8cc82c8 #394: Ordering of snippet fields is based on their field definition 2022-09-05 10:37:30 +02:00
Elio Struyf 69e0dc3343 Hide the initialize project action 2022-09-02 13:44:11 +02:00
Elio Struyf dda9b88752 #388: Added a stop server action 2022-09-02 13:42:50 +02:00
Elio Struyf 5b712e64d7 #390: Implement another JSON parser 2022-09-02 13:26:25 +02:00
Elio Struyf af1cc15d3d Fix for new popper menu on media items 2022-09-02 09:50:09 +02:00
Elio Struyf 76e3c08405 Fix for custom placeholders that return errors 2022-09-02 09:49:48 +02:00
Elio Struyf ebae16f724 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-08-11 20:58:32 +02:00
Elio Struyf 911adaa5d6 #383: Add the item menu to the content list view 2022-08-11 20:56:07 +02:00
Elio Struyf 1766c19133 #385: Add a default draft field value 2022-08-11 17:52:36 +02:00
Elio Struyf 4a8bbaf82e Fix for empty tags 2022-08-11 17:43:07 +02:00
Elio Struyf fa7b9f3ad1 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-08-10 16:09:36 +02:00
Elio Struyf ecc9c74091 #384: Fix issue title field in sub-fields 2022-08-10 16:09:21 +02:00
Elio Struyf 282c95be29 Initialize listeners 2022-08-04 21:33:00 +02:00
Elio Struyf 9325ce3638 Keep track of all config listeners 2022-08-04 21:32:30 +02:00
Elio Struyf a4da46ca21 #379 - New frontMatter.config.reload command 2022-08-04 21:29:21 +02:00
Elio Struyf 44f30f70d5 Update authenticate command 2022-08-04 20:58:45 +02:00
Elio Struyf 2ef39cb2ed Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-08-04 15:23:50 +02:00
Elio Struyf c8ecc92309 #376: post script functionality 2022-08-04 15:23:41 +02:00
Elio Struyf 3ca6609ace #377 - Git sync command 2022-07-30 14:23:57 +02:00
Elio Struyf 670791fcf6 #377 - update setting name 2022-07-29 16:16:38 +02:00
Elio Struyf 30dc33a859 #377: Git sync actions added on panel and content dashboard 2022-07-29 16:13:46 +02:00
Elio Struyf 07ed95793c #378 - Last modified update only to content in content folders 2022-07-28 11:36:26 +02:00
Elio Struyf 9a9ec33f9f Sorting fix 2022-07-27 12:08:39 +02:00
Elio Struyf 89aab6c74e #352 - Added notification + progress notification 2022-07-27 11:56:51 +02:00
Elio Struyf c0e6c79c67 qna link 2022-07-22 16:03:45 +03:00
Elio Struyf 7badfda41b #352 - Custom placeholders now support scripting 2022-07-21 18:38:53 +03:00
Elio Struyf 9445ce6d37 #374 - Auto-fold the front matter section 2022-07-21 13:15:49 +03:00
Elio Struyf 1aa8f77590 #370 - updated JSON schema 2022-07-20 15:00:17 +03:00
Elio Struyf dab6a46d98 #372 - Update taxonomy to taxonomies 2022-07-18 17:30:19 +03:00
Elio Struyf 6b940e2f24 Merge branch 'main' into dev 2022-07-18 17:27:06 +03:00
Elio Struyf 8c0ce05133 Hide settings icon 2022-07-17 08:13:51 +02:00
Elio Struyf 937494f81c Hide open settings + new docs and settings link 2022-07-17 08:13:42 +02:00
Elio Struyf 7392d7ea0d Merge pull request #371 from estruyf/dev 2022-07-13 13:57:22 +02:00
Elio Struyf 9fcc231a7a 8.0.1 2022-07-13 13:52:14 +02:00
Elio Struyf 149703a5df Fix for tags rendering on content cards 2022-07-13 13:52:06 +02:00
Elio Struyf 463455121e Improve media cards 2022-07-13 10:04:52 +02:00
Elio Struyf 43ae9a6ba2 Merge pull request #367 from estruyf/dev 2022-07-11 16:09:40 +02:00
Elio Struyf c24cc2165f Changelog update 2022-07-11 16:03:03 +02:00
Elio Struyf f177a61d4f Hide template button if disabled 2022-07-11 15:09:47 +02:00
Elio Struyf f13b9e8ea5 #364 - Check content if ends with newline 2022-07-11 15:09:18 +02:00
Elio Struyf c04dd79778 Test updates 2022-06-30 17:39:10 +02:00
Elio Struyf 00273a8c86 #366 - Better support for block in block fields 2022-06-29 09:57:32 +02:00
Elio Struyf 231ef804dc Added sponsor info 2022-06-28 17:13:11 +02:00
Elio Struyf 44dc22c792 #365 - FIx for the spinner 2022-06-28 15:17:32 +02:00
Elio Struyf 830fc550bd Ignore keywords field 2022-06-28 14:48:10 +02:00
Elio Struyf 6c7567a15c Fix when there is no title 2022-06-28 09:17:03 +02:00
Elio Struyf be9797cc77 Updated gitignore 2022-06-28 09:05:28 +02:00
Elio Struyf a78d9c5906 #364 - Honour file ending rules in data files 2022-06-28 09:04:38 +02:00
Elio Struyf 8f4fe45d9e Initial e2e test setup 2022-06-28 08:53:15 +02:00
Elio Struyf 79157feed5 #353 - Add the default content type on initialization 2022-06-16 11:53:08 +02:00
Elio Struyf 09888d5657 #291 - Hierarchy field support 2022-06-16 11:41:54 +02:00
Elio Struyf a0371167bc #356 - fix schema for fieldGroups 2022-06-15 16:52:59 +02:00
Elio Struyf 0dc2623ded #358 - FIx for relative path of the public folder 2022-06-14 13:54:56 +02:00
Elio Struyf 7d81a83672 Merge branch 'main' into dev 2022-06-13 10:28:52 +02:00
Elio 17150a53bc 7.3.4 2022-06-13 10:23:57 +02:00
Elio fbf1990045 Update version in changelog 2022-06-13 10:23:51 +02:00
Elio c5881d7905 Update changelog 2022-06-13 10:20:47 +02:00
Elio b83f7beb30 #354 - Windows file parsing fix 2022-06-13 10:19:20 +02:00
Elio d2c5a850ef Keep panel open 2022-06-13 10:18:04 +02:00
Elio 5b334db3c9 #354 - Windows file parsing fix 2022-06-13 10:17:56 +02:00
Elio Struyf 69aa7a7648 Merge branch 'dev' of github.com:estruyf/vscode-front-matter into dev 2022-06-13 09:10:51 +02:00
Elio Struyf 97e4313d93 Move content type methods 2022-06-13 09:10:40 +02:00
Elio Struyf 3f7acd7e26 Merge branch 'main' into dev 2022-06-11 20:04:31 +02:00
Elio Struyf 7a2b45f031 Fix double pages on contents dashboard 2022-06-11 20:02:35 +02:00
Elio Struyf 8ed64691c4 7.3.3 2022-06-11 20:00:16 +02:00
Elio Struyf 844971cdd9 Fix card render 2022-06-11 20:00:03 +02:00
Elio Struyf cf376cdda7 #291 - Taxonomy dashboard improvements + command 2022-06-10 15:50:49 +02:00
Elio Struyf 1a6acce77f #350 - New previewPath property for page folders 2022-06-10 10:52:10 +02:00
Elio Struyf e9258e1a7f #351 - New template property for content types 2022-06-10 10:28:18 +02:00
Elio Struyf 61b461661d Update use count 2022-06-10 09:35:58 +02:00
Elio Struyf a12a3852d2 Fixes for the table overflow 2022-06-10 09:34:35 +02:00
Elio Struyf 0c94b33606 Move taxonomy value 2022-06-09 16:22:53 +02:00
Elio Struyf 23f3fbfadf 8.0.0 2022-06-09 15:40:24 +02:00
Elio Struyf 434e87b074 Changes to the taxonomy dashboard 2022-06-09 15:40:21 +02:00
Elio Struyf 081fb7ce2e Start of the taxonomy dashboard implementation 2022-06-08 18:37:13 +02:00
Elio Struyf bd43ba8a6d #349 - Slug field 2022-06-03 15:58:19 +02:00
Elio Struyf bd2860e225 #307 - List field 2022-06-02 09:19:06 +02:00
Elio Struyf daeaf0a59d 7.4.0 2022-06-01 13:48:29 +02:00
Elio Struyf 9cc7ea09d6 #345 - Improve the UI of the media dashboard 2022-06-01 13:48:24 +02:00
Elio Struyf 4b6f283bf3 #348 - breadcrumb fix 2022-06-01 13:48:12 +02:00
Elio Struyf e695bad1c6 Merge pull request #347 from estruyf/dev 2022-06-01 12:01:18 +02:00
Elio Struyf fe31081907 7.3.2 2022-06-01 12:00:45 +02:00
Elio Struyf 248ccb3718 Update changelog 2022-06-01 12:00:36 +02:00
Elio Struyf 2260174ec2 Merge branch 'main' into dev 2022-06-01 11:59:30 +02:00
Elio Struyf cd3a867422 #346 - Fix media refresh 2022-06-01 11:59:02 +02:00
Elio Struyf 05a63dd110 Create PULL_REQUEST_TEMPLATE.md 2022-05-30 13:32:48 +02:00
Elio Struyf cfc0c3d5a1 Create CONTRIBUTING.md 2022-05-30 13:29:48 +02:00
Elio Struyf d6dbca25ce Create CODE_OF_CONDUCT.md 2022-05-30 13:16:10 +02:00
487 changed files with 47749 additions and 11775 deletions
+17
View File
@@ -0,0 +1,17 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"ignorePatterns": ["./**/*.js", "./webpack/*.config.js", "./e2e/*.ts"],
"rules": {
"no-throw-literal": "error",
"no-unused-expressions": "error",
"curly": "error",
"class-methods-use-this": "warn"
}
}
+14
View File
@@ -0,0 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://frontmatter.codes/docs
about: See our documentation.
- name: Changelog
url: https://frontmatter.codes/updates
about: See our changelog.
- name: Front Matter website
url: https://frontmatter.codes
about: Our website.
- name: Support Front Matter
url: https://github.com/sponsors/estruyf
about: Support Front Matter development.
+33
View File
@@ -0,0 +1,33 @@
# PR Details
<!--- Provide a general summary of your changes in the Title above -->
## Description
<!--- Describe your changes in detail -->
## Related Issue
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
## How Has This Been Tested
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Docs change / refactoring / dependency upgrade
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
+3 -3
View File
@@ -43,7 +43,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -54,7 +54,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
+65 -2
View File
@@ -5,8 +5,12 @@ on:
types: [created, moved, deleted]
jobs:
automate-issues-labels:
process-project:
name: Add/remove the project label and set the matrix variable
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.setMatrixData.outputs.matrix }}
statusLabel: ${{ steps.setStatusLabel.outputs.statusLabel }}
steps:
- name: Fetch project data
run: |
@@ -14,6 +18,7 @@ jobs:
curl --request GET --url '${{ github.event.project_card.project_url }}' --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
### Add or remove the project label ###
- name: Add the project label
uses: andymckay/labeler@master
if: ${{ contains(github.event.action, 'created') || contains(github.event.action, 'moved') }}
@@ -24,4 +29,62 @@ jobs:
uses: andymckay/labeler@master
if: ${{ contains(github.event.action, 'deleted') }}
with:
remove-labels: "Project: ${{ fromJSON(env.PROJECT_DATA).name }}"
remove-labels: "Project: ${{ fromJSON(env.PROJECT_DATA).name }}"
### Fetch project columns ###
- name: Fetch all columns
run: |
echo 'ALL_COLUMNS_DATA<<EOF' >> $GITHUB_ENV
curl --request GET --url '${{ fromJSON(env.PROJECT_DATA).columns_url }}' --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' | jq --compact-output '["Status: " + .[].name]' >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Fetch column info
run: |
echo 'COLUMN_DATA<<EOF' >> $GITHUB_ENV
curl --request GET --url '${{ github.event.project_card.column_url }}' --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- uses: actions/github-script@v6
id: setMatrixData
with:
result-encoding: string
script: |
const columnData = JSON.parse(process.env.COLUMN_DATA)
const allColumnsData = JSON.parse(process.env.ALL_COLUMNS_DATA)
const matrix = allColumnsData.filter((label) => {
return label !== `Status: ${columnData.name}`
});
core.setOutput('matrix', matrix)
- name: Set the status label
id: setStatusLabel
run: |
echo "statusLabel=${{ fromJSON(env.COLUMN_DATA).name }}" >> $GITHUB_OUTPUT
remove-labels:
runs-on: ubuntu-latest
needs: process-project
if: ${{ contains(github.event.action, 'deleted') || contains(github.event.action, 'moved') }}
strategy:
matrix:
label: ${{fromJson(needs.process-project.outputs.matrix)}}
steps:
- name: Remove the status label
uses: andymckay/labeler@master
with:
remove-labels: ${{ matrix.label }}
add-labels:
runs-on: ubuntu-latest
needs:
- process-project
- remove-labels
if: ${{ contains(github.event.action, 'created') || contains(github.event.action, 'moved') }}
steps:
- name: Add the status label
uses: andymckay/labeler@master
with:
add-labels: "Status: ${{ needs.process-project.outputs.statusLabel }}"
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
run: node scripts/beta-release.js $GITHUB_RUN_ID
- name: Publish
run: npx vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev
run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev
- name: Publish to open-vsx.org
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
run: node scripts/main-release.js
- name: Publish
run: npx vsce publish -p ${{ secrets.VSCE_PAT }}
run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }}
- name: Publish to open-vsx.org
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }}
+5 -1
View File
@@ -4,4 +4,8 @@ node_modules
*.vsix
.DS_Store
dist
todo.md
todo.md
e2e/storage
e2e/extensions
e2e/sample
+1
View File
@@ -0,0 +1 @@
auto-install-peers = true
+7
View File
@@ -0,0 +1,7 @@
{
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "none"
}
+4 -7
View File
@@ -1,8 +1,5 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"ms-vscode.vscode-typescript-tslint-plugin",
"eliostruyf.vscode-typescript-exportallmodules"
]
}
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": ["eliostruyf.vscode-typescript-exportallmodules", "esbenp.prettier-vscode"]
}
+17 -39
View File
@@ -3,46 +3,24 @@
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Extension",
"version": "0.2.0",
"configurations": [
{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "npm: build:ext"
},
{
"name": "Attach Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "npm: build:ext"
},
{
"name": "Attach Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
]
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "npm: test-compile"
}
]
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
]
}
+13 -13
View File
@@ -1,7 +1,7 @@
{
"Recoil Atom": {
"prefix": "sq-atom",
"body": [
"Recoil Atom": {
"prefix": "sq-atom",
"body": [
"import { atom } from 'recoil';",
"",
"export const ${1:CollectionId}Atom = atom({",
@@ -12,9 +12,9 @@
"description": "Creates a new atom",
"scope": "typescript"
},
"Recoil Selector (sync)": {
"prefix": "sq-selector-sync",
"body": [
"Recoil Selector (sync)": {
"prefix": "sq-selector-sync",
"body": [
"import { selector } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selector({",
@@ -27,9 +27,9 @@
"description": "Creates a new synchronous selector",
"scope": "typescript"
},
"Recoil Selector (async)": {
"prefix": "sq-selector-async",
"body": [
"Recoil Selector (async)": {
"prefix": "sq-selector-async",
"body": [
"import { selector } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selector({",
@@ -42,9 +42,9 @@
"description": "Creates a new asynchronous selector",
"scope": "typescript"
},
"Recoil selectorFamily": {
"prefix": "sq-selector-fam",
"body": [
"Recoil selectorFamily": {
"prefix": "sq-selector-fam",
"body": [
"import { selectorFamily } from 'recoil';",
"",
"export const ${1:CollectionData}Selector = selectorFamily({",
@@ -63,4 +63,4 @@
"description": "Include the translations",
"scope": "typescriptreact"
}
}
}
+48 -13
View File
@@ -1,17 +1,52 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"eliostruyf.writingstyleguide.terms.isDisabled": true,
"eliostruyf.writingstyleguide.biasFree.isDisabled": true,
"squarl.groups": [
{
"id": "dashboard",
"name": "Dashboard"
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
{
"id": "panel",
"name": "Panel"
}
],
"squarl.bookmarks": [
{
"name": "App.tsx",
"path": "src/dashboardWebView/components/App.tsx",
"description": "Start of dashboard",
"type": "file",
"groupId": "dashboard"
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"eliostruyf.writingstyleguide.terms.isDisabled": true,
"eliostruyf.writingstyleguide.biasFree.isDisabled": true,
"exportall.config.folderListener": [
"/src/pagesView/state/atom",
"/src/pagesView/state/selectors"
]
}
{
"name": "ViewPanel.tsx",
"path": "src/panelWebView/ViewPanel.tsx",
"description": "Start of panel",
"type": "file",
"groupId": "panel"
},
{
"name": "styles.css",
"path": "src/panelWebView/styles.css",
"description": "Panel styles",
"type": "file",
"groupId": "panel"
},
{
"name": "settings.ts",
"path": "src/constants/settings.ts",
"description": "Settings names",
"type": "file"
}
]
}
+24 -24
View File
@@ -1,28 +1,28 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "build:ext",
"group": {
"kind": "build",
"isDefault": true
}
}
]
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "build:ext",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
+3 -2
View File
@@ -5,7 +5,6 @@ src/**
.gitignore
vsc-extension-quickstart.md
**/tsconfig.json
**/tslint.json
**/*.map
**/*.ts
webpack.config.js
@@ -26,4 +25,6 @@ dist/*.html
frontmatter.json
.frontmatter
webpack
README.beta.md
README.beta.md
e2e
storage
+248 -6
View File
@@ -1,5 +1,248 @@
# Change Log
## [8.4.0] - 2023-04-03 - [Release notes](https://beta.frontmatter.codes/updates/v8.4.0)
### 🧪 Experimental features
- External UI script support for dashboards
- Visual Studio Code Theming support for the dashboards
- Front matter AI 🤖
> **Info**: To enable the experimental features you need to set the `frontMatter.experimental` setting to `true`.
### 🙏 Exclusive Features for Sponsors
We're excited to announce a brand new feature exclusively available to sponsors of Front Matter CMS. With this update, we've added Front Matter AI to the project, which provides helpful suggestions for creating new content such as title suggestions and tag/category suggestions.
> **Important**: To access the Front Matter AI feature, you will need to sign-in ([backers & supports sign-in instructions](https://frontmatter.codes/docs/getting-started#backers-&-supporters)) and set the `frontMatter.sponsors.ai.enabled` setting to `true` and you're good to go! We put it behind a setting to not automatically enable it and let you decide if you want to use it or not.
If you're not already a sponsor, now is a great time to consider supporting the project. By becoming a sponsor, you not only gain access to exclusive features like Front Matter AI, but also help to support the ongoing development and maintenance of the project. You can become a sponsor by visiting the [GitHub sponsor page](https://github.com/sponsors/estruyf).
### ✨ New features
- [#363](https://github.com/estruyf/vscode-front-matter/issues/363): Multiline support for the `string` field in data view
- [#513](https://github.com/estruyf/vscode-front-matter/issues/513): Added support for external UI scripts to add custom HTML on the dashboard elements
- [#530](https://github.com/estruyf/vscode-front-matter/issues/530): Implementation of the Front Matter AI 🤖 powered by [mendable.ai](https://mendable.ai)
- [#537](https://github.com/estruyf/vscode-front-matter/issues/537): Allow to use the root path `/` as the public folder
- [#541](https://github.com/estruyf/vscode-front-matter/issues/541): Added title AI suggestions for GitHub sponsors
- [#548](https://github.com/estruyf/vscode-front-matter/issues/548): Project selection support when working in mono-repos or multi-root workspaces
- [#550](https://github.com/estruyf/vscode-front-matter/issues/550): Added taxonomy (tags/categories) AI suggestions for GitHub sponsors
### 🎨 Enhancements
- Added an `unknown` field for uniforms when it has no type defined
- [#512](https://github.com/estruyf/vscode-front-matter/issues/512): Added the `jsonc` file association for the `frontMatter.json` file. That way, you can use comments in the file.
- [#522](https://github.com/estruyf/vscode-front-matter/issues/522): Configuration support added for [Astro](https://astro.build)
- [#523](https://github.com/estruyf/vscode-front-matter/issues/523): Added support for `floating`/`decimal` numbers with a new number field property called `numberOptions`
- [#524](https://github.com/estruyf/vscode-front-matter/issues/524): Removed the **Global settings** view from the panel. You can still get it back by configuring a [custom view mode](https://frontmatter.codes/docs/panel#view-modes).
- [#535](https://github.com/estruyf/vscode-front-matter/issues/535): Retain the scroll position after selecting a media file
- [#538](https://github.com/estruyf/vscode-front-matter/issues/538): Added support to encode emojis in the string field
- [#549](https://github.com/estruyf/vscode-front-matter/issues/549): Git submodule support to sync changes
- [#554](https://github.com/estruyf/vscode-front-matter/issues/554): When inserting snippets, only the content snippets will be shown
### ⚡️ Optimizations
- [#534](https://github.com/estruyf/vscode-front-matter/issues/534): Moved the `mediaDb.json` file to a `.frontmatter/database` folder instead of the `.frontmatter/content` folder
- [#536](https://github.com/estruyf/vscode-front-matter/issues/536): Set the start location from the script to the root of the workspace
- [#555](https://github.com/estruyf/vscode-front-matter/issues/555): When generating a content-type from existing content, Front Matter will better detect the type of field
- [#556](https://github.com/estruyf/vscode-front-matter/issues/556): Content values are aligned to the type of field
### 🐞 Fixes
- [#518](https://github.com/estruyf/vscode-front-matter/issues/518): Fix an issue where the `YAML` parser adds line breaks to long strings
- [#520](https://github.com/estruyf/vscode-front-matter/issues/520): Add the URL protocol to the host on opening the preview if it's missing
- [#521](https://github.com/estruyf/vscode-front-matter/issues/521): Fix empty snippets dashboard placeholder
- [#526](https://github.com/estruyf/vscode-front-matter/issues/526): Fix card content menu
- [#528](https://github.com/estruyf/vscode-front-matter/issues/528): Fix where the `.astro` code section `---` is seen as front matter
- [#529](https://github.com/estruyf/vscode-front-matter/issues/529): Fix YAML parsing in Windows which added an extra carriage return
- [#531](https://github.com/estruyf/vscode-front-matter/issues/531): Fix prettier update which caused data views to not render list items
- [#539](https://github.com/estruyf/vscode-front-matter/issues/539): Fix the override of the default file prefix on content creation
- [#543](https://github.com/estruyf/vscode-front-matter/issues/543): Fix JSON schema for script commands
- [#547](https://github.com/estruyf/vscode-front-matter/issues/547): Fix setting default value in a hidden group field (`block`)
- [#552](https://github.com/estruyf/vscode-front-matter/issues/552): Fix for content retrieval in multi-root workspaces
- [#557](https://github.com/estruyf/vscode-front-matter/issues/557): Fix for dropdown of the tag picker
## [8.3.0] - 2023-02-14 - [Release notes](https://beta.frontmatter.codes/updates/v8.3.0)
### 🧪 Experimental features
- Visual Studio Code Theming support for the dashboards
> **Info**: To enable the experimental features you need to set the `frontMatter.experimental` setting to `true`.
### ✨ New features
- [#407](https://github.com/estruyf/vscode-front-matter/issues/407): External config support
### 🎨 Enhancements
- [#425](https://github.com/estruyf/vscode-front-matter/issues/425): Added support for placeholders in the content paths and previews
- [#473](https://github.com/estruyf/vscode-front-matter/issues/473): Allow setting the SEO title name with the `frontMatter.taxonomy.seoTitleField` setting
- [#474](https://github.com/estruyf/vscode-front-matter/issues/474): Allow to define the file prefix on content types
- [#484](https://github.com/estruyf/vscode-front-matter/issues/484): Support for overriding scripts per environment type
- [#494](https://github.com/estruyf/vscode-front-matter/issues/494): Support for external image URLs in previews
- [#497](https://github.com/estruyf/vscode-front-matter/issues/497): Support for movie media previews in the content dashboard
- [#502](https://github.com/estruyf/vscode-front-matter/issues/502): Keyboard bindings added to open dashboard, insert media, and insert snippet
- [#503](https://github.com/estruyf/vscode-front-matter/issues/503): Allow making changes to the preview URL in the webview
- [#504](https://github.com/estruyf/vscode-front-matter/issues/504): Allow specifying the filename for your page bundles
- [#505](https://github.com/estruyf/vscode-front-matter/issues/505): Experimental Visual Studio Code theming support
### ⚡️ Optimizations
- [#496](https://github.com/estruyf/vscode-front-matter/issues/496): Make use of the `storageUri` and `globalStorageUri` for storing larger states
### 🐞 Fixes
- [#469](https://github.com/estruyf/vscode-front-matter/issues/469): Fix for using the root folder as content folder
- [#470](https://github.com/estruyf/vscode-front-matter/issues/470): Fix `initialize project` dashboard description
- [#480](https://github.com/estruyf/vscode-front-matter/issues/480): Updated _add missing fields_ label to _add missing fields to content-type_
- [#482](https://github.com/estruyf/vscode-front-matter/issues/482): Update the description when you want to overwrite the default content type description
- [#488](https://github.com/estruyf/vscode-front-matter/issues/488): Fix an issue where the `.frontmatter` folder gets created before initializing the project
- [#493](https://github.com/estruyf/vscode-front-matter/issues/493): Fix an issue where a custom placeholder value is replaced by an `array` instead of a `string`
## [8.2.0] - 2022-12-08 - [Release notes](https://beta.frontmatter.codes/updates/v8.2.0)
### ✨ New features
- [#362](https://github.com/estruyf/vscode-front-matter/issues/362): Support for conditional metadata
- [#412](https://github.com/estruyf/vscode-front-matter/issues/412): Allow `frontmatter.json` to be split in multiple files
### 🎨 Enhancements
- [#360](https://github.com/estruyf/vscode-front-matter/issues/360): Define which content types can be used on your page folders
- [#406](https://github.com/estruyf/vscode-front-matter/issues/406): Added support for single data entries in the data dashboard
- [#428](https://github.com/estruyf/vscode-front-matter/issues/428): Improved UX for inserting images to your content
- [#430](https://github.com/estruyf/vscode-front-matter/issues/430): Support for HEXO its `post_asset_folder` setting (image location)
- [#434](https://github.com/estruyf/vscode-front-matter/issues/434): Webview errors are logged in the extension output
- [#440](https://github.com/estruyf/vscode-front-matter/issues/440): Type to search/filter in the snippets dashboard
- [#447](https://github.com/estruyf/vscode-front-matter/issues/447): Allow to use placeholders on git commit messages
- [#449](https://github.com/estruyf/vscode-front-matter/issues/449): Show `filename` if the `title` is not set
- [#450](https://github.com/estruyf/vscode-front-matter/issues/450): Additional time placeholders added `{{hour12}}`, `{{hour24}}`, `{{ampm}}`, and `{{minute}}`
- [#458](https://github.com/estruyf/vscode-front-matter/issues/458): Ability to configure the file prefix on folder level
### ⚡️ Optimizations
- [#431](https://github.com/estruyf/vscode-front-matter/issues/431): Performance improvements for the content dashboard
- [#448](https://github.com/estruyf/vscode-front-matter/issues/448): Retrieving files fails when content folder name and workspace folder name are the same
- [#455](https://github.com/estruyf/vscode-front-matter/issues/455): Show a description for the SEO section when title nor description is set
### 🐞 Fixes
- Fix field error message color
- [#433](https://github.com/estruyf/vscode-front-matter/issues/433): Fix issue with rendering an incorrect title value on the content dashboard
- [#462](https://github.com/estruyf/vscode-front-matter/issues/462): Fix issue in script error notification
- [#465](https://github.com/estruyf/vscode-front-matter/issues/465): Deleted content does not get added in git when syncing
- [#471](https://github.com/estruyf/vscode-front-matter/issues/471): Fix typo on data dashboard
## [8.1.2] - 2022-10-06
### 🐞 Fixes
- [#435](https://github.com/estruyf/vscode-front-matter/issues/435): Fix required fields text color
- [#436](https://github.com/estruyf/vscode-front-matter/issues/436): Fix inserting image/video snippets without defined fields
## [8.1.1] - 2022-09-23
### 🐞 Fixes
- [#422](https://github.com/estruyf/vscode-front-matter/issues/422): Fix in panel initialization logic
## [8.1.0] - 2022-09-22 - [Release notes](https://beta.frontmatter.codes/updates/v8.1.0)
### ✨ New features
- [#369](https://github.com/estruyf/vscode-front-matter/issues/369): New `required` property to specify if a content-type field is required
- [#376](https://github.com/estruyf/vscode-front-matter/issues/376): Ability to run scripts after content was created
- [#377](https://github.com/estruyf/vscode-front-matter/issues/377): Git sync actions added on panel and content dashboard (pull and push your changes to remote)
- [#379](https://github.com/estruyf/vscode-front-matter/issues/377): New `frontMatter.config.reload` command to reload the configuration file + reinitialize its listeners
- [#391](https://github.com/estruyf/vscode-front-matter/issues/391): New `description` property to show a message underneath the input field
- [#401](https://github.com/estruyf/vscode-front-matter/issues/401): Content dashboard now has pagination enabled and can be disabled via the `frontMatter.dashboard.content.pagination` setting
### 🎨 Enhancements
- [#352](https://github.com/estruyf/vscode-front-matter/issues/352): Custom placeholders now support scripting
- [#370](https://github.com/estruyf/vscode-front-matter/issues/370): Define the tags and categories as reserved keywords for custom taxonomy
- [#372](https://github.com/estruyf/vscode-front-matter/issues/372): Rename Taxonomy tab to Taxonomies
- [#374](https://github.com/estruyf/vscode-front-matter/issues/374): Hide the front matter section to use the panel instead
- [#383](https://github.com/estruyf/vscode-front-matter/issues/383): Add the item menu to the content list view
- [#385](https://github.com/estruyf/vscode-front-matter/issues/385): If no default value for the draft field is defined, the field value will be set to `true`
- [#388](https://github.com/estruyf/vscode-front-matter/issues/388): New stop server action has been added to the panel
- [#390](https://github.com/estruyf/vscode-front-matter/issues/390): Implement another JSON parser in order to be able to parse the `frontmatter.json` file better
- [#394](https://github.com/estruyf/vscode-front-matter/issues/394): Ordering of snippet fields is based on their field definition
- [#395](https://github.com/estruyf/vscode-front-matter/issues/395): Added support for custom snippet fields on media snippets
- [#402](https://github.com/estruyf/vscode-front-matter/issues/402): Custom sorting of content now supports `number` fields
- [#417](https://github.com/estruyf/vscode-front-matter/issues/417): New `hyperlink` wysiwyg option
- [#418](https://github.com/estruyf/vscode-front-matter/issues/418): New `heading` and `divider` fields for your content-type definition
### ⚡️ Optimizations
- Internal post message optimizations to the webviews
- Preview tab now shows the title of the page/content if present
### 🐞 Fixes
- [#378](https://github.com/estruyf/vscode-front-matter/issues/378): Fix last modified update only to content in content folders
- [#384](https://github.com/estruyf/vscode-front-matter/issues/384): Fix issue `title` field in sub-fields
- [#393](https://github.com/estruyf/vscode-front-matter/issues/393): Fix Windows file path for retrieving the preview path
- [#396](https://github.com/estruyf/vscode-front-matter/issues/396): Fix for `index` and `_index` page previews
- [#398](https://github.com/estruyf/vscode-front-matter/issues/398): Fix Windows folder path parsing in data folder retrieval
- [#400](https://github.com/estruyf/vscode-front-matter/issues/400): Fix for draft/published content grouping
- [#403](https://github.com/estruyf/vscode-front-matter/issues/403): Fix for media files with spaces on importing in article content
- [#404](https://github.com/estruyf/vscode-front-matter/issues/404): Fix for published sorting option in media dashboard
- [#408](https://github.com/estruyf/vscode-front-matter/issues/408): Fix for missing `dashboard.taxonomy.view` view mode in the JSON schema
## [8.0.1] - 2022-07-13
### 🐞 Fixes
- Fix `PSD` media card icon image
- Fix missing clipboard icon for the media card action
- Fix in tags rendering on content cards
## [8.0.0] - 2022-07-11 - [Release notes](https://beta.frontmatter.codes/updates/v8.0.0)
### ✨ New Features
- [#291](https://github.com/estruyf/vscode-front-matter/issues/291): New taxonomy dashboard for managing tags, categories, and custom taxonomies
### 🎨 Enhancements
- Ignore the SEO `keywords` field for missing content type field
- [#307](https://github.com/estruyf/vscode-front-matter/issues/307): New `list` field which allows to create a list of items
- [#345](https://github.com/estruyf/vscode-front-matter/issues/345): Media dashboard UI improvements to visualize the content and public folders
- [#349](https://github.com/estruyf/vscode-front-matter/issues/349): New `slug` field which allows you to manage the slug of your post from the Front Matter panel
- [#350](https://github.com/estruyf/vscode-front-matter/issues/350): New `previewPath` property for the `frontMatter.content.pageFolders` setting. This allows you to specify a section prefix for all content created in that directory.
- [#351](https://github.com/estruyf/vscode-front-matter/issues/351): New `template` property for content types which allows you to combine templates and content types for content creation
- [#353](https://github.com/estruyf/vscode-front-matter/issues/353): Add the default content type on project initialization
- [#366](https://github.com/estruyf/vscode-front-matter/issues/366): Better support for using block fields in another block field
### 🐞 Fixes
- [#348](https://github.com/estruyf/vscode-front-matter/issues/348): Fix media dashboard breadcrumb when multiple page folders are in use
- [#356](https://github.com/estruyf/vscode-front-matter/issues/356): Re-introduce the `labelField` to the `frontMatter.taxonomy.fieldGroups` setting
- [#358](https://github.com/estruyf/vscode-front-matter/issues/358): Fix for relative path of the public folder
- [#364](https://github.com/estruyf/vscode-front-matter/issues/364): Honour file ending rules in data files
- [#365](https://github.com/estruyf/vscode-front-matter/issues/365): Show spinner on the initial load of the content dashboard
## [7.3.4] - 2022-06-13
### 🐞 Fixes
- [#354](https://github.com/estruyf/vscode-front-matter/issues/354): Fix Windows file path parsing for inserting media files
## [7.3.3] - 2022-06-11
### 🐞 Fixes
- Card render when taxonomy is not an array value
- Double pages on contents dashboard
## [7.3.2] - 2022-06-01
### 🐞 Fixes
- [#346](https://github.com/estruyf/vscode-front-matter/issues/346): Fix media dashboard refresh action
## [7.3.1] - 2022-05-26
### 🐞 Fixes
@@ -26,7 +269,6 @@
- [#334](https://github.com/estruyf/vscode-front-matter/issues/334): Fix for locked content folders retrieval
- [#339](https://github.com/estruyf/vscode-front-matter/issues/339): Fix for content folders without a title
## [7.2.0] - 2022-05-02 - [Release notes](https://beta.frontmatter.codes/updates/v7.2.0)
### 🎨 Enhancements
@@ -68,7 +310,7 @@
## [7.1.0] - 2022-04-07 - [Release notes](https://beta.frontmatter.codes/updates/v7.1.0)
### 🎨 Enhancements
- [#240](https://github.com/estruyf/vscode-front-matter/issues/240): Capability added to define display modes
- [#246](https://github.com/estruyf/vscode-front-matter/issues/246): Support to add multiple tags/keywords/taxonomy via comma separated values
- [#293](https://github.com/estruyf/vscode-front-matter/issues/293): Support added for setting preview images in block fields
@@ -88,7 +330,7 @@
- [#304](https://github.com/estruyf/vscode-front-matter/issues/304): Fix yaml stringify which caused additional fields to be added
- [#305](https://github.com/estruyf/vscode-front-matter/issues/305): Fix for overflow issue in taxonomy picker
- [#306](https://github.com/estruyf/vscode-front-matter/issues/306): Fix for default value of content type fields
- [#311](https://github.com/estruyf/vscode-front-matter/issues/311): Fix for updating snippets
- [#311](https://github.com/estruyf/vscode-front-matter/issues/311): Fix for updating snippets
## [7.0.0] - 2022-03-21 - [Release notes](https://beta.frontmatter.codes/updates/v7.0.0)
@@ -264,7 +506,7 @@ As from this version onwards, the extension will be published to [open-vsx.org](
### 🎨 Enhancements
- [#173](https://github.com/estruyf/vscode-front-matter/issues/173): Allow to specify your own sorting for the content dashboard
- [#174](https://github.com/estruyf/vscode-front-matter/issues/174): Added option to exclude sub-directories from page/markdown content retrieval
- [#174](https://github.com/estruyf/vscode-front-matter/issues/174): Added option to exclude sub-directories from page/markdown content retrieval
## [5.4.0] - 2021-11-05
@@ -410,10 +652,10 @@ As from this version onwards, the extension will be published to [open-vsx.org](
- Fix typo in the `package.json` file for the preview command
## [2.5.0] - 2020-08-19
## [2.5.0] - 2020-08-19
- Moved the center layout button to the other actions section
- [#60](https://github.com/estruyf/vscode-front-matter/issues/60): Added the ability to open a site preview in VS Code
- [#60](https://github.com/estruyf/vscode-front-matter/issues/60): Added the ability to open a site preview in VS Code
## [2.4.1] - 2020-08-16
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
elio@struyfconsulting.be.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+59
View File
@@ -0,0 +1,59 @@
# Contributing to Front Matter
First of all, it is amazing you want to contribute to Front Matter 💚.
There are various ways in how you can contribute to the project, it can be as simple from opening a bug report to implementing fixes or features.
## How you can help us
- Testing out the extension and providing feedback
- Reporting issues and bugs
- Suggesting new features
- Fixing an issue
- Updating documentation
- UI improvements
- Tutorials
- etc.
Eager to start contributing? Great 🤩, you can contribute to the following projects:
- [Extension](https://github.com/estruyf/vscode-front-matter)
- [Documentation](https://github.com/FrontMatter/web-documentation-nextjs)
- [Sample Projects](https://github.com/FrontMatter/project-samples)
## How to get started
- Start by forking this project;
- Clone your fork to your local machine;
- Run `pnpm i`;
- Open the project in VS Code;
- To start developing, run `pnpm dev:ext` and press `f5` to start the debugging session.
### Tips
- Ensure that the main branch on your fork is in sync with the original **vscode-front-matter** repository
```bash
# assuming you are in the folder of your locally cloned fork....
git checkout main
# assuming you have a remote named `upstream` pointing to the official **vscode-front-matter** repo
git fetch upstream
# update your local main to be a mirror of what's in the main repo
git pull --rebase upstream main
```
- Create a feature branch in your fork. In case you get stuck, or have issues with merging your PR, this will allow you to have a clean main branch that you can use for contributing other changes.
```bash
git checkout -b issue/<id>
```
## Pull request
Once you are done with implementing the fix or feature. Please create a PR to our `dev` branch.
## License
By contributing, you agree that your contributions will be licensed under its MIT License.
+11 -5
View File
@@ -10,12 +10,12 @@
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter" title="Check it out on the Visual Studio Marketplace">
<img src="https://vsmarketplacebadge.apphb.com/version/eliostruyf.vscode-front-matter.svg" alt="Visual Studio Marketplace" style="display: inline-block" />
<img src="https://vsmarketplacebadges.dev/version/eliostruyf.vscode-front-matter.svg" alt="Visual Studio Marketplace" style="display: inline-block" />
</a>
<img src="https://vsmarketplacebadge.apphb.com/installs/eliostruyf.vscode-front-matter.svg" alt="Number of installs" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadges.dev/installs/eliostruyf.vscode-front-matter.svg" alt="Number of installs" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadge.apphb.com/rating/eliostruyf.vscode-front-matter.svg" alt="Ratings" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadges.dev/rating/eliostruyf.vscode-front-matter.svg" alt="Ratings" style="display: inline-block;margin-left:10px" />
<a href="https://github.com/sponsors/estruyf" title="Become a sponsor" style="margin-left:10px">
<img src="https://img.shields.io/github/sponsors/estruyf?color=%23CE2E7C&logo=github&style=flat" alt="Sponsor the project" style="display: inline-block" />
@@ -54,6 +54,12 @@ A couple of our extension highlights that hopefully get you interested in giving
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 8**
The taxonomy dashboard got introduced on which you can manage your tags, categories, and custom taxonomy.
![Taxonomy dashboard](https://beta.frontmatter.codes/assets/marketplace/v8.1.0/taxonomy-dashboard.png)
**Version 7**
Snippets support for Front Matter has been added!
@@ -185,6 +191,6 @@ You can open showcase issues for the following things:
<p align="center">
<a href="https://visitorbadge.io">
<img src="https://estruyf-github.azurewebsites.net/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" />
</a>
<img src="https://api.visitorbadge.io/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" />
</a>
</p>
+25 -11
View File
@@ -8,12 +8,12 @@
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter" title="Check it out on the Visual Studio Marketplace">
<img src="https://vsmarketplacebadge.apphb.com/version/eliostruyf.vscode-front-matter.svg" alt="Visual Studio Marketplace" style="display: inline-block" />
<img src="https://vsmarketplacebadges.dev/version/eliostruyf.vscode-front-matter.svg" alt="Visual Studio Marketplace" style="display: inline-block" />
</a>
<img src="https://vsmarketplacebadge.apphb.com/installs/eliostruyf.vscode-front-matter.svg" alt="Number of installs" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadge.apphb.com/rating/eliostruyf.vscode-front-matter.svg" alt="Ratings" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadges.dev/installs/eliostruyf.vscode-front-matter.svg" alt="Number of installs" style="display: inline-block;margin-left:10px" />
<img src="https://vsmarketplacebadges.dev/rating/eliostruyf.vscode-front-matter.svg" alt="Ratings" style="display: inline-block;margin-left:10px" />
<a href="https://github.com/sponsors/estruyf" title="Become a sponsor" style="margin-left:10px">
<img src="https://img.shields.io/github/sponsors/estruyf?color=%23CE2E7C&logo=github&style=flat" alt="Sponsor the project" style="display: inline-block" />
@@ -52,6 +52,12 @@ A couple of our extension highlights that hopefully get you interested in giving
> If you see something missing in your article creation flow, please feel free to reach out.
**Version 8**
The taxonomy dashboard got introduced on which you can manage your tags, categories, and custom taxonomy.
![Taxonomy dashboard](https://frontmatter.codes/assets/marketplace/v8.1.0/taxonomy-dashboard.png)
**Version 7**
Snippets support for Front Matter has been added!
@@ -109,7 +115,7 @@ You can get the extension via:
If you have the courage to test out the beta features, we made available a beta version as well. You can install this via:
- Uninstall the main Front Matter version
- Install the beta version
- Install the beta version
- VS Code marketplace: [VS Code Marketplace - Front Matter BETA](https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter-beta).
- The extension CLI: `ext install eliostruyf.vscode-front-matter-beta`
- Or by clicking on the following link: <a href="" title="open extension in VS Code" data-vscode="vscode:extension/eliostruyf.vscode-front-matter-beta">open extension in VS Code</a>
@@ -157,21 +163,29 @@ You can open showcase issues for the following things:
<p align="center">
<a href="https://github.com/estruyf/vscode-front-matter/graphs/contributors">
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" />
<img src="https://contrib.rocks/image?repo=estruyf/vscode-front-matter" alt="Front Matter contributors" />
</a>
</p>
## 🖤 Backers & Sponsors 👇 🤘
<p align="center">
<img src="https://frontmatter.codes/api/img-sponsors" />
<img src="https://frontmatter.codes/api/img-sponsors" alt="Front Matter sponsors" />
</p>
<br />
<p align="center" title="Powered by Vercel">
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" alt="Powered by Vercel" />
</a>
</p>
<br />
<p align="center">
<a href="https://vercel.com/?utm_source=vscode-frontmatter&utm_campaign=oss">
<img src="https://frontmatter.codes/assets/sponsors/powered-by-vercel.png" />
<a href="http://bejs.io/" title="Supported by the BEJS Community">
<img src="https://frontmatter.codes/assets/sponsors/bejs-community.png" alt="Supported by the BEJS Community" height="50px"/>
</a>
</p>
@@ -184,6 +198,6 @@ You can open showcase issues for the following things:
<p align="center">
<a href="https://visitorbadge.io">
<img src="https://estruyf-github.azurewebsites.net/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" />
<img src="https://api.visitorbadge.io/api/VisitorHit?user=estruyf&repo=vscode-front-matter&countColor=%23F05450&labelColor=%230E131F" height="25px" alt="Front Matter visitors" />
</a>
</p>
</p>
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<path fill="#C5C5C5" d="M16 19a6.99 6.99 0 0 1-5.833-3.129l1.666-1.107a5 5 0 0 0 8.334 0l1.666 1.107A6.99 6.99 0 0 1 16 19zm4-11a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2zm-8 0a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2z"/>
<path fill="#C5C5C5" d="M17.736 30L16 29l4-7h6a1.997 1.997 0 0 0 2-2V6a1.997 1.997 0 0 0-2-2H6a1.997 1.997 0 0 0-2 2v14a1.997 1.997 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V6a3.999 3.999 0 0 1 4-4h20a3.999 3.999 0 0 1 4 4v14a4 4 0 0 1-4 4h-4.835Z"/>
</svg>

After

Width:  |  Height:  |  Size: 540 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<path fill="#424242" d="M16 19a6.99 6.99 0 0 1-5.833-3.129l1.666-1.107a5 5 0 0 0 8.334 0l1.666 1.107A6.99 6.99 0 0 1 16 19zm4-11a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2zm-8 0a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2z"/>
<path fill="#424242" d="M17.736 30L16 29l4-7h6a1.997 1.997 0 0 0 2-2V6a1.997 1.997 0 0 0-2-2H6a1.997 1.997 0 0 0-2 2v14a1.997 1.997 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V6a3.999 3.999 0 0 1 4-4h20a3.999 3.999 0 0 1 4 4v14a4 4 0 0 1-4 4h-4.835Z"/>
</svg>

After

Width:  |  Height:  |  Size: 540 B

+44 -291
View File
@@ -33,20 +33,27 @@
position: inherit !important;
}
.z-10 { z-index: 10 !important; }
.z-20 { z-index: 10 !important; }
.z-10 {
z-index: 10 !important;
}
.z-20 {
z-index: 10 !important;
}
.w-full {
width: 100% !important;
}
.collapsible__body,
.ext_settings {
padding: 1rem 1.25rem;
.ext_settings,
.git_actions,
.initialize_actions {
padding: 1rem 1.25rem;
box-sizing: border-box;
}
#app, .frontmatter {
#app,
.frontmatter {
height: 100%;
}
@@ -56,7 +63,7 @@
align-items: center;
opacity: 0.8;
text-align: center;
padding: 1rem 1.25rem;
padding: 1rem 1.25rem;
}
.spinner,
@@ -101,7 +108,7 @@
padding-bottom: var(--input-margin-vertical);
display: flex;
flex-direction: column;
justify-content: space-between;
justify-content: start;
}
.frontmatter h3 {
@@ -111,7 +118,7 @@
.frontmatter p,
.frontmatter h4,
.frontmatter ul {
margin-bottom: .5rem;
margin-bottom: 0.5rem;
}
.article__tags h3,
@@ -129,7 +136,8 @@
margin-right: 0.5rem;
}
.seo__status__details, .seo__status__keywords {
.seo__status__details,
.seo__status__keywords {
margin-bottom: 1rem;
}
@@ -160,41 +168,11 @@
}
.article__tags__dropbox.open {
border: 1px solid rgba(0, 0, 0, .9);
}
.article__tags__input input {
border: 1px solid var(--vscode-inputValidation-infoBorder);
}
.article__tags__input input:disabled {
border-color: transparent;
}
.article__tags__input.freeform {
position: relative;
outline: 1px solid var(--vscode-inputValidation-infoBorder);
outline-offset: -1px;
}
.article__tags__input.freeform input {
padding-right: 35px;
border: 0;
}
.article__tags__input button {
position: absolute;
bottom: 0;
top: 0;
right: 0;
width: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 0, 0, 0.9);
}
.article__tags ul {
color: var(--vscode-dropdown-foreground);
color: var(--vscode-dropdown-foreground);
background-color: var(--vscode-dropdown-background);
}
@@ -204,16 +182,16 @@
}
.article__tags li:active {
color: var(--vscode-button-foreground);
color: var(--vscode-button-foreground);
background-color: var(--vscode-button-background);
}
.article__tags li[aria-selected="true"] {
.article__tags li[aria-selected='true'] {
color: var(--vscode-button-foreground);
background-color: var(--vscode-button-hoverBackground);
}
.article__tags li[aria-disabled="true"] {
.article__tags li[aria-disabled='true'] {
display: none;
}
@@ -252,7 +230,7 @@
}
.ext_link_block svg {
margin-right: .5rem;
margin-right: 0.5rem;
display: block;
width: 16px;
height: 16px;
@@ -286,16 +264,16 @@
}
.ext_link_block button.active {
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
}
.ext_link_block button.active:hover {
cursor: pointer;
background: var(--vscode-button-hoverBackground);
cursor: pointer;
background: var(--vscode-button-hoverBackground);
}
.ext_link_block a:hover,
.ext_link_block a:active,
.ext_link_block a:hover,
.ext_link_block a:active,
.ext_link_block a:focus,
.ext_link_block a:visited {
color: var(--vscode-button-secondaryForeground);
@@ -329,15 +307,15 @@
}
.table__cell__validation .valid {
color: #46EC86;
color: #46ec86;
}
.table__cell__validation .warning {
color: #E6AF2E;
color: #e6af2e;
}
.table__cell__validation div span + span {
margin-left: .5rem;
margin-left: 0.5rem;
}
.seo__status__note {
@@ -353,7 +331,7 @@
height: 24px;
}
.field__toggle input {
.field__toggle input {
opacity: 0;
width: 0;
height: 0;
@@ -367,21 +345,21 @@
right: 0;
bottom: 0;
background-color: var(--vscode-button-secondaryBackground);
-webkit-transition: .4s;
transition: .4s;
-webkit-transition: 0.4s;
transition: 0.4s;
border-radius: 34px;
}
.field__toggle__slider:before {
position: absolute;
content: "";
content: '';
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
-webkit-transition: 0.4s;
transition: 0.4s;
border-radius: 50%;
}
@@ -400,162 +378,6 @@ input:checked + .field__toggle__slider:before {
}
/* Metadata */
.metadata_field {
margin-bottom: 1rem;
}
.vscode-dark .metadata_field__box {
background: rgba(255, 255, 255, 0.1);
border: 1px dashed rgba(255, 255, 255, 0.2);
}
.vscode-light .metadata_field__box {
background: rgba(0, 0, 0, 0.1);
border: 1px dashed rgba(0, 0, 0, 0.2);
}
.metadata_field__box {
background: rgba(255, 255, 255, 0.1);
border: 1px dashed rgba(255, 255, 255, 0.2);
margin-bottom: .5rem;
padding: .5rem 1rem;
}
.metadata_field__label {
display: flex;
align-items: center;
margin-bottom: .5rem;
}
.metadata_field__label.metadata_field__label_parent {
justify-content: center;
}
.metadata_field__label svg {
margin-right: .5rem;
}
.metadata_field__error {
color: var(--vscode-errorForeground);
display: flex;
justify-content: space-between;
align-items: center;
}
.metadata_field__error button {
color: var(--vscode-button-secondaryForeground);
background-color: var(--vscode-button-secondaryBackground);
padding-left: 1rem;
padding-right: 1rem;
width: auto;
}
.metadata_field__error button:hover {
background-color: var(--vscode-button-secondaryHoverBackground);
}
.metadata_field__input, .metadata_field__input:focus,
.metadata_field__textarea, .metadata_field__textarea:focus {
outline: none;
}
.metadata_field__limit {
color: var(--vscode-inputValidation-warningBorder);
margin-top: .25rem;
}
.metadata_field__number {
border: 1px solid var(--vscode-inputValidation-infoBorder) !important;
outline: none !important;
}
.metadata_field__choice__toggle {
color: var(--vscode-input-placeholderForeground);
border: 1px solid var(--vscode-inputValidation-infoBorder) !important;
outline: none !important;
width: 100%;
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
background-color: var(--vscode-input-background);
display: flex;
align-items: center;
position: relative;
}
.metadata_field__choice__toggle:hover,
.metadata_field__choice__toggle:focus,
.metadata_field__choice__toggle:active,
.metadata_field__choice__toggle:disabled {
background-color: var(--vscode-input-background);
}
.metadata_field__choice__toggle span {
margin-right: 1rem;
}
.metadata_field__choice__toggle svg.icon {
height: 1rem;
width: 1rem;
margin-left: .25rem;
position: absolute;
right: .25rem;
}
.metadata_field__choice_list {
width: 90%;
margin: 0;
padding: 0;
z-index: 1;
position: absolute;
list-style: none;
overflow: auto;
max-height: 200px;
color: var(--vscode-dropdown-foreground);
background-color: var(--vscode-dropdown-background);
}
.metadata_field__choice_list.open {
border: 1px solid rgba(0, 0, 0, .9);
}
.metadata_field__choice_list li {
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
cursor: pointer;
}
.metadata_field__choice_list li:active {
color: var(--vscode-button-foreground);
background-color: var(--vscode-button-background);
}
.metadata_field__choice_list li[aria-selected="true"] {
color: var(--vscode-button-foreground);
background-color: var(--vscode-button-hoverBackground);
}
.metadata_field__choice_list li[aria-disabled="true"] {
display: none;
}
.metadata_field__choice_list__item {
opacity: 0.8;
}
.metadata_field__choice__button {
margin-top: .5rem;
display: inline-flex;
align-items: center;
width: auto;
margin-right: .5rem;
}
.metadata_field__choice__button_icon {
height: 1.25rem;
width: 1.25rem;
margin-left: .5rem;
}
.metadata_field__datetime {
display: flex;
@@ -578,75 +400,6 @@ input:checked + .field__toggle__slider:before {
background-color: var(--vscode-button-secondaryHoverBackground);
}
.metadata_field__multiple_images {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
}
.metadata_field__preview_image img {
display: block;
margin: 0 auto;
max-height: 16rem;
}
.metadata_field__file__button,
.metadata_field__preview_image__button {
background-color: transparent;
border: 1px dashed var(--vscode-button-background);
padding: 1.5rem;
filter: brightness(85%);
}
.metadata_field__file__button:hover,
.metadata_field__preview_image__button:hover {
background-color: rgba(255, 255, 255, .1);
filter: brightness(100%);
}
.metadata_field__file__button svg,
.metadata_field__preview_image__button svg {
color: var(--vscode-foreground);
display: block;
width: 3rem;
height: 3rem;
margin: 0 auto;
}
.metadata_field__file__button span,
.metadata_field__preview_image__button span {
color: var(--vscode-foreground);
display: inline-block;
margin: 0 auto;
margin-top: .5rem;
}
.vscode-light .metadata_field__preview_image__preview {
background: rgba(0, 0, 0, 0.1);
}
.vscode-dark .metadata_field__preview_image__preview {
background: rgba(255, 255, 255, 0.1);
}
.metadata_field__preview_image__preview {
background-color: var(--vscode-button-secondaryBackground);
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.metadata_field__preview_image__remove {
background-color: var(--vscode-inputValidation-errorBackground);
color: var(--vscode-inputValidation-errorForeground);
}
.metadata_field__preview_image__remove:hover {
background-color: var(--vscode-inputValidation-errorBackground);
color: var(--vscode-inputValidation-errorForeground);
opacity: .9;
}
/* File list */
.file_list vscode-label {
border-bottom: 1px solid var(--vscode-foreground);
@@ -658,7 +411,7 @@ input:checked + .field__toggle__slider:before {
}
.file_list__items__item {
color: var(--vscode-foreground);
color: var(--vscode-foreground);
font-size: var(--vscode-font-size);
font-weight: var(--vscode-font-weight);
cursor: pointer;
@@ -672,7 +425,7 @@ input:checked + .field__toggle__slider:before {
}
.file_list__items__item:hover {
background-color: var(--vscode-list-hoverBackground);
background-color: var(--vscode-list-hoverBackground);
color: var(--vscode-list-hoverForeground);
cursor: pointer;
}
@@ -682,7 +435,7 @@ input:checked + .field__toggle__slider:before {
flex-shrink: 0;
height: 20px;
width: 20px;
margin-right: .25rem;
margin-right: 0.25rem;
}
.file_list__items__item span {
@@ -707,7 +460,7 @@ input:checked + .field__toggle__slider:before {
.sponsor svg {
height: 20px;
width: 20px;
margin-right: .25rem;
margin-right: 0.25rem;
}
.sponsor a {
@@ -724,7 +477,7 @@ input:checked + .field__toggle__slider:before {
}
.sponsor a > span {
margin-right: .25rem;
margin-right: 0.25rem;
}
/* Timepicker */
@@ -747,4 +500,4 @@ input:checked + .field__toggle__slider:before {
.react-datepicker-time__input input {
border: 1px solid #aeaeae !important;
}
}
+78
View File
@@ -0,0 +1,78 @@
import { By, VSBrowser, EditorView, WebView, Workbench, Notification, StatusBar, NotificationType } from "vscode-extension-tester";
import { expect } from "chai";
import { sleep } from "./utils";
import { join } from "path";
// https://github.com/microsoft/vscode-java-dependency/blob/4256fa6adcaff5ec24dbdbb8d9a516fad21431c5/test/ui/index.ts
// https://github.com/microsoft/vscode-java-dependency/blob/4256fa6adcaff5ec24dbdbb8d9a516fad21431c5/test/ui/command.test.ts
describe("Initialization testing", function() {
this.timeout(2 * 60 * 1000 /*ms*/);
let workbench: Workbench;
let view: WebView;
before(async function() {
await VSBrowser.instance.openResources(join(__dirname, '../sample'));
await sleep(3000);
workbench = new Workbench();
await workbench.executeCommand("frontMatter.dashboard");
await sleep(3000);
await new EditorView().openEditor(`FrontMatter Dashboard`);
view = new WebView();
await view.switchToFrame();
});
it("1. Open welcome dashboard", async function() {
const element = await view.findWebElement(By.css('h1'));
const title = await element.getText();
expect(title).has.string(`Front Matter`);
});
it("2. Initialize project", async function() {
const btn = await view.findWebElement(By.css('[data-test="welcome-init"] button'));
expect(btn).to.exist;
await btn.click();
await sleep(1000);
await VSBrowser.instance.driver.wait(() => {
return notificationExists(workbench, 'Front Matter:');
}, 2000) as Notification;
const notifications = await workbench.getNotifications();
let notification!: Notification;
for (const not of notifications) {
console.log(not);
// const message = await not.get;
// console.log(message);
// if (message.includes('Front Matter:')) {
// notification = not;
// }
}
expect(await notification.getMessage()).has.string(`Project initialized successfully.`);
});
it("3. Check if project file is created", async function() {});
});
async function notificationExists(workbench: Workbench, text: string): Promise<Notification | undefined> {
const notifications = await (await (new StatusBar()).openNotificationsCenter()).getNotifications(NotificationType.Info);
for (const notification of notifications) {
const message = await notification.getMessage();
if (message.indexOf(text) >= 0) {
return notification;
}
}
}
+33
View File
@@ -0,0 +1,33 @@
import * as path from 'path'
import * as semver from 'semver'
import { ExTester, ReleaseQuality } from 'vscode-extension-tester'
async function main(): Promise<void> {
const vsCodeVersion: semver.SemVer = new semver.SemVer(`1.66.0`)
const version = vsCodeVersion.version
const storageFolder = path.join(__dirname, '..', 'storage')
const extFolder = path.join(__dirname, '..', 'extensions')
try {
const testPath = path.join(__dirname, 'command.test.js')
const exTester = new ExTester(storageFolder, ReleaseQuality.Stable, extFolder)
await exTester.downloadCode(version)
await exTester.installVsix({ useYarn: false })
// await exTester.installFromMarketplace("eliostruyf.vscode-front-matter");
await exTester.downloadChromeDriver(version)
// await exTester.setupRequirements({vscodeVersion: version});
const result = await exTester.runTests(testPath, {
vscodeVersion: version,
resources: [storageFolder],
})
process.exit(result)
} catch (err) {
console.log(err)
process.exit(1)
}
}
main()
+1
View File
@@ -0,0 +1 @@
export * from './sleep';
+3
View File
@@ -0,0 +1,3 @@
export async function sleep(time: number) {
await new Promise((resolve) => setTimeout(resolve, time));
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

+17158 -2043
View File
File diff suppressed because it is too large Load Diff
+727 -275
View File
File diff suppressed because it is too large Load Diff
+7614
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -1,9 +1,10 @@
const tailwindcss = require('tailwindcss');
module.exports = {
plugins: [
require('postcss-nested'),
tailwindcss('./tailwind.config.js'),
require('autoprefixer'),
],
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
}
};
+2 -2
View File
@@ -8,14 +8,14 @@ const version = packageJson.version.split('.');
packageJson.version = `${version[0]}.${version[1]}.${process.argv[process.argv.length-1].substr(0, 7)}`;
packageJson.preview = true;
packageJson.name = "vscode-front-matter-beta";
packageJson.displayName = `${packageJson.displayName} BETA`;
packageJson.displayName = `${packageJson.displayName} (BETA)`;
packageJson.description = `BETA Version of Front Matter. ${packageJson.description}`;
packageJson.icon = "assets/frontmatter-beta.png";
packageJson.homepage = "https://beta.frontmatter.codes";
console.log(packageJson.version);
core.summary.addHeading(`Version info`).addDetails(`${packageJson.version}`);
core.summary.addHeading(`Version info`).addRaw(`Version: ${packageJson.version}`).write();
const scripts = packageJson.scripts;
for (const key in scripts) {
+43
View File
@@ -0,0 +1,43 @@
const packageJson = require('../package.json');
for (const key of Object.keys(packageJson.contributes.configuration.properties)) {
const type = packageJson.contributes.configuration.properties[key].type;
if (type.includes('object') || type.includes('array')) {
console.log(`${key} - ${packageJson.contributes.configuration.properties[key].type}`);
}
}
// TO IGNORE
// frontMatter.extends - array
// frontMatter.dashboard.mediaSnippet - array
// TO PROCESS AS A WHOLE OBJECT
// frontMatter.content.draftField - object
// frontMatter.content.supportedFileTypes - array
// frontMatter.global.notifications - array
// frontMatter.global.disabledNotificaitons - array
// frontMatter.media.supportedMimeTypes - array
// frontMatter.taxonomy.commaSeparatedFields - array
// MERGE ARRAYS
// frontMatter.taxonomy.categories - array
// frontMatter.taxonomy.tags - array
// frontMatter.taxonomy.noPropertyValueQuotes - array
// PROCESS ITEM BY ITEM
// frontMatter.custom.scripts - array - id
// frontMatter.taxonomy.contentTypes - array,null - name
// frontMatter.data.files - array - id
// frontMatter.data.folders - array - id
// frontMatter.data.types - array - id
// frontMatter.content.pageFolders - array - path
// frontMatter.content.placeholders - array - id
// frontMatter.content.sorting - array - id
// frontMatter.global.modes - array - id
// frontMatter.taxonomy.fieldGroups - array - id
// frontMatter.taxonomy.customTaxonomy - array - id
// frontMatter.content.snippets - object
+134 -75
View File
@@ -1,9 +1,20 @@
import { Folders } from './Folders';
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
import { isValidFile } from './../helpers/isValidFile';
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants';
import {
SETTING_AUTO_UPDATE_DATE,
SETTING_SLUG_UPDATE_FILE_NAME,
SETTING_TEMPLATES_PREFIX,
CONFIG_KEY,
SETTING_DATE_FORMAT,
SETTING_SLUG_PREFIX,
SETTING_SLUG_SUFFIX,
SETTING_CONTENT_PLACEHOLDERS,
TelemetryEvent
} from './../constants';
import * as vscode from 'vscode';
import { Field, TaxonomyType } from "../models";
import { format } from "date-fns";
import { CustomPlaceholder, Field, TaxonomyType } from '../models';
import { format } from 'date-fns';
import { ArticleHelper, Settings, SlugHelper } from '../helpers';
import { Notifications } from '../helpers/Notifications';
import { extname, basename, parse, dirname } from 'path';
@@ -17,13 +28,12 @@ import { MediaListener } from '../listeners/panel';
import { NavigationType } from '../dashboardWebView/models';
import { processKnownPlaceholders } from '../helpers/PlaceholderHelper';
export class Article {
/**
* Insert taxonomy
*
* @param type
*/
* Insert taxonomy
*
* @param type
*/
public static async insert(type: TaxonomyType) {
const editor = vscode.window.activeTextEditor;
if (!editor) {
@@ -37,16 +47,21 @@ export class Article {
}
let options: vscode.QuickPickItem[] = [];
const matterProp: string = type === TaxonomyType.Tag ? "tags" : "categories";
const matterProp: string = type === TaxonomyType.Tag ? 'tags' : 'categories';
// Add the selected options to the options array
if (article.data[matterProp]) {
const propData = article.data[matterProp];
if (propData && propData.length > 0) {
options = [...propData].filter(p => p).map(p => ({
label: p,
picked: true
} as vscode.QuickPickItem));
options = [...propData]
.filter((p) => p)
.map(
(p) =>
({
label: p,
picked: true
} as vscode.QuickPickItem)
);
}
}
@@ -54,7 +69,7 @@ export class Article {
const crntOptions = Settings.getTaxonomy(type);
if (crntOptions && crntOptions.length > 0) {
for (const crntOpt of crntOptions) {
if (!options.find(o => o.label === crntOpt)) {
if (!options.find((o) => o.label === crntOpt)) {
options.push({
label: crntOpt
});
@@ -63,18 +78,18 @@ export class Article {
}
if (options.length === 0) {
Notifications.info(`No ${type === TaxonomyType.Tag ? "tags" : "categories"} configured.`);
Notifications.info(`No ${type === TaxonomyType.Tag ? 'tags' : 'categories'} configured.`);
return;
}
const selectedOptions = await vscode.window.showQuickPick(options, {
placeHolder: `Select your ${type === TaxonomyType.Tag ? "tags" : "categories"} to insert`,
placeHolder: `Select your ${type === TaxonomyType.Tag ? 'tags' : 'categories'} to insert`,
canPickMany: true,
ignoreFocusOut: true
});
if (selectedOptions) {
article.data[matterProp] = selectedOptions.map(o => o.label);
article.data[matterProp] = selectedOptions.map((o) => o.label);
}
ArticleHelper.update(editor, article);
@@ -94,21 +109,23 @@ export class Article {
return;
}
article = this.updateDate(article, true);
article = this.updateDate(article);
try {
ArticleHelper.update(editor, article);
} catch (e) {
Notifications.error(`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
Notifications.error(
`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`
);
}
}
/**
* Update the date in the front matter
* @param article
* @param article
*/
public static updateDate(article: ParsedFrontMatter, forceCreate: boolean = false) {
article.data = ArticleHelper.updateDates(article.data);
public static updateDate(article: ParsedFrontMatter) {
article.data = ArticleHelper.updateDates(article.data);
return article;
}
@@ -123,14 +140,11 @@ export class Article {
const updatedArticle = this.setLastModifiedDateInner(editor.document);
if (typeof updatedArticle === "undefined") {
if (typeof updatedArticle === 'undefined') {
return;
}
ArticleHelper.update(
editor,
updatedArticle as ParsedFrontMatter
);
ArticleHelper.update(editor, updatedArticle as ParsedFrontMatter);
}
public static async setLastModifiedDateOnSave(
@@ -138,7 +152,7 @@ export class Article {
): Promise<vscode.TextEdit[]> {
const updatedArticle = this.setLastModifiedDateInner(document);
if (typeof updatedArticle === "undefined") {
if (typeof updatedArticle === 'undefined') {
return [];
}
@@ -162,21 +176,43 @@ export class Article {
try {
cloneArticle.data[dateField] = Article.formatDate(new Date());
return cloneArticle;
} catch (e: any) {
Notifications.error(`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
} catch (e: unknown) {
Notifications.error(
`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`
);
}
}
/**
* Generate the new slug
*/
public static generateSlug(title: string) {
if (!title) {
return;
}
const prefix = Settings.get(SETTING_SLUG_PREFIX) as string;
const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string;
const slug = SlugHelper.createSlug(title);
if (slug) {
return {
slug,
slugWithPrefixAndSuffix: `${prefix}${slug}${suffix}`
};
}
return undefined;
}
/**
* Generate the slug based on the article title
*/
public static async generateSlug() {
Telemetry.send(TelemetryEvent.generateSlug);
const prefix = Settings.get(SETTING_SLUG_PREFIX) as string;
const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string;
public static async updateSlug() {
Telemetry.send(TelemetryEvent.generateSlug);
const updateFileName = Settings.get(SETTING_SLUG_UPDATE_FILE_NAME) as string;
const filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
const editor = vscode.window.activeTextEditor;
if (!editor) {
@@ -188,31 +224,41 @@ export class Article {
return;
}
let filePrefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
const contentType = ArticleHelper.getContentType(article.data);
const titleField = "title";
filePrefix = ArticleHelper.getFilePrefix(filePrefix, editor.document.uri.fsPath, contentType);
const titleField = 'title';
const articleTitle: string = article.data[titleField];
const slug = SlugHelper.createSlug(articleTitle);
if (slug) {
let slugFieldValue = `${prefix}${slug}${suffix}`;
article.data["slug"] = slugFieldValue;
const slugInfo = Article.generateSlug(articleTitle);
if (slugInfo && slugInfo.slug && slugInfo.slugWithPrefixAndSuffix) {
article.data['slug'] = slugInfo.slugWithPrefixAndSuffix;
if (contentType) {
// Update the fields containing the slug placeholder
let fieldsToUpdate: Field[] = contentType.fields.filter(f => f.default === "{{slug}}");
const fieldsToUpdate: Field[] = contentType.fields.filter((f) => f.default === '{{slug}}');
for (const field of fieldsToUpdate) {
article.data[field.name] = slug;
article.data[field.name] = slugInfo.slug;
}
// Update the fields containing a custom placeholder that depends on slug
const placeholders = Settings.get<{id: string, value: string}[]>(SETTING_CONTENT_PLACEHOLDERS);
const customPlaceholders = placeholders?.filter(p => p.value.includes("{{slug}}"));
const placeholders = Settings.get<CustomPlaceholder[]>(SETTING_CONTENT_PLACEHOLDERS);
const customPlaceholders = placeholders?.filter(
(p) => p.value && p.value.includes('{{slug}}')
);
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
for (const customPlaceholder of (customPlaceholders || [])) {
const customPlaceholderFields = contentType.fields.filter(f => f.default === `{{${customPlaceholder.id}}}`);
for (const customPlaceholder of customPlaceholders || []) {
const customPlaceholderFields = contentType.fields.filter(
(f) => f.default === `{{${customPlaceholder.id}}}`
);
for (const pField of customPlaceholderFields) {
article.data[pField.name] = customPlaceholder.value;
article.data[pField.name] = processKnownPlaceholders(article.data[pField.name], articleTitle, dateFormat);
article.data[pField.name] = processKnownPlaceholders(
article.data[pField.name],
articleTitle,
dateFormat
);
}
}
}
@@ -226,13 +272,13 @@ export class Article {
if (editor) {
const ext = extname(editor.document.fileName);
const fileName = basename(editor.document.fileName);
let slugName = slug.startsWith("/") ? slug.substring(1) : slug;
slugName = slugName.endsWith("/") ? slugName.substring(0, slugName.length - 1) : slugName;
let slugName = slugInfo.slug.startsWith('/') ? slugInfo.slug.substring(1) : slugInfo.slug;
slugName = slugName.endsWith('/') ? slugName.substring(0, slugName.length - 1) : slugName;
let newFileName = `${slugName}${ext}`;
if (filePrefix && typeof filePrefix === "string") {
newFileName = `${format(new Date(), DateHelper.formatUpdate(filePrefix) as string)}-${newFileName}`;
if (filePrefix && typeof filePrefix === 'string') {
newFileName = `${filePrefix}-${newFileName}`;
}
const newPath = editor.document.uri.fsPath.replace(fileName, newFileName);
@@ -243,13 +289,13 @@ export class Article {
await vscode.workspace.fs.rename(editor.document.uri, vscode.Uri.file(newPath), {
overwrite: false
});
} catch (e: any) {
Notifications.error(`Failed to rename file: ${e?.message || e}`);
} catch (e: unknown) {
Notifications.error(`Failed to rename file: ${(e as Error).message || e}`);
}
}
}
}
}
}
/**
* Retrieve the slug from the front matter
@@ -268,7 +314,7 @@ export class Article {
const parsedFile = parse(file);
if (parsedFile.name.toLowerCase() !== "index") {
if (parsedFile.name.toLowerCase() !== 'index') {
return parsedFile.name;
}
@@ -289,8 +335,8 @@ export class Article {
return;
}
const newDraftStatus = !article.data["draft"];
article.data["draft"] = newDraftStatus;
const newDraftStatus = !article.data['draft'];
article.data['draft'] = newDraftStatus;
ArticleHelper.update(editor, article);
}
@@ -303,6 +349,14 @@ export class Article {
if (document && ArticleHelper.isSupportedFile(document)) {
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
// Is article located in one of the content folders
const folders = Folders.get();
const documentPath = parseWinPath(document.fileName);
const folder = folders.find((f) => documentPath.startsWith(f.path));
if (!folder) {
return;
}
if (autoUpdate) {
event.waitUntil(Article.setLastModifiedDateOnSave(document));
}
@@ -315,10 +369,12 @@ export class Article {
public static formatDate(dateValue: Date): string {
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
if (dateFormat && typeof dateFormat === "string") {
if (dateFormat && typeof dateFormat === 'string') {
return format(dateValue, DateHelper.formatUpdate(dateFormat) as string);
} else {
return typeof dateValue.toISOString === 'function' ? dateValue.toISOString() : dateValue?.toString();
return typeof dateValue.toISOString === 'function'
? dateValue.toISOString()
: dateValue?.toString();
}
}
@@ -326,35 +382,38 @@ export class Article {
* Insert an image from the media dashboard into the article
*/
public static async insertMedia() {
let editor = vscode.window.activeTextEditor;
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
const contentType = article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
const contentType =
article && article.data ? ArticleHelper.getContentType(article.data) : DEFAULT_CONTENT_TYPE;
const position = editor.selection.active;
const selectionText = editor.document.getText(editor.selection);
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
type: "media",
type: 'media',
data: {
pageBundle: !!contentType.pageBundle,
filePath: editor.document.uri.fsPath,
fieldName: basename(editor.document.uri.fsPath),
position
position,
selection: selectionText
}
} as DashboardData);
// Let the editor panel know you are selecting an image
MediaListener.getMediaSelection();
}
}
/**
* Insert a snippet into the article
*/
public static async insertSnippet() {
let editor = vscode.window.activeTextEditor;
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
@@ -367,24 +426,24 @@ export class Article {
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
type: NavigationType.Snippets,
data: {
fileTitle: article?.data.title || "",
fileTitle: article?.data.title || '',
filePath: editor.document.uri.fsPath,
fieldName: basename(editor.document.uri.fsPath),
position,
selection: selectionText
}
} as DashboardData);
}
}
/**
* Update the article date and return it
* @param article
* @param dateFormat
* @param field
* @param forceCreate
* @param article
* @param dateFormat
* @param field
* @param forceCreate
*/
private static articleDate(article: ParsedFrontMatter, field: string, forceCreate: boolean) {
if (typeof article.data[field] !== "undefined" || forceCreate) {
if (typeof article.data[field] !== 'undefined' || forceCreate) {
article.data[field] = Article.formatDate(new Date());
}
return article;
+12 -10
View File
@@ -1,8 +1,8 @@
import { commands, ExtensionContext } from 'vscode';
import { CONTEXT } from '../constants';
import { COMMAND_NAME, CONTEXT } from '../constants';
import { Extension } from '../helpers';
import { Credentials } from "../services/Credentials";
import fetch from "node-fetch";
import { Credentials } from '../services/Credentials';
import fetch from 'node-fetch';
import { ExplorerView } from '../explorerView/ExplorerView';
import { Dashboard } from './Dashboard';
import { SettingsListener } from '../listeners/panel';
@@ -17,7 +17,7 @@ export class Backers {
Backers.tryUsernameCheck();
context.subscriptions.push(
commands.registerCommand('frontMatter.authenticate', async () => {
commands.registerCommand(COMMAND_NAME.authenticate, async () => {
Backers.tryUsernameCheck();
})
);
@@ -26,16 +26,16 @@ export class Backers {
public static async tryUsernameCheck() {
try {
const username = await Backers.getUsername();
Backers.validate(username || "");
Backers.validate(username || '');
} catch (e) {
Backers.validate("");
Backers.validate('');
}
}
public static async getUsername() {
const octokit = await Backers.creds?.getOctokit();
const user = await octokit?.users.getAuthenticated();
if (user?.data?.login) {
return user?.data?.login;
}
@@ -52,7 +52,9 @@ export class Backers {
const isBeta = ext.isBetaVersion();
const response = await fetch(`https://${isBeta ? `beta.` : ``}frontmatter.codes/api/backers?backer=${username}`);
const response = await fetch(
`https://${isBeta ? `beta.` : ``}frontmatter.codes/api/backers?backer=${username}`
);
if (response.ok) {
const prevData = await ext.getState<boolean>(CONTEXT.backer, 'global');
@@ -63,7 +65,7 @@ export class Backers {
if (explorerView.visible) {
SettingsListener.getSettings();
}
if (Dashboard.isOpen) {
Dashboard.reload();
}
@@ -72,4 +74,4 @@ export class Backers {
ext.setState(CONTEXT.backer, false, 'global');
}
}
}
}
+34
View File
@@ -0,0 +1,34 @@
import { commands } from 'vscode';
import { COMMAND_NAME, ExtensionState } from '../constants';
import { Extension, Notifications } from '../helpers';
export class Cache {
public static async registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.clearCache, Cache.clear));
}
public static async get<T>(key: string, type: 'workspace' | 'global'): Promise<T | undefined> {
const ext = Extension.getInstance();
const cache = await ext.getState<T>(key, type);
return cache || undefined;
}
public static async set(key: string, data: unknown, type: 'workspace' | 'global' = 'workspace') {
await Extension.getInstance().setState(key, data, type);
}
public static async clear(showNotification: boolean = true) {
const ext = Extension.getInstance();
await ext.setState(ExtensionState.Dashboard.Pages.Cache, undefined, 'workspace', true);
await ext.setState(ExtensionState.Dashboard.Pages.Index, undefined, 'workspace', true);
await ext.setState(ExtensionState.Settings.Extends, undefined, 'workspace', true);
if (showNotification) {
Notifications.info('Cache cleared');
}
}
}
+105
View File
@@ -0,0 +1,105 @@
import { Telemetry } from './../helpers/Telemetry';
import { TelemetryEvent, PreviewCommands, SETTING_EXPERIMENTAL } from './../constants';
import { join } from 'path';
import { commands, Uri, ViewColumn, window } from 'vscode';
import { Extension, Settings } from '../helpers';
import { WebviewHelper } from '@estruyf/vscode';
export class Chatbot {
/**
* Open the Chatbot in the editor
*/
public static async open(extensionPath: string) {
// Create the preview webview
const webView = window.createWebviewPanel(
'frontMatterChatbot',
'Front Matter AI - Ask me anything',
{
viewColumn: ViewColumn.Beside,
preserveFocus: true
},
{
enableScripts: true
}
);
webView.iconPath = {
dark: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-dark.svg')),
light: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-light.svg'))
};
const cspSource = webView.webview.cspSource;
webView.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case PreviewCommands.toVSCode.open:
if (message.data) {
commands.executeCommand('vscode.open', message.data);
}
return;
}
});
const dashboardFile = 'dashboardWebView.js';
const localPort = `9000`;
const localServerUrl = `localhost:${localPort}`;
const nonce = WebviewHelper.getNonce();
const ext = Extension.getInstance();
const isProd = ext.isProductionMode;
const version = ext.getVersion();
const isBeta = ext.isBetaVersion();
const extensionUri = ext.extensionPath;
const csp = [
`default-src 'none';`,
`img-src ${cspSource} http: https:;`,
`script-src ${
isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`
} 'unsafe-eval'`,
`style-src ${cspSource} 'self' 'unsafe-inline' http: https:`,
`connect-src https://* ${
isProd
? ``
: `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`
}`
];
let scriptUri = '';
if (isProd) {
scriptUri = webView.webview
.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile))
.toString();
} else {
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
}
// By default, the chatbot is seen as experimental
const experimental = true;
webView.webview.html = `
<!DOCTYPE html>
<html lang="en" style="width:100%;height:100%;margin:0;padding:0;">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="${csp.join('; ')}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Front Matter Docs Chatbot</title>
</head>
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden">
<div id="app" data-type="chatbot" data-isProd="${isProd}" data-environment="${
isBeta ? 'BETA' : 'main'
}" data-version="${version.usedVersion}" ${
experimental ? `data-experimental="${experimental}"` : ''
} style="width:100%;height:100%;margin:0;padding:0;"></div>
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
</body>
</html>
`;
Telemetry.send(TelemetryEvent.openChatbot);
}
}
+14 -10
View File
@@ -1,24 +1,28 @@
import { commands, QuickPickItem, window } from 'vscode';
import { commands, QuickPickItem, window } from 'vscode';
import { COMMAND_NAME, SETTING_TEMPLATES_ENABLED } from '../constants';
import { Settings } from '../helpers';
export class Content {
public static async create() {
const templatesEnabled = await Settings.get(SETTING_TEMPLATES_ENABLED);
if (!templatesEnabled) {
commands.executeCommand(COMMAND_NAME.createByContentType);
return;
}
const options: QuickPickItem[] = [{
label: "Create content by content type",
description: "Select if you want to create new content by the available content type(s)"
}, {
label: "Create content by template",
description: "Select if you want to create new content by the available template(s)"
} as QuickPickItem];
const options: QuickPickItem[] = [
{
label: 'Create content by content type',
description: 'Select if you want to create new content by the available content type(s)'
},
{
label: 'Create content by template',
description: 'Select if you want to create new content by the available template(s)'
} as QuickPickItem
];
const selectedOption = await window.showQuickPick(options, {
title: 'Create content',
placeHolder: `Select how you want to create your new content`,
canPickMany: false,
ignoreFocusOut: true
@@ -34,4 +38,4 @@ export class Content {
return;
}
}
}
+113 -40
View File
@@ -1,20 +1,37 @@
import { SETTING_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
import { join } from "path";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode";
import {
SETTING_DASHBOARD_OPENONSTART,
CONTEXT,
ExtensionState,
SETTING_EXPERIMENTAL,
SETTING_EXTENSIBILITY_SCRIPTS
} from '../constants';
import { join } from 'path';
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace } from 'vscode';
import { Logger, Settings as SettingsHelper } from '../helpers';
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { Extension } from '../helpers/Extension';
import { WebviewHelper } from '@estruyf/vscode';
import { DashboardData } from '../models/DashboardData';
import { MediaLibrary } from '../helpers/MediaLibrary';
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener } from '../listeners/dashboard';
import { MediaListener as PanelMediaListener } from '../listeners/panel'
import { ModeListener } from '../listeners/general';
import {
DashboardListener,
MediaListener,
SettingsListener,
TelemetryListener,
DataListener,
PagesListener,
ExtensionListener,
SnippetListener,
TaxonomyListener,
LogListener
} from '../listeners/dashboard';
import { MediaListener as PanelMediaListener } from '../listeners/panel';
import { GitListener, ModeListener } from '../listeners/general';
import { Folders } from './Folders';
export class Dashboard {
private static webview: WebviewPanel | null = null;
private static _viewData: DashboardData | undefined;
private static isDisposed: boolean = true;
private static isDisposed = true;
public static get viewData(): DashboardData | undefined {
return Dashboard._viewData;
@@ -34,15 +51,13 @@ export class Dashboard {
* Open or reveal the dashboard
*/
public static async open(data?: DashboardData) {
MediaLibrary.getInstance();
Dashboard._viewData = data;
if (Dashboard.isOpen) {
Dashboard.reveal(!!data);
} else {
Dashboard.create();
}
Dashboard.reveal(!!data);
} else {
Dashboard.create();
}
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, true);
}
@@ -57,12 +72,15 @@ export class Dashboard {
/**
* Reveal the dashboard if it is open
*/
public static reveal(hasData: boolean = false) {
public static reveal(hasData = false) {
if (Dashboard.webview) {
Dashboard.webview.reveal();
if (hasData) {
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: Dashboard.viewData });
Dashboard.postWebviewMessage({
command: DashboardCommand.viewData,
payload: Dashboard.viewData
});
}
}
}
@@ -74,6 +92,11 @@ export class Dashboard {
public static reload() {
if (Dashboard.isOpen) {
Dashboard.webview?.dispose();
Extension.getInstance().setState(
ExtensionState.Dashboard.Pages.Cache,
undefined,
'workspace'
);
setTimeout(() => {
Dashboard.open();
@@ -84,7 +107,7 @@ export class Dashboard {
public static resetViewData() {
Dashboard._viewData = undefined;
}
/**
* Create the dashboard webview
*/
@@ -98,7 +121,8 @@ export class Dashboard {
ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true
retainContextWhenHidden: true,
enableCommandUris: true
}
);
@@ -109,14 +133,20 @@ export class Dashboard {
light: Uri.file(join(extensionUri.fsPath, 'assets/icons/frontmatter-short-light.svg'))
};
Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, extensionUri);
Dashboard.webview.webview.html = Dashboard.getWebviewContent(
Dashboard.webview.webview,
extensionUri
);
Dashboard.webview.onDidChangeViewState(async () => {
if (!this.webview?.visible) {
Dashboard._viewData = undefined;
PanelMediaListener.getMediaSelection();
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: null });
Dashboard.postWebviewMessage({
command: DashboardCommand.viewData,
payload: null
});
}
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, this.webview?.visible);
@@ -129,13 +159,13 @@ export class Dashboard {
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false);
});
SettingsHelper.onConfigChange((global?: any) => {
SettingsListener.getSettings();
SettingsHelper.onConfigChange(() => {
SettingsListener.getSettings(true);
});
Dashboard.webview.webview.onDidReceiveMessage(async (msg) => {
Logger.info(`Receiving message from webview: ${msg.command}`);
DashboardListener.process(msg);
ExtensionListener.process(msg);
MediaListener.process(msg);
@@ -145,6 +175,9 @@ export class Dashboard {
TelemetryListener.process(msg);
SnippetListener.process(msg);
ModeListener.process(msg);
GitListener.process(msg);
TaxonomyListener.process(msg);
LogListener.process(msg);
});
}
@@ -158,9 +191,9 @@ export class Dashboard {
/**
* Post data to the dashboard
* @param msg
* @param msg
*/
public static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) {
public static postWebviewMessage(msg: { command: DashboardCommand; payload?: unknown }) {
if (Dashboard.isDisposed) {
return;
}
@@ -169,22 +202,24 @@ export class Dashboard {
Dashboard.webview?.webview.postMessage(msg);
}
}
/**
* Retrieve the webview HTML contents
* @param webView
* @param webView
*/
private static getWebviewContent(webView: Webview, extensionPath: Uri): string {
const dashboardFile = "dashboardWebView.js";
const dashboardFile = 'dashboardWebView.js';
const localPort = `9000`;
const localServerUrl = `localhost:${localPort}`;
let scriptUri = "";
let scriptUri = '';
const isProd = Extension.getInstance().isProductionMode;
if (isProd) {
scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile)).toString();
scriptUri = webView
.asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile))
.toString();
} else {
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
}
const nonce = WebviewHelper.getNonce();
@@ -193,13 +228,41 @@ export class Dashboard {
const version = ext.getVersion();
const isBeta = ext.isBetaVersion();
// Get experimental setting
const experimental = SettingsHelper.get(SETTING_EXPERIMENTAL);
const extensibilityScripts = SettingsHelper.get<string[]>(SETTING_EXTENSIBILITY_SCRIPTS) || [];
const scriptsToLoad: string[] = [];
if (experimental) {
for (const script of extensibilityScripts) {
if (script.startsWith('https://')) {
scriptsToLoad.push(script);
} else {
const absScriptPath = Folders.getAbsFilePath(script);
const scriptUri = webView.asWebviewUri(Uri.file(absScriptPath));
scriptsToLoad.push(scriptUri.toString());
}
}
}
const csp = [
`default-src 'none';`,
`img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'`,
`script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`} 'unsafe-eval'`,
`style-src ${webView.cspSource} 'self' 'unsafe-inline'`,
`img-src ${`vscode-file://vscode-app`} ${
webView.cspSource
} https://api.visitorbadge.io 'self' 'unsafe-inline' https://*`,
`media-src ${`vscode-file://vscode-app`} ${
webView.cspSource
} 'self' 'unsafe-inline' https://*`,
`script-src ${
isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`
} 'unsafe-eval' https://*`,
`style-src ${webView.cspSource} 'self' 'unsafe-inline' https://*`,
`font-src ${webView.cspSource}`,
`connect-src https://o1022172.ingest.sentry.io ${isProd ? `` : `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`}`
`connect-src https://o1022172.ingest.sentry.io https://* ${
isProd
? ``
: `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`
}`
];
return `
@@ -212,14 +275,24 @@ export class Dashboard {
<title>Front Matter Dashboard</title>
</head>
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden" class="bg-gray-100 text-vulcan-500 dark:bg-vulcan-500 dark:text-whisper-500">
<div id="app" data-isProd="${isProd}" data-environment="${isBeta ? "BETA" : "main"}" data-version="${version.usedVersion}" style="width:100%;height:100%;margin:0;padding:0;" ${version.usedVersion ? "" : `data-showWelcome="true"`}></div>
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden">
<div id="app" class="bg-gray-100 text-vulcan-500 dark:bg-vulcan-500 dark:text-whisper-500" data-isProd="${isProd}" data-environment="${
isBeta ? 'BETA' : 'main'
}" data-version="${version.usedVersion}" style="width:100%;height:100%;margin:0;padding:0;" ${
version.usedVersion ? '' : `data-showWelcome="true"`
} ${experimental ? `data-experimental="${experimental}"` : ''} ></div>
${(scriptsToLoad || [])
.map((script) => {
return `<script type="module" src="${script}" nonce="${nonce}"></script>`;
})
.join('')}
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
<img style="display:none" src="https://api.visitorbadge.io/api/combined?user=estruyf&repo=frontmatter-usage&countColor=%23263759&slug=${`dashboard-${version.installedVersion}`}" alt="Daily usage" />
<script ${isProd ? `nonce="${nonce}"` : ""} src="${scriptUri}"></script>
</body>
</html>
`;
}
}
}
+35 -18
View File
@@ -1,12 +1,11 @@
import { Folders } from "./Folders";
import { ViewColumn, workspace } from "vscode";
import ContentProvider from "../providers/ContentProvider";
import { join } from "path";
import { ContentFolder } from "../models";
import { Folders } from './Folders';
import { ViewColumn, workspace } from 'vscode';
import ContentProvider from '../providers/ContentProvider';
import { join } from 'path';
import { ContentFolder } from '../models';
import { Settings } from '../helpers/SettingsHelper';
export class Diagnostics {
public static async show() {
const folders = Folders.get();
const projectName = Folders.getProjectFolderName();
@@ -25,11 +24,11 @@ ${projectName}
# Folders
${folders.map(f => `- ${f.title}: "${f.path}"`).join("\n")}
${folders.map((f) => `- ${f.title}: "${f.path}"`).join('\n')}
# Workspace folder
${wsFolder ? wsFolder.fsPath : "No workspace folder"}
${wsFolder ? wsFolder.fsPath : 'No workspace folder'}
# Total files
@@ -37,10 +36,16 @@ ${all}
# Folders to search files
${folderData.join("\n")}
${folderData.join('\n')}
# Complete frontmatter.json config
\`\`\`json
${JSON.stringify(Settings.globalConfig, null, 2)}
\`\`\`
`;
ContentProvider.show(logging, `${projectName} diagnostics`, "markdown", ViewColumn.One);
ContentProvider.show(logging, `${projectName} diagnostics`, 'markdown', ViewColumn.One);
}
private static async allProjectFiles() {
@@ -50,14 +55,26 @@ ${folderData.join("\n")}
private static async processFolder(folder: ContentFolder, projectName: string) {
let projectStart = folder.path.split(projectName).pop();
projectStart = projectStart || "";
projectStart = projectStart || '';
projectStart = projectStart?.replace(/\\/g, '/');
projectStart = projectStart?.startsWith('/') ? projectStart.substr(1) : projectStart;
projectStart = projectStart?.startsWith('/') ? projectStart.substring(1) : projectStart;
const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md'));
const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx'));
const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown'));
const mdFiles = await workspace.findFiles(
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md')
);
const mdxFiles = await workspace.findFiles(
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx')
);
const markdownFiles = await workspace.findFiles(
join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown')
);
return `- Project start length: ${projectStart.length} | Search in: "${join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.*')}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${markdownFiles.length}`;
return `- Project start length: ${projectStart.length} | Search in: "${join(
projectStart,
folder.excludeSubdir ? '/' : '**/',
'*.*'
)}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${
markdownFiles.length
}`;
}
}
}
+283 -123
View File
@@ -1,13 +1,20 @@
import { STATIC_FOLDER_PLACEHOLDER } from './../constants/StaticFolderPlaceholder';
import { Questions } from './../helpers/Questions';
import { SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_STATIC_FOLDER, SETTING_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants';
import { commands, Uri, workspace, window } from "vscode";
import { basename, dirname, join, relative, sep } from "path";
import { ContentFolder, FileInfo, FolderInfo } from "../models";
import uniqBy = require("lodash.uniqby");
import { Template } from "./Template";
import { Notifications } from "../helpers/Notifications";
import { Logger, Settings } from "../helpers";
import { existsSync, mkdirSync } from 'fs';
import {
SETTING_CONTENT_PAGE_FOLDERS,
SETTING_CONTENT_STATIC_FOLDER,
SETTING_CONTENT_SUPPORTED_FILETYPES,
SETTING_DATE_FORMAT,
TelemetryEvent
} from './../constants';
import { commands, Uri, workspace, window } from 'vscode';
import { basename, dirname, join, relative, sep } from 'path';
import { ContentFolder, FileInfo, FolderInfo } from '../models';
import uniqBy = require('lodash.uniqby');
import { Template } from './Template';
import { Notifications } from '../helpers/Notifications';
import { Logger, processKnownPlaceholders, Settings } from '../helpers';
import { existsSync } from 'fs';
import { format } from 'date-fns';
import { Dashboard } from './Dashboard';
import { parseWinPath } from '../helpers/parseWinPath';
@@ -16,32 +23,41 @@ import { MediaListener, PagesListener, SettingsListener } from '../listeners/das
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
import { Telemetry } from '../helpers/Telemetry';
import { glob } from 'glob';
import { mkdirAsync } from '../utils/mkdirAsync';
import { existsAsync } from '../utils';
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
export class Folders {
/**
* Add a media folder
* @returns
* @returns
*/
public static async addMediaFolder(data?: {selectedFolder?: string}) {
let wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
public static async addMediaFolder(data?: { selectedFolder?: string }) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Folders.getStaticFolderRelativePath();
let startPath = "";
let startPath = '';
if (data?.selectedFolder) {
startPath = data.selectedFolder.replace(parseWinPath(wsFolder?.fsPath || ""), "");
startPath = data.selectedFolder.replace(parseWinPath(wsFolder?.fsPath || ''), '');
} else if (staticFolder) {
startPath = `/${staticFolder}`;
}
if (startPath && !startPath.endsWith("/")) {
startPath += "/";
if (startPath && !startPath.endsWith('/')) {
startPath += '/';
}
const folderName = await window.showInputBox({
if (startPath.includes(STATIC_FOLDER_PLACEHOLDER.hexo.placeholder)) {
startPath = startPath.replace(
STATIC_FOLDER_PLACEHOLDER.hexo.placeholder,
STATIC_FOLDER_PLACEHOLDER.hexo.postsFolder
);
}
const folderName = await window.showInputBox({
title: `Add media folder`,
prompt: `Which name would you like to give to your folder (use "/" to create multi-level folders)?`,
value: startPath,
ignoreFocusOut: true,
@@ -52,23 +68,18 @@ export class Folders {
Notifications.warning(`No folder name was specified.`);
return;
}
const folders = folderName.split("/").filter(f => f);
let parentFolders: string[] = [];
for (const folder of folders) {
const folderPath = join(parseWinPath(wsFolder?.fsPath || ""), parentFolders.join("/"), folder);
await Folders.createFolder(join(parseWinPath(wsFolder?.fsPath || ''), folderName));
}
parentFolders.push(folder);
if (!existsSync(folderPath)) {
mkdirSync(folderPath);
}
public static async createFolder(folderPath: string) {
if (!(await existsAsync(folderPath))) {
await mkdirAsync(folderPath, { recursive: true });
}
if (Dashboard.isOpen) {
MediaHelpers.resetMedia();
MediaListener.sendMediaFiles(0, folderName);
MediaListener.sendMediaFiles(0, folderPath);
}
Telemetry.send(TelemetryEvent.addMediaFolder);
@@ -76,7 +87,7 @@ export class Folders {
/**
* Create content in a registered folder
* @returns
* @returns
*/
public static async create() {
const selectedFolder = await Questions.SelectContentFolder();
@@ -85,7 +96,7 @@ export class Folders {
}
const folders = Folders.get();
const location = folders.find(f => f.title === selectedFolder);
const location = folders.find((f) => f.title === selectedFolder);
if (location) {
const folderPath = Folders.getFolderPath(Uri.file(location.path));
if (folderPath) {
@@ -96,9 +107,9 @@ export class Folders {
/**
* Register the new folder path
* @param folder
* @param folderInfo
*/
public static async register(folderInfo: { title: string, path: Uri } | Uri) {
public static async register(folderInfo: { title: string; path: Uri } | Uri) {
let folderName = folderInfo instanceof Uri ? undefined : folderInfo.title;
const folder = folderInfo instanceof Uri ? folderInfo : folderInfo.path;
@@ -107,7 +118,9 @@ export class Folders {
let folders = Folders.get();
const exists = folders.find(f => f.path.includes(folder.fsPath) || f.path.includes(wslPath));
const exists = folders.find(
(f) => f.path.includes(folder.fsPath) || f.path.includes(wslPath)
);
if (exists) {
Notifications.warning(`Folder is already registered`);
@@ -115,7 +128,8 @@ export class Folders {
}
if (!folderName) {
folderName = await window.showInputBox({
folderName = await window.showInputBox({
title: `Register folder`,
prompt: `Which name would you like to specify for this folder?`,
placeHolder: `Folder name`,
value: basename(folder.fsPath),
@@ -128,44 +142,71 @@ export class Folders {
path: folder.fsPath
} as ContentFolder);
folders = uniqBy(folders, f => f.path);
folders = uniqBy(folders, (f) => f.path);
await Folders.update(folders);
Notifications.info(`Folder registered`);
Telemetry.send(TelemetryEvent.registerFolder);
Telemetry.send(TelemetryEvent.registerFolder);
SettingsListener.getSettings();
SettingsListener.getSettings(true);
}
}
/**
* Unregister a folder path
* @param folder
* @param folder
*/
public static async unregister(folder: Uri) {
if (folder && folder.path) {
let folders = Folders.get();
folders = folders.filter(f => f.path !== folder.fsPath);
folders = folders.filter((f) => f.path !== folder.fsPath);
await Folders.update(folders);
Telemetry.send(TelemetryEvent.unregisterFolder);
Telemetry.send(TelemetryEvent.unregisterFolder);
}
}
/**
* Get the static folder its relative path
* @returns
*/
public static getStaticFolderRelativePath(): string | undefined {
let staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
if (
staticFolder &&
(staticFolder.includes(WORKSPACE_PLACEHOLDER) ||
staticFolder === '/' ||
staticFolder === './')
) {
staticFolder =
staticFolder === '/' || staticFolder === './'
? Folders.getAbsFilePath('[[workspace]]')
: Folders.getAbsFilePath(staticFolder);
const wsFolder = Folders.getWorkspaceFolder();
if (wsFolder) {
const relativePath = relative(parseWinPath(wsFolder.fsPath), parseWinPath(staticFolder));
return relativePath === '' ? '/' : relativePath;
}
}
return staticFolder;
}
/**
* Retrieve the folder path
* @param folder
* @returns
* @param folder
* @returns
*/
public static getFolderPath(folder: Uri) {
let folderPath = "";
let folderPath = '';
const wsFolder = Folders.getWorkspaceFolder();
if (folder && folder.fsPath) {
folderPath = folder.fsPath;
} else if (wsFolder) {
folderPath = wsFolder.fsPath;
}
if (folder && folder.fsPath) {
folderPath = folder.fsPath;
} else if (wsFolder) {
folderPath = wsFolder.fsPath;
}
return folderPath;
}
@@ -174,12 +215,12 @@ export class Folders {
*/
public static getWorkspaceFolder(): Uri | undefined {
const folders = workspace.workspaceFolders;
if (folders && folders.length === 1) {
return folders[0].uri;
} else if (folders && folders.length > 1) {
let projectFolder = undefined;
let projectFolder = undefined;
for (const folder of folders) {
if (!projectFolder && existsSync(join(folder.uri.fsPath, Settings.globalFile))) {
projectFolder = folder.uri;
@@ -187,20 +228,22 @@ export class Folders {
}
if (!projectFolder) {
window.showWorkspaceFolderPick({
placeHolder: `Please select the main workspace folder for Front Matter to use.`
}).then(selectedFolder => {
if (selectedFolder) {
Settings.createGlobalFile(selectedFolder.uri);
// Full reload to make sure the whole extension is reloaded correctly
commands.executeCommand(`workbench.action.reloadWindow`);
}
});
window
.showWorkspaceFolderPick({
placeHolder: `Please select the main workspace folder for Front Matter to use.`
})
.then(async (selectedFolder) => {
if (selectedFolder) {
await Settings.createGlobalFile(selectedFolder.uri);
// Full reload to make sure the whole extension is reloaded correctly
commands.executeCommand(`workbench.action.reloadWindow`);
}
});
}
return projectFolder;
}
return undefined;
}
@@ -212,7 +255,7 @@ export class Folders {
if (wsFolder) {
return basename(wsFolder.fsPath);
}
return "";
return '';
}
/**
@@ -221,26 +264,40 @@ export class Folders {
public static async getInfo(limit?: number): Promise<FolderInfo[] | null> {
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES);
const folders = Folders.get();
const wsFolder = parseWinPath(Folders.getWorkspaceFolder()?.fsPath || '');
if (folders && folders.length > 0) {
let folderInfo: FolderInfo[] = [];
const folderInfo: FolderInfo[] = [];
for (const folder of folders) {
try {
const projectName = Folders.getProjectFolderName();
let projectStart = folder.path.split(projectName).pop();
if (projectStart) {
const folderPath = parseWinPath(folder.path);
let projectStart = parseWinPath(folder.path).replace(wsFolder, '');
if (typeof projectStart === 'string') {
projectStart = projectStart.replace(/\\/g, '/');
projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart;
projectStart = projectStart.startsWith('/') ? projectStart.substring(1) : projectStart;
let files: Uri[] = [];
for (const fileType of (supportedFiles || DEFAULT_FILE_TYPES)) {
const filePath = join(projectStart, folder.excludeSubdir ? '/' : '**', `*${fileType.startsWith('.') ? '' : '.'}${fileType}`);
const foundFiles = await workspace.findFiles(filePath, '**/node_modules/**');
for (const fileType of supportedFiles || DEFAULT_FILE_TYPES) {
let filePath = join(
projectStart,
folder.excludeSubdir ? '/' : '**',
`*${fileType.startsWith('.') ? '' : '.'}${fileType}`
);
if (projectStart === '' && folder.excludeSubdir) {
filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`;
}
let foundFiles = await workspace.findFiles(filePath, '**/node_modules/**');
// Make sure these file are coming from the folder path (this could be an issue in multi-root workspaces)
foundFiles = foundFiles.filter((f) => parseWinPath(f.fsPath).startsWith(folderPath));
files = [...files, ...foundFiles];
}
if (files) {
let fileStats: FileInfo[] = [];
@@ -248,7 +305,7 @@ export class Folders {
try {
const fileName = basename(file.fsPath);
const folderName = dirname(file.fsPath).split(sep).pop();
const stats = await workspace.fs.stat(file);
fileStats.push({
@@ -263,7 +320,7 @@ export class Folders {
}
fileStats = fileStats.sort((a, b) => b.mtime - a.mtime);
if (limit) {
fileStats = fileStats.slice(0, limit);
}
@@ -288,48 +345,72 @@ export class Folders {
/**
* Get the folder settings
* @returns
* @returns
*/
public static get(): ContentFolder[] {
const wsFolder = Folders.getWorkspaceFolder();
const folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
const contentFolders = folders.map(folder => {
const contentFolders = folders.map((folder) => {
if (!folder.title) {
folder.title = basename(folder.path);
}
let folderPath = Folders.absWsFolder(folder, wsFolder);
if (!existsSync(folderPath)) {
Notifications.errorShowOnce(`Folder "${folder.title} (${folder.path})" does not exist. Please remove it from the settings.`, "Remove folder").then(answer => {
if (answer === "Remove folder") {
let folders = Folders.get();
Folders.update(folders.filter(f => f.path !== folder.path));
}
});
return null;
let folderPath: string | undefined = Folders.absWsFolder(folder, wsFolder);
if (folderPath.includes(`{{`) && folderPath.includes(`}}`)) {
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
folderPath = processKnownPlaceholders(folderPath, undefined, dateFormat);
} else {
if (folderPath && !existsSync(folderPath)) {
Notifications.errorShowOnce(
`Folder "${folder.title} (${folder.path})" does not exist. Please remove it from the settings.`,
'Remove folder',
'Create folder'
).then((answer) => {
if (answer === 'Remove folder') {
const folders = Folders.get();
Folders.update(folders.filter((f) => f.path !== folder.path));
} else if (answer === 'Create folder') {
mkdirAsync(folderPath as string, { recursive: true });
}
});
return null;
}
}
return {
...folder,
originalPath: folder.path,
path: folderPath
}
})
return contentFolders.filter(folder => folder !== null) as ContentFolder[];
};
});
return contentFolders.filter((folder) => folder !== null) as ContentFolder[];
}
/**
* Update the folder settings
* @param folders
* @param folders
*/
public static async update(folders: ContentFolder[]) {
const wsFolder = Folders.getWorkspaceFolder();
let folderDetails = folders.map(folder => ({
...folder,
path: Folders.relWsFolder(folder, wsFolder)
}));
const folderDetails = folders
.map((folder) => {
const detail = {
...folder,
path: Folders.relWsFolder(folder, wsFolder)
};
if (detail['$schema'] || detail.extended) {
return null;
}
delete detail.originalPath;
return detail;
})
.filter((folder) => folder !== null);
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
@@ -339,39 +420,62 @@ export class Folders {
/**
* Retrieve the absolute file path
* @param filePath
* @returns
* @param filePath
* @returns
*/
public static getAbsFilePath(filePath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
const isWindows = process.platform === 'win32';
let absPath = filePath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ""));
let absPath = filePath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
return absPath;
return parseWinPath(absPath);
}
/**
* Retrieve the absolute folder path
* @param filePath
* @returns
*/
public static getAbsFolderPath(folderPath: string): string {
const wsFolder = Folders.getWorkspaceFolder();
const isWindows = process.platform === 'win32';
let absPath = '';
if (folderPath.includes(WORKSPACE_PLACEHOLDER)) {
absPath = folderPath.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
} else {
absPath = join(parseWinPath(wsFolder?.fsPath || ''), folderPath);
}
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
return parseWinPath(absPath);
}
/**
* Generate the absolute URL for the workspace
* @param folder
* @param wsFolder
* @returns
* @param folder
* @param wsFolder
* @returns
*/
private static absWsFolder(folder: ContentFolder, wsFolder?: Uri) {
const isWindows = process.platform === 'win32';
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ""));
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ''));
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
return absPath;
return parseWinPath(absPath);
}
/**
* Generate relative folder path
* @param folder
* @param wsFolder
* @returns
* @param folder
* @param wsFolder
* @returns
*/
public static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
const isWindows = process.platform === 'win32';
let absPath = parseWinPath(folder.path).replace(parseWinPath(wsFolder?.fsPath || ""), WORKSPACE_PLACEHOLDER);
let absPath = parseWinPath(folder.path).replace(
parseWinPath(wsFolder?.fsPath || ''),
WORKSPACE_PLACEHOLDER
);
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
return absPath;
}
@@ -382,36 +486,92 @@ export class Folders {
public static async getContentFolders() {
// Find folders that contain files
const wsFolder = Folders.getWorkspaceFolder();
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
const patterns = supportedFiles.map(fileType => `${join(parseWinPath(wsFolder?.fsPath || ""), "**", `*${fileType.startsWith('.') ? '' : '.'}${fileType}`)}`);
const supportedFiles =
Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
const patterns = supportedFiles.map(
(fileType) =>
`${join(
parseWinPath(wsFolder?.fsPath || ''),
'**',
`*${fileType.startsWith('.') ? '' : '.'}${fileType}`
)}`
);
let folders: string[] = [];
for (const pattern of patterns) {
try {
folders = [...folders, ...(await this.findFolders(pattern))];
} catch (e) {
Logger.error(`Something went wrong while searching for folders with pattern "${pattern}": ${(e as Error).message}`);
Logger.error(
`Something went wrong while searching for folders with pattern "${pattern}": ${
(e as Error).message
}`
);
}
}
// Filter out the workspace folder
if (wsFolder) {
folders = folders.filter(folder => folder !== wsFolder.fsPath);
folders = folders.filter((folder) => folder !== wsFolder.fsPath);
}
const uniqueFolders = [...new Set(folders)];
return uniqueFolders.map(folder => relative(wsFolder?.path || "", folder));
return uniqueFolders.map((folder) => relative(wsFolder?.path || '', folder));
}
/**
* Returns the file prefix for the given folder path
* @param folderPath
* @returns
*/
public static getFilePrefixByFolderPath(folderPath: string) {
const folders = Folders.get();
const pageFolder = folders.find((f) => parseWinPath(f.path) === parseWinPath(folderPath));
if (pageFolder && typeof pageFolder.filePrefix !== 'undefined') {
return pageFolder.filePrefix;
}
return;
}
/**
* Returns the file prefix for the given file path
* @param filePath
* @returns
*/
public static getFilePrefixBeFilePath(filePath: string) {
const folders = Folders.get();
if (folders.length > 0) {
filePath = parseWinPath(filePath);
let selectedFolder: ContentFolder | null = null;
for (const folder of folders) {
const folderPath = parseWinPath(folder.path);
if (filePath.startsWith(folderPath)) {
if (!selectedFolder || selectedFolder.path.length < folderPath.length) {
selectedFolder = folder;
}
}
}
if (selectedFolder && typeof selectedFolder.filePrefix !== 'undefined') {
return selectedFolder.filePrefix;
}
}
return;
}
/**
* Retrieve all content folders
* @param pattern
* @returns
* @param pattern
* @returns
*/
private static findFolders(pattern: string): Promise<string[]> {
return new Promise(resolve => {
glob(pattern, { ignore: "**/node_modules/**" }, (err, files) => {
const allFolders = files.map(file => dirname(file));
return new Promise((resolve) => {
glob(pattern, { ignore: '**/node_modules/**' }, (err, files) => {
const allFolders = files.map((file) => dirname(file));
const uniqueFolders = [...new Set(allFolders)];
resolve(uniqueFolders);
});
+160 -31
View File
@@ -1,27 +1,36 @@
import { processFmPlaceholders } from './../helpers/processFmPlaceholders';
import { processPathPlaceholders } from './../helpers/processPathPlaceholders';
import { Telemetry } from './../helpers/Telemetry';
import { SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME, CONTEXT, TelemetryEvent, PreviewCommands } from './../constants';
import {
SETTING_PREVIEW_HOST,
SETTING_PREVIEW_PATHNAME,
CONTEXT,
TelemetryEvent,
PreviewCommands,
SETTING_EXPERIMENTAL,
SETTING_DATE_FORMAT
} from './../constants';
import { ArticleHelper } from './../helpers/ArticleHelper';
import { join } from "path";
import { commands, env, Uri, ViewColumn, window } from "vscode";
import { Extension, Settings } from '../helpers';
import { PreviewSettings } from '../models';
import { join } from 'path';
import { commands, env, Uri, ViewColumn, window } from 'vscode';
import { Extension, parseWinPath, processKnownPlaceholders, Settings } from '../helpers';
import { ContentFolder, ContentType, PreviewSettings } from '../models';
import { format } from 'date-fns';
import { DateHelper } from '../helpers/DateHelper';
import { Article } from '.';
import { urlJoin } from 'url-join-ts';
import { WebviewHelper } from '@estruyf/vscode';
import { Folders } from './Folders';
export class Preview {
/** 
/**
* Init the preview
*/
public static async init() {
const settings = Preview.getSettings();
await commands.executeCommand('setContext', CONTEXT.canOpenPreview, !!settings.host);
}
/**
* Open the markdown preview in the editor
*/
@@ -31,14 +40,57 @@ export class Preview {
if (!settings.host) {
return;
}
const editor = window.activeTextEditor;
const article = editor ? ArticleHelper.getFrontMatter(editor) : null;
let slug = article?.data ? article.data.slug : "";
let slug = article?.data ? article.data.slug : '';
let pathname = settings.pathname;
let selectedFolder: ContentFolder | undefined | null = null;
const filePath = parseWinPath(editor?.document.uri.fsPath);
let contentType: ContentType | undefined = undefined;
if (article?.data) {
contentType = ArticleHelper.getContentType(article.data);
}
// Check if there is a pathname defined on content folder level
const folders = Folders.get();
if (folders.length > 0) {
const foldersWithPath = folders.filter((folder) => folder.previewPath);
for (const folder of foldersWithPath) {
const folderPath = parseWinPath(folder.path);
if (filePath.startsWith(folderPath)) {
if (!selectedFolder || selectedFolder.path.length < folderPath.length) {
selectedFolder = folder;
}
}
}
if (!selectedFolder && article?.data && contentType && !contentType.previewPath) {
// Try to find the folder by content type
const crntFolders = folders.filter((folder) =>
folder.contentTypes?.includes((contentType as ContentType).name)
);
if (crntFolders && crntFolders.length === 1) {
selectedFolder = crntFolders[0];
} else if (crntFolders && crntFolders.length > 1) {
selectedFolder = await Preview.askUserToPickFolder(crntFolders);
} else {
selectedFolder = await Preview.askUserToPickFolder(folders.filter((f) => f.previewPath));
}
}
if (selectedFolder && selectedFolder.previewPath) {
pathname = selectedFolder.previewPath;
}
}
// Check if there is a pathname defined on content type level
if (article?.data) {
const contentType = ArticleHelper.getContentType(article.data);
if (contentType && contentType.previewPath) {
pathname = contentType.previewPath;
}
@@ -49,19 +101,52 @@ export class Preview {
}
if (pathname) {
const articleDate = ArticleHelper.getDate(article);
// Known placeholders
const dateFormat = Settings.get(SETTING_DATE_FORMAT) as string;
pathname = processKnownPlaceholders(pathname, article?.data?.title, dateFormat);
// Custom placeholders
pathname = await ArticleHelper.processCustomPlaceholders(
pathname,
article?.data?.title,
filePath
);
// Process the path placeholders - {{pathToken.<integer>}}
if (filePath) {
const wsFolder = Folders.getWorkspaceFolder();
// Get relative file path
const folderPath = wsFolder ? parseWinPath(wsFolder.fsPath) : '';
const relativePath = filePath.replace(folderPath, '');
pathname = processPathPlaceholders(pathname, relativePath, filePath, selectedFolder);
}
// Support front matter placeholders - {{fm.<field>}}
pathname = processFmPlaceholders(pathname, article?.data);
try {
slug = join(format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string), slug);
const articleDate = ArticleHelper.getDate(article);
slug = join(
format(articleDate || new Date(), DateHelper.formatUpdate(pathname) as string),
slug
);
} catch (error) {
slug = join(pathname, slug);
}
}
// Make sure there are no backslashes in the slug
slug = parseWinPath(slug);
// Verify if the slug doesn't end with _index or index
if (slug.endsWith('_index') || slug.endsWith('index')) {
slug = slug.substring(0, slug.endsWith('_index') ? slug.length - 6 : slug.length - 5);
}
// Create the preview webview
const webView = window.createWebviewPanel(
'frontMatterPreview',
'FrontMatter Preview',
article?.data?.title ? `Preview: ${article?.data?.title}` : 'FrontMatter Preview',
{
viewColumn: ViewColumn.Beside,
preserveFocus: true
@@ -74,15 +159,14 @@ export class Preview {
webView.iconPath = {
dark: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-dark.svg')),
light: Uri.file(join(extensionPath, 'assets/icons/frontmatter-short-light.svg'))
}
};
const localhostUrl = await env.asExternalUri(
Uri.parse(settings.host)
);
const crntUrl = settings.host.startsWith('http') ? settings.host : `http://${settings.host}`;
const localhostUrl = await env.asExternalUri(Uri.parse(crntUrl));
const cspSource = webView.webview.cspSource;
webView.webview.onDidReceiveMessage(message => {
webView.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case PreviewCommands.toVSCode.open:
if (message.data) {
@@ -92,8 +176,7 @@ export class Preview {
}
});
const dashboardFile = "dashboardWebView.js";
const dashboardFile = 'dashboardWebView.js';
const localPort = `9000`;
const localServerUrl = `localhost:${localPort}`;
@@ -104,23 +187,34 @@ export class Preview {
const version = ext.getVersion();
const isBeta = ext.isBetaVersion();
const extensionUri = ext.extensionPath;
const csp = [
`default-src 'none';`,
`img-src ${localhostUrl} ${cspSource} http: https:;`,
`script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`} 'unsafe-eval'`,
`script-src ${
isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`
} 'unsafe-eval'`,
`style-src ${cspSource} 'self' 'unsafe-inline' http: https:`,
`connect-src https://o1022172.ingest.sentry.io ${isProd ? `` : `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`}`,
`frame-src ${localhostUrl} ${cspSource} http: https:;`,
`connect-src https://o1022172.ingest.sentry.io ${
isProd
? ``
: `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`
}`,
`frame-src ${localhostUrl} ${cspSource} http: https:;`
];
let scriptUri = "";
let scriptUri = '';
if (isProd) {
scriptUri = webView.webview.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile)).toString();
scriptUri = webView.webview
.asWebviewUri(Uri.joinPath(extensionUri, 'dist', dashboardFile))
.toString();
} else {
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
scriptUri = `http://${localServerUrl}/${dashboardFile}`;
}
// Get experimental setting
const experimental = Settings.get(SETTING_EXPERIMENTAL);
webView.webview.html = `
<!DOCTYPE html>
<html lang="en" style="width:100%;height:100%;margin:0;padding:0;">
@@ -132,9 +226,16 @@ export class Preview {
<title>Front Matter Preview</title>
</head>
<body style="width:100%;height:100%;margin:0;padding:0;overflow:hidden">
<div id="app" data-type="preview" data-url="${urlJoin(localhostUrl.toString(), slug || '')}" data-isProd="${isProd}" data-environment="${isBeta ? "BETA" : "main"}" data-version="${version.usedVersion}" style="width:100%;height:100%;margin:0;padding:0;"></div>
<div id="app" data-type="preview" data-url="${urlJoin(
localhostUrl.toString(),
slug || ''
)}" data-isProd="${isProd}" data-environment="${
isBeta ? 'BETA' : 'main'
}" data-version="${version.usedVersion}" ${
experimental ? `data-experimental="${experimental}"` : ''
} style="width:100%;height:100%;margin:0;padding:0;"></div>
<script ${isProd ? `nonce="${nonce}"` : ""} src="${scriptUri}"></script>
<script ${isProd ? `nonce="${nonce}"` : ''} src="${scriptUri}"></script>
</body>
</html>
`;
@@ -154,4 +255,32 @@ export class Preview {
pathname
};
}
/**
* Ask the user to select the folder of the article to preview
* @param crntFolders
* @returns
*/
private static async askUserToPickFolder(
crntFolders: ContentFolder[]
): Promise<ContentFolder | undefined> {
let selectedFolder: ContentFolder | undefined = undefined;
if (crntFolders.length === 0) {
return undefined;
}
// Ask the user to select the folder
const folderNames = crntFolders.map((folder) => folder.title);
const selectedFolderName = await window.showQuickPick(folderNames, {
canPickMany: false,
title: 'Select the folder of the article to preview'
});
if (selectedFolderName) {
selectedFolder = crntFolders.find((folder) => folder.title === selectedFolderName);
}
return selectedFolder;
}
}
+69 -25
View File
@@ -1,21 +1,26 @@
import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType';
import { Telemetry } from './../helpers/Telemetry';
import { workspace, Uri } from "vscode";
import { join } from "path";
import * as fs from "fs";
import { Notifications } from "../helpers/Notifications";
import { Template } from "./Template";
import { Folders } from "./Folders";
import { FrameworkDetector, Logger, Settings } from "../helpers";
import { SETTING_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants";
import { workspace, Uri, commands, window } from 'vscode';
import { join } from 'path';
import { Notifications } from '../helpers/Notifications';
import { Template } from './Template';
import { Folders } from './Folders';
import { Extension, FrameworkDetector, Logger, MediaLibrary, Settings } from '../helpers';
import {
COMMAND_NAME,
SETTING_CONTENT_DEFAULT_FILETYPE,
SETTING_TAXONOMY_CONTENT_TYPES,
TelemetryEvent
} from '../constants';
import { SettingsListener } from '../listeners/dashboard';
import { existsAsync, writeFileAsync } from '../utils';
export class Project {
private static content = `---
title:
slug:
description:
author:
author:
date: 2019-08-22T15:20:28.000Z
lastmod: 2019-08-22T15:20:28.000Z
draft: true
@@ -24,8 +29,20 @@ categories: []
---
`;
public static registerCommands() {
const ext = Extension.getInstance();
const subscriptions = ext.subscriptions;
subscriptions.push(commands.registerCommand(COMMAND_NAME.switchProject, Project.switchProject));
}
public static isInitialized() {
return Settings.hasProjectFile();
const hasProjectFile = Settings.hasProjectFile();
// If it has a project file, initialize the media library
if (hasProjectFile) {
MediaLibrary.getInstance();
}
return hasProjectFile;
}
/**
@@ -33,35 +50,60 @@ categories: []
*/
public static async init(sampleTemplate?: boolean) {
try {
Settings.createTeamSettings();
await Settings.createTeamSettings();
// Add the default content type
await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true);
if (sampleTemplate !== undefined) {
await Project.createSampleTemplate();
} else {
Notifications.info("Project initialized successfully.");
Notifications.info('Project initialized successfully.');
}
// Initialize the media library
MediaLibrary.getInstance();
Telemetry.send(TelemetryEvent.initialization);
// Check if you can find the framework
const wsFolder = Folders.getWorkspaceFolder();
const framework = FrameworkDetector.get(wsFolder?.fsPath || "");
const framework = await FrameworkDetector.get(wsFolder?.fsPath || '');
if (framework) {
SettingsListener.setFramework(framework.name);
await SettingsListener.setFramework(framework.name);
}
SettingsListener.getSettings();
} catch (err: any) {
SettingsListener.getSettings(true);
} catch (error: unknown) {
const err = error as Error;
Logger.error(`Project::init: ${err?.message || err}`);
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
}
}
public static async switchProject() {
const projects = Settings.getProjects();
const project = await window.showQuickPick(
projects.map((p) => p.name),
{
canPickMany: false,
ignoreFocusOut: true,
title: 'Select a project to switch to'
}
);
if (!project) {
return;
}
SettingsListener.switchProject(project);
}
/**
* Creates the templates folder + sample if needed
* @param sampleTemplate
* @returns
* @param sampleTemplate
* @returns
*/
public static async createSampleTemplate(sampleTemplate?: boolean) {
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
@@ -72,16 +114,18 @@ categories: []
if (!folder || !templatePath) {
return;
}
const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`));
if (!fs.existsSync(templatePath.fsPath)) {
if (!(await existsAsync(templatePath.fsPath))) {
await workspace.fs.createDirectory(templatePath);
}
if (sampleTemplate) {
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
Notifications.info("Sample template created.");
await writeFileAsync(article.fsPath, Project.content, {
encoding: 'utf-8'
});
Notifications.info('Sample template created.');
}
}
@@ -99,4 +143,4 @@ categories: []
const templatePath = Uri.file(join(wsFolder.fsPath, folder));
return templatePath;
}
}
}
+116 -164
View File
@@ -1,35 +1,38 @@
import { TaxonomyHelper } from './../helpers/TaxonomyHelper';
import * as vscode from 'vscode';
import * as fs from 'fs';
import { TaxonomyType } from "../models";
import { TaxonomyType } from '../models';
import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, EXTENSION_NAME } from '../constants';
import { ArticleHelper, Settings as SettingsHelper, FilesHelper } from '../helpers';
import { FrontMatterParser } from '../parsers';
import { DumpOptions } from 'js-yaml';
import { Notifications } from '../helpers/Notifications';
export class Settings {
/**
* Create a new taxonomy
*
* @param type
*
* @param type
*/
public static async create(type: TaxonomyType) {
const newOption = await vscode.window.showInputBox({
prompt: `Insert the value of the ${type === TaxonomyType.Tag ? "tag" : "category"} that you want to add to your configuration.`,
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`,
const newOption = await vscode.window.showInputBox({
prompt: `Insert the value of the ${
type === TaxonomyType.Tag ? 'tag' : 'category'
} that you want to add to your configuration.`,
placeHolder: `Name of the ${type === TaxonomyType.Tag ? 'tag' : 'category'}`,
ignoreFocusOut: true
});
if (newOption) {
const configSetting = type === TaxonomyType.Tag ? SETTING_TAXONOMY_TAGS : SETTING_TAXONOMY_CATEGORIES;
const configSetting =
type === TaxonomyType.Tag ? SETTING_TAXONOMY_TAGS : SETTING_TAXONOMY_CATEGORIES;
let options = SettingsHelper.get(configSetting, true) as string[];
if (!options) {
options = [];
}
if (options.find(o => o === newOption)) {
Notifications.info(`The provided ${type === TaxonomyType.Tag ? "tag" : "category"} already exists.`);
if (options.find((o) => o === newOption)) {
Notifications.info(
`The provided ${type === TaxonomyType.Tag ? 'tag' : 'category'} already exists.`
);
return;
}
@@ -37,13 +40,15 @@ export class Settings {
await SettingsHelper.updateTaxonomy(type, options);
// Ask if the new term needs to be added to the page
const addToPage = await vscode.window.showQuickPick(["yes", "no"], {
canPickMany: false,
placeHolder: `Do you want to add the new ${type === TaxonomyType.Tag ? "tag" : "category"} to the page?`,
const addToPage = await vscode.window.showQuickPick(['yes', 'no'], {
canPickMany: false,
placeHolder: `Do you want to add the new ${
type === TaxonomyType.Tag ? 'tag' : 'category'
} to the page?`,
ignoreFocusOut: true
});
if (addToPage && addToPage === "yes") {
if (addToPage && addToPage === 'yes') {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
@@ -54,11 +59,11 @@ export class Settings {
return;
}
const matterProp: string = type === TaxonomyType.Tag ? "tags" : "categories";
const matterProp: string = type === TaxonomyType.Tag ? 'tags' : 'categories';
// Add the selected options to the options array
if (article.data[matterProp]) {
const propData: string[] = article.data[matterProp];
if (propData && !propData.find(o => o === newOption)) {
if (propData && !propData.find((o) => o === newOption)) {
propData.push(newOption);
}
} else {
@@ -70,107 +75,116 @@ export class Settings {
}
}
/**
* Export the tags/categories front matter to the user settings
*/
public static async export() {
// Retrieve all the Markdown files
const allMdFiles = await FilesHelper.getMdFiles();
const allMdFiles = await FilesHelper.getAllFiles();
if (!allMdFiles) {
return;
}
vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `${EXTENSION_NAME}: exporting tags and categories`,
cancellable: false
}, async (progress) => {
// Fetching all tags and categories from MD files
let tags: string[] = [];
let categories: string[] = [];
vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: `${EXTENSION_NAME}: exporting tags and categories`,
cancellable: false
},
async (progress) => {
// Fetching all tags and categories from MD files
let tags: string[] = [];
let categories: string[] = [];
// Set the initial progress
const progressNr = allMdFiles.length/100;
progress.report({ increment: 0});
// Set the initial progress
const progressNr = allMdFiles.length / 100;
progress.report({ increment: 0 });
let i = 0;
for (const file of allMdFiles) {
progress.report({ increment: (++i/progressNr) });
const mdFile = await vscode.workspace.openTextDocument(file);
if (mdFile) {
const txtData = mdFile.getText();
if (txtData) {
try {
const article = FrontMatterParser.fromFile(txtData);
if (article && article.data) {
const { data } = article;
const mdTags = data["tags"];
const mdCategories = data["categories"];
if (mdTags) {
tags = [...tags, ...mdTags];
let i = 0;
for (const file of allMdFiles) {
progress.report({ increment: ++i / progressNr });
const mdFile = await vscode.workspace.openTextDocument(file);
if (mdFile) {
const txtData = mdFile.getText();
if (txtData) {
try {
const article = FrontMatterParser.fromFile(txtData);
if (article && article.data) {
const { data } = article;
const mdTags = data['tags'];
const mdCategories = data['categories'];
if (mdTags) {
tags = [...tags, ...mdTags];
}
if (mdCategories) {
categories = [...categories, ...mdCategories];
}
}
if (mdCategories) {
categories = [...categories, ...mdCategories];
}
}
} catch (e) {
// Continue with the next file
} catch (e) {
// Continue with the next file
}
}
}
}
// Retrieve the currently known tags, and add the new ones
let crntTags: string[] = SettingsHelper.get(SETTING_TAXONOMY_TAGS, true) as string[];
if (!crntTags) {
crntTags = [];
}
crntTags = [...crntTags, ...tags];
// Update the tags and filter out the duplicates
crntTags = [...new Set(crntTags)];
crntTags = crntTags.sort().filter((t) => !!t);
await SettingsHelper.update(SETTING_TAXONOMY_TAGS, crntTags, true);
// Retrieve the currently known tags, and add the new ones
let crntCategories: string[] = SettingsHelper.get(
SETTING_TAXONOMY_CATEGORIES,
true
) as string[];
if (!crntCategories) {
crntCategories = [];
}
crntCategories = [...crntCategories, ...categories];
// Update the categories and filter out the duplicates
crntCategories = [...new Set(crntCategories)];
crntCategories = crntCategories.sort().filter((c) => !!c);
await SettingsHelper.update(SETTING_TAXONOMY_CATEGORIES, crntCategories, true);
// Done
Notifications.info(
`Export completed. Tags: ${crntTags.length} - Categories: ${crntCategories.length}.`
);
}
// Retrieve the currently known tags, and add the new ones
let crntTags: string[] = SettingsHelper.get(SETTING_TAXONOMY_TAGS, true) as string[];
if (!crntTags) { crntTags = []; }
crntTags = [...crntTags, ...tags];
// Update the tags and filter out the duplicates
crntTags = [...new Set(crntTags)];
crntTags = crntTags.sort().filter(t => !!t);
await SettingsHelper.update(SETTING_TAXONOMY_TAGS, crntTags, true);
// Retrieve the currently known tags, and add the new ones
let crntCategories: string[] = SettingsHelper.get(SETTING_TAXONOMY_CATEGORIES, true) as string[];
if (!crntCategories) { crntCategories = []; }
crntCategories = [...crntCategories, ...categories];
// Update the categories and filter out the duplicates
crntCategories = [...new Set(crntCategories)];
crntCategories = crntCategories.sort().filter(c => !!c);
await SettingsHelper.update(SETTING_TAXONOMY_CATEGORIES, crntCategories, true);
// Done
Notifications.info(`Export completed. Tags: ${crntTags.length} - Categories: ${crntCategories.length}.`);
});
);
}
/**
* Remap a tag or category to a new one
*/
public static async remap() {
const taxType = await vscode.window.showQuickPick([
"Tag",
"Category"
], {
const taxType = await vscode.window.showQuickPick(['Tag', 'Category'], {
title: `Remap`,
placeHolder: `What do you want to remap?`,
canPickMany: false,
ignoreFocusOut: true
});
if (!taxType) {
return;
}
const type = taxType === "Tag" ? TaxonomyType.Tag : TaxonomyType.Category;
let options = SettingsHelper.getTaxonomy(type);
const type = taxType === 'Tag' ? TaxonomyType.Tag : TaxonomyType.Category;
const options = SettingsHelper.getTaxonomy(type);
if (!options || options.length === 0) {
Notifications.info(`No ${type === TaxonomyType.Tag ? "tags" : "categories"} configured.`);
Notifications.info(`No ${type === TaxonomyType.Tag ? 'tags' : 'categories'} configured.`);
return;
}
const selectedOption = await vscode.window.showQuickPick(options, {
placeHolder: `Select your ${type === TaxonomyType.Tag ? "tags" : "categories"} to insert`,
const selectedOption = await vscode.window.showQuickPick(options, {
placeHolder: `Select your ${type === TaxonomyType.Tag ? 'tags' : 'categories'} to insert`,
canPickMany: false,
ignoreFocusOut: true
});
@@ -179,93 +193,31 @@ export class Settings {
return;
}
const newOptionValue = await vscode.window.showInputBox({
prompt: `Specify the value of the ${type === TaxonomyType.Tag ? "tag" : "category"} with which you want to remap "${selectedOption}". Leave the input <blank> if you want to remove the ${type === TaxonomyType.Tag ? "tag" : "category"} from all articles.`,
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`,
const newOptionValue = await vscode.window.showInputBox({
prompt: `Specify the value of the ${
type === TaxonomyType.Tag ? 'tag' : 'category'
} with which you want to remap "${selectedOption}". Leave the input <blank> if you want to remove the ${
type === TaxonomyType.Tag ? 'tag' : 'category'
} from all articles.`,
placeHolder: `Name of the ${type === TaxonomyType.Tag ? 'tag' : 'category'}`,
ignoreFocusOut: true
});
if (!newOptionValue) {
const deleteAnswer = await vscode.window.showQuickPick(["yes", "no"], {
canPickMany: false,
placeHolder: `Delete ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"}?`,
const deleteAnswer = await vscode.window.showQuickPick(['yes', 'no'], {
canPickMany: false,
placeHolder: `Delete ${selectedOption} ${type === TaxonomyType.Tag ? 'tag' : 'category'}?`,
ignoreFocusOut: true
});
if (deleteAnswer === "no") {
if (deleteAnswer === 'no') {
return;
}
}
// Retrieve all the markdown files
const allMdFiles = await FilesHelper.getMdFiles();
if (!allMdFiles) {
return;
if (newOptionValue) {
TaxonomyHelper.process('edit', type, selectedOption, newOptionValue);
} else {
TaxonomyHelper.process('delete', type, selectedOption, undefined);
}
let progressText = `${EXTENSION_NAME}: Remapping "${selectedOption}" ${type === TaxonomyType.Tag ? "tag" : "category"} to "${newOptionValue}".`;
if (!newOptionValue) {
progressText = `${EXTENSION_NAME}: Deleting "${selectedOption}" ${type === TaxonomyType.Tag ? "tag" : "category"}.`;
}
vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: progressText,
cancellable: false
}, async (progress) => {
// Set the initial progress
const progressNr = allMdFiles.length/100;
progress.report({ increment: 0});
const matterProp: string = type === TaxonomyType.Tag ? "tags" : "categories";
let i = 0;
for (const file of allMdFiles) {
progress.report({ increment: (++i/progressNr) });
const mdFile = fs.readFileSync(file.path, { encoding: "utf8" });
if (mdFile) {
try {
const article = FrontMatterParser.fromFile(mdFile);
if (article && article.data) {
const { data } = article;
let taxonomies: string[] = data[matterProp];
if (taxonomies && taxonomies.length > 0) {
const idx = taxonomies.findIndex(o => o === selectedOption);
if (idx !== -1) {
if (newOptionValue) {
taxonomies[idx] = newOptionValue;
} else {
taxonomies = taxonomies.filter(o => o !== selectedOption);
}
data[matterProp] = [...new Set(taxonomies)].sort();
const spaces = vscode.window.activeTextEditor?.options?.tabSize;
// Update the file
fs.writeFileSync(file.path, FrontMatterParser.toFile(article.content, article.data, mdFile, {
indent: spaces || 2
} as DumpOptions as any), { encoding: "utf8" });
}
}
}
} catch (e) {
// Continue with the next file
}
}
}
// Update the settings
const idx = options.findIndex(o => o === selectedOption);
if (newOptionValue) {
// Add or update the new option
if (idx !== -1) {
options[idx] = newOptionValue;
} else {
options.push(newOptionValue);
}
} else {
// Remove the selected option
options = options.filter(o => o !== selectedOption);
}
await SettingsHelper.updateTaxonomy(type, options);
Notifications.info(`${newOptionValue ? "Remapping" : "Deleation"} of the ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"} completed.`);
});
}
}
}
+155 -28
View File
@@ -1,30 +1,41 @@
import { CONTEXT, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from './../constants';
import { ParsedFrontMatter } from './../parsers/FrontMatterParser';
import {
CONTEXT,
NOTIFICATION_TYPE,
SETTING_SEO_DESCRIPTION_FIELD,
SETTING_SEO_DESCRIPTION_LENGTH,
SETTING_SEO_TITLE_FIELD,
SETTING_SEO_TITLE_LENGTH
} from './../constants';
import * as vscode from 'vscode';
import { ArticleHelper, SeoHelper, Settings } from '../helpers';
import { ArticleHelper, Notifications, SeoHelper, Settings } from '../helpers';
import { ExplorerView } from '../explorerView/ExplorerView';
import { DefaultFields } from '../constants';
import { ContentType } from '../helpers/ContentType';
import { DataListener } from '../listeners/panel';
import { commands } from 'vscode';
import { Field } from '../models';
export class StatusListener {
/**
* Update the text of the status bar
*
* @param frontMatterSB
* @param collection
*
* @param frontMatterSB
* @param collection
*/
public static async verify(frontMatterSB: vscode.StatusBarItem, collection: vscode.DiagnosticCollection) {
const draftMsg = "in draft";
const publishMsg = "to publish";
public static async verify(
frontMatterSB: vscode.StatusBarItem,
collection: vscode.DiagnosticCollection
) {
const draftMsg = 'in draft';
const publishMsg = 'to publish';
const draft = ContentType.getDraftField();
if (!draft || draft.type !== "boolean") {
if (!draft || draft.type !== 'boolean') {
frontMatterSB.hide();
}
let editor = vscode.window.activeTextEditor;
const editor = vscode.window.activeTextEditor;
if (editor && ArticleHelper.isSupportedFile()) {
try {
commands.executeCommand('setContext', CONTEXT.isValidFile, true);
@@ -32,37 +43,43 @@ export class StatusListener {
const article = ArticleHelper.getFrontMatter(editor);
// Update the StatusBar based on the article draft state
if (article && typeof article.data["draft"] !== "undefined") {
if (article.data["draft"] === true) {
if (article && typeof article.data['draft'] !== 'undefined') {
if (article.data['draft'] === true) {
frontMatterSB.text = `$(book) ${draftMsg}`;
frontMatterSB.show();
} else if (article.data["draft"] === false) {
} else if (article.data['draft'] === false) {
frontMatterSB.text = `$(book) ${publishMsg}`;
frontMatterSB.show();
}
}
// Check SEO for title and description length
// Check SEO and required fields
if (article && article.data) {
collection.clear();
// Retrieve the SEO config properties
const titleLength = Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1;
const descLength = Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1;
const fieldName = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
if (article.data.title && titleLength > -1) {
SeoHelper.checkLength(editor, collection, article, "title", titleLength);
const titleLength = (Settings.get(SETTING_SEO_TITLE_LENGTH) as number) || -1;
const descLength = (Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number) || -1;
const titleField =
(Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
const descriptionField =
(Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
if (article.data[titleField] && titleLength > -1) {
SeoHelper.checkLength(editor, collection, article, titleField, titleLength);
}
if (article.data[fieldName] && descLength > -1) {
SeoHelper.checkLength(editor, collection, article, fieldName, descLength);
if (article.data[descriptionField] && descLength > -1) {
SeoHelper.checkLength(editor, collection, article, descriptionField, descLength);
}
// Check the required fields
StatusListener.verifyRequiredFields(editor, article, collection);
}
const panel = ExplorerView.getInstance();
if (panel && panel.visible) {
DataListener.pushMetadata(article!.data);
DataListener.pushMetadata(article?.data);
}
return;
@@ -80,4 +97,114 @@ export class StatusListener {
frontMatterSB.hide();
}
}
/**
* Verify the required fields
* @param article
* @param collection
*/
private static verifyRequiredFields(
editor: vscode.TextEditor,
article: ParsedFrontMatter,
collection: vscode.DiagnosticCollection
) {
// Check for missing fields
const emptyFields = ContentType.findEmptyRequiredFields(article);
const fieldsToReport = [];
if (emptyFields && emptyFields.length > 0) {
const text = editor.document.getText();
const markdown = ArticleHelper.stringifyFrontMatter('', article.data);
const editorSpaces = vscode.window.activeTextEditor?.options?.tabSize;
const requiredDiagnostics: vscode.Diagnostic[] = [];
for (const fields of emptyFields) {
let txtIdx = -1;
let fieldName = '';
let level = 0;
for (const field of fields) {
const totalSpaces =
level * (typeof editorSpaces === 'string' ? parseInt(editorSpaces) : editorSpaces || 2);
const crntIdx = StatusListener.findFieldLine(text, txtIdx, totalSpaces, field);
if (crntIdx && crntIdx > txtIdx) {
txtIdx = crntIdx;
fieldName = field.name;
}
++level;
}
if (txtIdx !== -1 && txtIdx < markdown.length) {
fieldsToReport.push(fields.map((f) => f.title).join('/'));
const posStart = editor.document.positionAt(txtIdx);
const posEnd = editor.document.positionAt(txtIdx + 1 + fieldName.length);
const diagnostic: vscode.Diagnostic = {
code: '',
message: `This ${fields
.map((f) => f.name)
.join('/')} field is required to contain a value.`,
range: new vscode.Range(posStart, posEnd),
severity: vscode.DiagnosticSeverity.Error,
source: 'Front Matter'
};
requiredDiagnostics.push(diagnostic);
}
}
if (collection.has(editor.document.uri)) {
const otherDiag = collection.get(editor.document.uri) || [];
collection.set(editor.document.uri, [...otherDiag, ...requiredDiagnostics]);
} else {
collection.set(editor.document.uri, [...requiredDiagnostics]);
}
if (fieldsToReport.length > 0) {
Notifications.showIfNotDisabled(
NOTIFICATION_TYPE.requiredFieldValidation,
'ERROR_ONCE',
`The following fields are required to contain a value: ${fieldsToReport.join(', ')}`
);
}
}
}
/**
* Find the line of the field
* @param text
* @param startIdx
* @param totalSpaces
* @param field
* @returns
*/
private static findFieldLine(
text: string,
startIdx: number,
totalSpaces: number,
field: Field
): number | undefined {
const crntIdx = text.indexOf(field.name, startIdx === -1 ? 0 : startIdx);
if (crntIdx > -1) {
// Find the linebreak before the current index
const txtFromStart = text.substring(0, crntIdx);
const splitLineBreaks = txtFromStart.split(/\r?\n/);
const lastLine = splitLineBreaks[splitLineBreaks.length - 1];
if (lastLine.length === totalSpaces) {
if (crntIdx > startIdx) {
return crntIdx;
}
} else {
return StatusListener.findFieldLine(text, crntIdx + field.name.length, totalSpaces, field);
}
}
return;
}
}
+58 -66
View File
@@ -1,55 +1,23 @@
import { Questions } from './../helpers/Questions';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants';
import {
SETTING_CONTENT_DEFAULT_FILETYPE,
SETTING_TEMPLATES_FOLDER,
TelemetryEvent
} from '../constants';
import { ArticleHelper, Settings } from '../helpers';
import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
import { CONTEXT } from '../constants';
import { Project } from './Project';
import { Folders } from './Folders';
import { ContentType } from '../helpers/ContentType';
import { ContentType as IContentType } from '../models';
import { PagesListener } from '../listeners/dashboard';
import { extname } from 'path';
import { Telemetry } from '../helpers/Telemetry';
import { writeFileAsync, copyFileAsync } from '../utils';
export class Template {
/**
* Check if the template folder is available
*/
public static async init() {
const isInitialized = await Template.isInitialized();
await vscode.commands.executeCommand('setContext', CONTEXT.canInit, !isInitialized);
if (isInitialized) {
await vscode.commands.executeCommand('setContext', CONTEXT.initialized, true);
}
}
/**
* Check if the project is already initialized
*/
public static async isInitialized() {
const wsFolder = Folders.getWorkspaceFolder();
const folder = Template.getSettings();
if (!folder || !wsFolder) {
return false;
}
const templatePath = vscode.Uri.file(path.join(wsFolder.fsPath, folder));
try {
await vscode.workspace.fs.stat(templatePath);
return true;
} catch (e) {
return false;
}
}
/**
* Generate a template
*/
@@ -62,7 +30,8 @@ export class Template {
const article = ArticleHelper.getFrontMatter(editor);
const clonedArticle = Object.assign({}, article);
const titleValue = await vscode.window.showInputBox({
const titleValue = await vscode.window.showInputBox({
title: `Template title`,
prompt: `What name would you like to give your template?`,
placeHolder: `article`,
ignoreFocusOut: true
@@ -73,27 +42,30 @@ export class Template {
return;
}
const keepContents = await vscode.window.showQuickPick(
["yes", "no"],
{
canPickMany: false,
placeHolder: `Do you want to keep the contents for the template?`,
ignoreFocusOut: true
}
);
const keepContents = await vscode.window.showQuickPick(['yes', 'no'], {
title: `Keep contents`,
canPickMany: false,
placeHolder: `Do you want to keep the contents for the template?`,
ignoreFocusOut: true
});
if (!keepContents) {
Notifications.warning(`You did not pick any of the options for keeping the template its content.`);
Notifications.warning(
`You did not pick any of the options for keeping the template its content.`
);
return;
}
await Project.init(false);
const templatePath = Project.templatePath();
if (templatePath) {
let fileContents = ArticleHelper.stringifyFrontMatter(keepContents === "no" ? "" : clonedArticle.content, clonedArticle.data);
const fileContents = ArticleHelper.stringifyFrontMatter(
keepContents === 'no' ? '' : clonedArticle.content,
clonedArticle.data
);
const templateFile = path.join(templatePath.fsPath, `${titleValue}.${fileType}`);
fs.writeFileSync(templateFile, fileContents, { encoding: "utf-8" });
await writeFileAsync(templateFile, fileContents, { encoding: 'utf-8' });
Notifications.info(`Template created and is now available in your ${folder} folder.`);
}
@@ -111,7 +83,10 @@ export class Template {
return;
}
return await vscode.workspace.findFiles(`${folder}/**/*`, "**/node_modules/**,**/archetypes/**");
return await vscode.workspace.findFiles(
`${folder}/**/*`,
'**/node_modules/**,**/archetypes/**'
);
}
/**
@@ -131,10 +106,14 @@ export class Template {
return;
}
const selectedTemplate = await vscode.window.showQuickPick(templates.map(t => path.basename(t.fsPath)), {
placeHolder: `Select the content template to use`,
ignoreFocusOut: true
});
const selectedTemplate = await vscode.window.showQuickPick(
templates.map((t) => path.basename(t.fsPath)),
{
title: `Select a template`,
placeHolder: `Select the content template to use`,
ignoreFocusOut: true
}
);
if (!selectedTemplate) {
Notifications.warning(`No template selected.`);
return;
@@ -146,40 +125,53 @@ export class Template {
}
// Start the template read
const template = templates.find(t => t.fsPath.endsWith(selectedTemplate));
const template = templates.find((t) => t.fsPath.endsWith(selectedTemplate));
if (!template) {
Notifications.warning(`Content template could not be found.`);
return;
}
const templateData = ArticleHelper.getFrontMatterByPath(template.fsPath);
const templateData = await ArticleHelper.getFrontMatterByPath(template.fsPath);
let contentType: IContentType | undefined;
if (templateData && templateData.data && templateData.data.type) {
contentType = contentTypes?.find(t => t.name === templateData.data.type);
contentType = contentTypes?.find((t) => t.name === templateData.data.type);
}
const fileExtension = extname(template.fsPath).replace(".", "");
let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue, fileExtension);
const fileExtension = extname(template.fsPath).replace('.', '');
const newFilePath: string | undefined = await ArticleHelper.createContent(
contentType,
folderPath,
titleValue,
fileExtension
);
if (!newFilePath) {
return;
}
// Start the new file creation
fs.copyFileSync(template.fsPath, newFilePath);
await copyFileAsync(template.fsPath, newFilePath);
// Update the properties inside the template
let frontMatter = ArticleHelper.getFrontMatterByPath(newFilePath);
let frontMatter = await ArticleHelper.getFrontMatterByPath(newFilePath);
if (!frontMatter) {
Notifications.warning(`Something failed when retrieving the newly created file.`);
return;
}
if (frontMatter.data) {
frontMatter.data = ArticleHelper.updatePlaceholders(frontMatter.data, titleValue);
frontMatter.data = await ArticleHelper.updatePlaceholders(
frontMatter.data,
titleValue,
newFilePath
);
frontMatter = Article.updateDate(frontMatter);
fs.writeFileSync(newFilePath, ArticleHelper.stringifyFrontMatter(frontMatter.content, frontMatter.data), { encoding: "utf8" });
await writeFileAsync(
newFilePath,
ArticleHelper.stringifyFrontMatter(frontMatter.content, frontMatter.data),
{ encoding: 'utf8' }
);
await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(newFilePath));
}
@@ -204,4 +196,4 @@ export class Template {
const folder = Settings.get<string>(SETTING_TEMPLATES_FOLDER);
return folder;
}
}
}
+212 -92
View File
@@ -1,6 +1,6 @@
import { commands, window, Selection, QuickPickItem } from "vscode";
import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from "../constants";
import { Settings } from "../helpers";
import { commands, window, Selection, QuickPickItem, TextEditor } from 'vscode';
import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from '../constants';
import { Settings } from '../helpers';
enum MarkupType {
bold = 1,
@@ -12,18 +12,17 @@ enum MarkupType {
heading,
unorderedList,
orderedList,
taskList
taskList,
hyperlink
}
export class Wysiwyg {
/**
* Registers the markup commands for the WYSIWYG controls
* @param subscriptions
* @returns
* @param subscriptions
* @returns
*/
public static async registerCommands(subscriptions: any) {
public static async registerCommands(subscriptions: unknown[]) {
const wysiwygEnabled = Settings.get(SETTING_CONTENT_WYSIWYG);
if (!wysiwygEnabled) {
@@ -33,58 +32,123 @@ export class Wysiwyg {
await commands.executeCommand('setContext', CONTEXT.wysiwyg, true);
// Surrounding markup
subscriptions.push(commands.registerCommand(COMMAND_NAME.bold, () => this.addMarkup(MarkupType.bold)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.italic, () => this.addMarkup(MarkupType.italic)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.strikethrough, () => this.addMarkup(MarkupType.strikethrough)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.code, () => this.addMarkup(MarkupType.code)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.codeblock, () => this.addMarkup(MarkupType.codeblock)));
subscriptions.push(
commands.registerCommand(COMMAND_NAME.bold, () => this.addMarkup(MarkupType.bold))
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.italic, () => this.addMarkup(MarkupType.italic))
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.strikethrough, () =>
this.addMarkup(MarkupType.strikethrough)
)
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.code, () => this.addMarkup(MarkupType.code))
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.codeblock, () => this.addMarkup(MarkupType.codeblock))
);
// Prefix markup
subscriptions.push(commands.registerCommand(COMMAND_NAME.heading, () => this.addMarkup(MarkupType.heading)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.blockquote, () => this.addMarkup(MarkupType.blockquote)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.unorderedlist, () => this.addMarkup(MarkupType.unorderedList)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.orderedlist, () => this.addMarkup(MarkupType.orderedList)));
subscriptions.push(commands.registerCommand(COMMAND_NAME.taskList, () => this.addMarkup(MarkupType.taskList)));
subscriptions.push(
commands.registerCommand(COMMAND_NAME.heading, () => this.addMarkup(MarkupType.heading))
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.blockquote, () => this.addMarkup(MarkupType.blockquote))
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.unorderedlist, () =>
this.addMarkup(MarkupType.unorderedList)
)
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.orderedlist, () =>
this.addMarkup(MarkupType.orderedList)
)
);
subscriptions.push(
commands.registerCommand(COMMAND_NAME.taskList, () => this.addMarkup(MarkupType.taskList))
);
// Other markup
subscriptions.push(
commands.registerCommand(COMMAND_NAME.hyperlink, () => this.addMarkup(MarkupType.hyperlink))
);
// Options
subscriptions.push(commands.registerCommand(COMMAND_NAME.options, async () => {
const qpItems: QuickPickItem[] = [
{ label: "$(list-unordered) Unordered list", detail: "Add an unordered list", alwaysShow: true, },
{ label: "$(list-ordered) Ordered list", detail: "Add an ordered list", alwaysShow: true },
{ label: "$(tasklist) Task list", detail: "Add a task list", alwaysShow: true },
{ label: "$(code) Code", detail: "Add inline code snippet", alwaysShow: true },
{ label: "$(symbol-namespace) Code block", detail: "Add a code block", alwaysShow: true },
{ label: "$(quote) Blockquote", detail: "Add a blockquote", alwaysShow: true },
]
subscriptions.push(
commands.registerCommand(COMMAND_NAME.options, async () => {
const qpItems: QuickPickItem[] = [
{
label: '$(list-unordered) Unordered list',
detail: 'Add an unordered list',
alwaysShow: true
},
{
label: '$(list-ordered) Ordered list',
detail: 'Add an ordered list',
alwaysShow: true
},
{
label: '$(tasklist) Task list',
detail: 'Add a task list',
alwaysShow: true
},
{
label: '$(code) Code',
detail: 'Add inline code snippet',
alwaysShow: true
},
{
label: '$(symbol-namespace) Code block',
detail: 'Add a code block',
alwaysShow: true
},
{
label: '$(quote) Blockquote',
detail: 'Add a blockquote',
alwaysShow: true
},
{
label: '$(symbol-text) Strikethrough',
detail: 'Add a strikethrough',
alwaysShow: true
}
];
const option = await window.showQuickPick([ ...qpItems ], {
placeHolder: "Which type of markup would you like to insert?",
canPickMany: false,
ignoreFocusOut: true
});
const option = await window.showQuickPick([...qpItems], {
title: 'WYSIWYG Options',
placeHolder: 'Which type of markup would you like to insert?',
canPickMany: false,
ignoreFocusOut: true
});
if (option) {
if (option.label === qpItems[0].label) {
await this.addMarkup(MarkupType.unorderedList);
} else if (option.label === qpItems[1].label) {
await this.addMarkup(MarkupType.orderedList);
} else if (option.label === qpItems[2].label) {
await this.addMarkup(MarkupType.taskList);
} else if (option.label === qpItems[3].label) {
await this.addMarkup(MarkupType.code);
} else if (option.label === qpItems[4].label) {
await this.addMarkup(MarkupType.codeblock);
} else if (option.label === qpItems[5].label) {
await this.addMarkup(MarkupType.blockquote);
if (option) {
if (option.label === qpItems[0].label) {
await this.addMarkup(MarkupType.unorderedList);
} else if (option.label === qpItems[1].label) {
await this.addMarkup(MarkupType.orderedList);
} else if (option.label === qpItems[2].label) {
await this.addMarkup(MarkupType.taskList);
} else if (option.label === qpItems[3].label) {
await this.addMarkup(MarkupType.code);
} else if (option.label === qpItems[4].label) {
await this.addMarkup(MarkupType.codeblock);
} else if (option.label === qpItems[5].label) {
await this.addMarkup(MarkupType.blockquote);
} else if (option.label === qpItems[6].label) {
await this.addMarkup(MarkupType.strikethrough);
}
}
}
}));
})
);
}
/**
* Add the markup to the content
* @param type
* @returns
* @param type
* @returns
*/
private static async addMarkup(type: MarkupType) {
const editor = window.activeTextEditor;
@@ -95,6 +159,10 @@ export class Wysiwyg {
const selection = editor.selection;
const hasTextSelection = !selection.isEmpty;
if (type === MarkupType.hyperlink) {
return this.addHyperlink(editor, selection);
}
const markers = this.getMarkers(type);
if (!markers) {
return;
@@ -106,18 +174,21 @@ export class Wysiwyg {
// Replace the selection and surround with the markup
const selectionText = editor.document.getText(selection);
const txt = await this.insertText(markers, type, selectionText);
editor.edit(builder => {
editor.edit((builder) => {
builder.replace(selection, txt);
});
} else {
const txt = await this.insertText(markers, type);
// Insert the markers where cursor is located.
const markerLength = this.isMarkupWrapping(type) ? txt.length + 1 : markers.length;
let newPosition = crntSelection.with(crntSelection.line, crntSelection.character + markerLength);
let newPosition = crntSelection.with(
crntSelection.line,
crntSelection.character + markerLength
);
await editor.edit(builder => {
await editor.edit((builder) => {
builder.insert(newPosition, txt);
});
@@ -130,13 +201,61 @@ export class Wysiwyg {
}
/**
* Check if the text will be wrapped
* @param type
* @returns
* Add a hyperlink to the content
* @returns void
*/
private static isMarkupWrapping(type: MarkupType) {
private static async addHyperlink(editor: TextEditor, selection: Selection) {
const hasTextSelection = !selection.isEmpty;
const linkText = hasTextSelection ? editor.document.getText(selection) : '';
const link = await window.showInputBox({
title: 'WYSIWYG Hyperlink',
placeHolder: 'Enter the URL',
prompt: 'Enter the URL',
value: linkText,
ignoreFocusOut: true
});
const text = await window.showInputBox({
title: 'WYSIWYG Text',
prompt: 'Enter the text for the hyperlink',
placeHolder: 'Enter the text for the hyperlink',
value: linkText,
ignoreFocusOut: true
});
if (link) {
const txt = `[${text || link}](${link})`;
if (hasTextSelection) {
editor.edit((builder) => {
builder.replace(selection, txt);
});
} else {
const crntSelection = selection.active;
const markerLength = txt.length;
const newPosition = crntSelection.with(
crntSelection.line,
crntSelection.character + markerLength
);
await editor.edit((builder) => {
builder.insert(newPosition, txt);
});
editor.selection = new Selection(newPosition, newPosition);
}
}
}
/**
* Check if the text will be wrapped
* @param type
* @returns
*/
private static isMarkupWrapping(type: MarkupType) {
return (
type === MarkupType.blockquote ||
type === MarkupType.blockquote ||
type === MarkupType.heading ||
type === MarkupType.unorderedList ||
type === MarkupType.orderedList ||
@@ -147,38 +266,39 @@ export class Wysiwyg {
/**
* Insert text at the current cursor position
*/
private static async insertText(marker: string | undefined, type: MarkupType, text: string | null = null) {
private static async insertText(
marker: string | undefined,
type: MarkupType,
text: string | null = null
) {
const crntText = text || this.lineBreak(type);
if (this.isMarkupWrapping(type)) {
if (type === MarkupType.heading) {
const headingLvl = await window.showQuickPick([
"Heading 1",
"Heading 2",
"Heading 3",
"Heading 4",
"Heading 5",
"Heading 6"
], {
canPickMany: false,
placeHolder: "Which heading level do you want to insert?",
ignoreFocusOut: true
});
const headingLvl = await window.showQuickPick(
['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6'],
{
title: 'Heading Level',
canPickMany: false,
placeHolder: 'Which heading level do you want to insert?',
ignoreFocusOut: true
}
);
if (headingLvl) {
const headingNr = parseInt(headingLvl.replace("Heading ", ""));
const headingNr = parseInt(headingLvl.replace('Heading ', ''));
return `${Array(headingNr + 1).join(marker)} ${crntText}`;
}
}
if (type === MarkupType.unorderedList || type === MarkupType.taskList) {
const lines = crntText.split("\n").map(line => `${marker} ${line}`);
return lines.join("\n");
const lines = crntText.split('\n').map((line) => `${marker} ${line}`);
return lines.join('\n');
}
if (type === MarkupType.orderedList) {
const lines = crntText.split("\n").map((line, idx) => `${idx+1}. ${line}`);
return lines.join("\n");
const lines = crntText.split('\n').map((line, idx) => `${idx + 1}. ${line}`);
return lines.join('\n');
}
return `${marker} ${crntText}`;
@@ -189,23 +309,23 @@ export class Wysiwyg {
/**
* Check if linebreak needs to be added
* @param type
* @returns
* @param type
* @returns
*/
private static lineBreak(type: MarkupType) {
if (type === MarkupType.codeblock) {
return `\n\n`;
}
return "";
return '';
}
/**
* Retrieve the type of markers
* @param type
* @returns
* @param type
* @returns
*/
private static getMarkers(type: MarkupType) {
switch(type) {
switch (type) {
case MarkupType.bold:
return `**`;
case MarkupType.italic:
@@ -213,21 +333,21 @@ export class Wysiwyg {
case MarkupType.strikethrough:
return `~~`;
case MarkupType.code:
return "`";
return '`';
case MarkupType.codeblock:
return "```";
return '```';
case MarkupType.blockquote:
return ">";
return '>';
case MarkupType.heading:
return "#";
return '#';
case MarkupType.unorderedList:
return "-";
return '-';
case MarkupType.orderedList:
return "1.";
return '1.';
case MarkupType.taskList:
return "- [ ]";
return '- [ ]';
default:
return;
}
}
}
}
+11
View File
@@ -1,3 +1,14 @@
export * from './Article';
export * from './Backers';
export * from './Cache';
export * from './Chatbot';
export * from './Content';
export * from './Dashboard';
export * from './Diagnostics';
export * from './Folders';
export * from './Preview';
export * from './Project';
export * from './Settings';
export * from './StatusListener';
export * from './Template';
export * from './Wysiwyg';
+9 -11
View File
@@ -6,9 +6,13 @@ export interface IFeatureFlagProps {
alternative?: JSX.Element;
}
export const FeatureFlag: React.FunctionComponent<IFeatureFlagProps> = ({ flag, features, alternative, children }: React.PropsWithChildren<IFeatureFlagProps>) => {
if (!features ||( features.length > 0 && !features.includes(flag))) {
export const FeatureFlag: React.FunctionComponent<IFeatureFlagProps> = ({
flag,
features,
alternative,
children
}: React.PropsWithChildren<IFeatureFlagProps>) => {
if (!features || (features.length > 0 && !features.includes(flag))) {
if (alternative) {
return alternative;
}
@@ -16,11 +20,5 @@ export const FeatureFlag: React.FunctionComponent<IFeatureFlagProps> = ({ flag,
return null;
}
return (
<>
{children}
</>
);
};
return <>{children}</>;
};
+14
View File
@@ -0,0 +1,14 @@
import * as React from 'react';
export interface IChatIconProps {
className?: string;
}
export const ChatIcon: React.FunctionComponent<IChatIconProps> = ({ className }: React.PropsWithChildren<IChatIconProps>) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" className={className || 'h-4 w-4'} viewBox="0 0 32 32">
<path fill="currentcolor" d="M16 19a6.99 6.99 0 0 1-5.833-3.129l1.666-1.107a5 5 0 0 0 8.334 0l1.666 1.107A6.99 6.99 0 0 1 16 19zm4-11a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2zm-8 0a2 2 0 1 0 2 2a1.98 1.98 0 0 0-2-2z" />
<path fill="currentcolor" d="M17.736 30L16 29l4-7h6a1.997 1.997 0 0 0 2-2V6a1.997 1.997 0 0 0-2-2H6a1.997 1.997 0 0 0-2 2v14a1.997 1.997 0 0 0 2 2h9v2H6a4 4 0 0 1-4-4V6a3.999 3.999 0 0 1 4-4h20a3.999 3.999 0 0 1 4 4v14a4 4 0 0 1-4 4h-4.835Z" />
</svg>
);
};
+16 -4
View File
@@ -4,10 +4,22 @@ export interface ICompressIconProps {
className?: string;
}
export const CompressIcon: React.FunctionComponent<ICompressIconProps> = ({className}: React.PropsWithChildren<ICompressIconProps>) => {
export const CompressIcon: React.FunctionComponent<ICompressIconProps> = ({
className
}: React.PropsWithChildren<ICompressIconProps>) => {
return (
<svg className={className || ""} aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M436 192H312c-13.3 0-24-10.7-24-24V44c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v84h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-276-24V44c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v84H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24zm0 300V344c0-13.3-10.7-24-24-24H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-84h84c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12H312c-13.3 0-24 10.7-24 24v124c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12z"></path>
<svg
className={className || ''}
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<path
fill="currentColor"
d="M436 192H312c-13.3 0-24-10.7-24-24V44c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v84h84c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm-276-24V44c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v84H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24zm0 300V344c0-13.3-10.7-24-24-24H12c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-84h84c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12H312c-13.3 0-24 10.7-24 24v124c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12z"
></path>
</svg>
);
};
};
+19
View File
@@ -0,0 +1,19 @@
import * as React from 'react';
export interface IMergeIconProps {
className: string;
}
export const MergeIcon: React.FunctionComponent<IMergeIconProps> = ({
className
}: React.PropsWithChildren<IMergeIconProps>) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" className={className}>
<path
xmlns="http://www.w3.org/2000/svg"
d="M7.586 8.00366L4 8.00366C3.44772 8.00366 3 7.55595 3 7.00366C3 6.45138 3.44772 6.00366 4 6.00366L8 6.00366C8.26509 6.00366 8.51933 6.10892 8.70685 6.2963L13.414 11H18.5845L15.2931 7.71103C14.9025 7.32065 14.9023 6.68748 15.2926 6.29681C15.683 5.90615 16.3162 5.90592 16.7068 6.2963L21.7068 11.2926C21.8945 11.4802 22 11.7346 22 11.9998C22 12.2651 21.8947 12.5195 21.7071 12.7071L16.7071 17.7071C16.3166 18.0976 15.6834 18.0976 15.2929 17.7071C14.9024 17.3166 14.9024 16.6834 15.2929 16.2929L18.5858 13H13.4142L8.70711 17.7071C8.51957 17.8947 8.26522 18 8 18H4C3.44772 18 3 17.5523 3 17C3 16.4477 3.44772 16 4 16H7.58579L11.5855 12.0003L7.586 8.00366Z"
fill="currentcolor"
/>
</svg>
);
};
@@ -1,23 +1,21 @@
import * as invariant from 'invariant';
import { createAutoField } from 'uniforms';
import { PreviewImageField } from '../../panelWebView/components/Fields/PreviewImageField';
export { AutoFieldProps } from 'uniforms';
import BoolField from './BoolField';
import DateField from './DateField';
import ListField from './ListField';
import LongTextField from './LongTextField';
import NestField from './NestField';
import NumField from './NumField';
import RadioField from './RadioField';
import SelectField from './SelectField';
import TextField from './TextField';
import UnknownField from './UnknownField';
const AutoField = createAutoField(props => {
const AutoField = createAutoField((props) => {
if (props.allowedValues) {
return props.checkboxes && props.fieldType !== Array
? RadioField
: SelectField;
return props.checkboxes && props.fieldType !== Array ? RadioField : SelectField;
}
switch (props.fieldType) {
@@ -32,7 +30,12 @@ const AutoField = createAutoField(props => {
case Object:
return NestField;
case String:
if (props["multiline"]) {
return LongTextField;
}
return TextField;
default:
return UnknownField;
}
return invariant(false, 'Unsupported field type: %s', props.fieldType);
@@ -23,7 +23,7 @@ export default function AutoFields({
element,
props,
(fields ?? schema.getSubfields())
.filter(field => !omitFields.includes(field))
.map(field => createElement(autoField, { key: field, name: field })),
.filter((field) => !omitFields.includes(field))
.map((field) => createElement(autoField, { key: field, name: field }))
);
}
@@ -2,8 +2,9 @@ import { AutoForm } from 'uniforms';
import ValidatedQuickForm from './ValidatedQuickForm';
function Auto(parent: any) {
class _ extends AutoForm.Auto(parent) {
function Auto(parent: unknown) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class _ extends AutoForm.Auto(parent as any) {
static Auto = Auto;
}
@@ -1,5 +1,6 @@
import { BaseForm } from 'uniforms';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function Unstyled(parent: any) {
class _ extends parent {
static Unstyled = Unstyled;
@@ -5,7 +5,7 @@
height: 24px;
}
.field__toggle input {
.field__toggle input {
opacity: 0;
width: 0;
height: 0;
@@ -18,22 +18,25 @@
left: 0;
right: 0;
bottom: 0;
background-color: var(--frontmatter-toggle-secondaryBackground, var(--vscode-button-secondaryBackground));
-webkit-transition: .4s;
transition: .4s;
background-color: var(
--frontmatter-toggle-secondaryBackground,
var(--vscode-button-secondaryBackground)
);
-webkit-transition: 0.4s;
transition: 0.4s;
border-radius: 34px;
}
.field__toggle__slider:before {
position: absolute;
content: "";
content: '';
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
-webkit-transition: 0.4s;
transition: 0.4s;
border-radius: 50%;
}
@@ -49,4 +52,4 @@ input:checked + .field__toggle__slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
}
@@ -24,7 +24,7 @@ function Bool({
return (
<div {...filterDOMProps(props)}>
<LabelField label={label} id={id} required={props.required} />
<label className="field__toggle">
<input
checked={value || false}
@@ -36,7 +36,7 @@ function Date({
max={dateFormat(max)}
min={dateFormat(min)}
name={name}
onChange={event => {
onChange={(event) => {
const date = new DateConstructor(event.target.valueAsNumber);
if (date.getFullYear() < 10000) {
onChange(date);
@@ -4,16 +4,14 @@ import { Override, connectField, filterDOMProps } from 'uniforms';
export type ErrorFieldProps = Override<
Omit<HTMLProps<HTMLDivElement>, 'onChange'>,
{ error?: any; errorMessage?: string }
{ error?: unknown; errorMessage?: string }
>;
function Error({ children, error, errorMessage, ...props }: ErrorFieldProps) {
return !error ? null : (
<div {...filterDOMProps(props)}>{children || errorMessage}</div>
);
return !error ? null : <div {...filterDOMProps(props)}>{children || errorMessage}</div>;
}
export default connectField<ErrorFieldProps>(Error, {
initialValue: false,
kind: 'leaf',
kind: 'leaf'
});
@@ -1,7 +1,8 @@
.autoform-error {
background-color: var(--frontmatter-error-background, var(--vscode-inputValidation-errorBackground));
background-color: var(
--frontmatter-error-background,
var(--vscode-inputValidation-errorBackground)
);
border: 1px solid var(--frontmatter-error-border, var(--vscode-inputValidation-errorBorder));
border-radius: 2px;
margin: 20px 0px;
@@ -15,4 +16,4 @@
li {
text-transform: capitalize;
}
}
}
@@ -8,7 +8,7 @@ export type ErrorsFieldProps = HTMLProps<HTMLDivElement>;
export default function ErrorsField(props: ErrorsFieldProps) {
const { error, schema } = useForm();
return !error && !props.children ? null : (
<div className='autoform-error'>
<div className="autoform-error">
<div {...filterDOMProps(props)}>
{props.children}
@@ -2,18 +2,20 @@ import * as React from 'react';
import { HTMLProps, Ref, useEffect } from 'react';
import { Override, filterDOMProps, useField } from 'uniforms';
type ValueType = string | number | readonly string[] | undefined;
export type HiddenFieldProps = Override<
HTMLProps<HTMLInputElement>,
{
inputRef?: Ref<HTMLInputElement>;
name: string;
noDOM?: boolean;
value?: any;
value?: ValueType;
}
>;
export default function HiddenField({ value, ...rawProps }: HiddenFieldProps) {
const props = useField(rawProps.name, rawProps, { initialValue: false })[0];
const defaultValue = (props.value as ValueType) ?? '';
useEffect(() => {
if (value !== undefined && value !== props.value) {
@@ -28,7 +30,7 @@ export default function HiddenField({ value, ...rawProps }: HiddenFieldProps) {
readOnly={props.readOnly}
ref={props.inputRef}
type="hidden"
value={value ?? props.value ?? ''}
value={value ?? defaultValue}
{...filterDOMProps(props)}
/>
);
@@ -1,5 +1,3 @@
.autoform__label {
display: block;
margin-bottom: 0.5rem;
@@ -11,4 +9,4 @@
color: var(--vscode-inputValidation-errorBorder);
margin-left: 0.25rem;
}
}
}
@@ -8,13 +8,19 @@ export interface ILabelFieldProps {
required?: boolean;
}
export const LabelField: React.FunctionComponent<ILabelFieldProps> = ({ label, id, required }: React.PropsWithChildren<ILabelFieldProps>) => {
return (
label ? (
<label className="autoform__label" htmlFor={id}>
{label}
{required && <span title='Required field' className='autoform__label__required'>*</span>}
</label>
) : null
);
};
export const LabelField: React.FunctionComponent<ILabelFieldProps> = ({
label,
id,
required
}: React.PropsWithChildren<ILabelFieldProps>) => {
return label ? (
<label className="autoform__label" htmlFor={id}>
{label}
{required && (
<span title="Required field" className="autoform__label__required">
*
</span>
)}
</label>
) : null;
};
@@ -1,12 +1,10 @@
.autoform__list_add_field {
display: flex;
padding: 5px;
border: 1px dashed var(--frontmatter-field-border, var(--vscode-editor-foreground));
width: 100%;
justify-content: center;
margin-top: .5rem;
margin-top: 0.5rem;
&:hover {
border-color: var(--frontmatter-field-borderActive, var(--vscode-button-background));
@@ -18,4 +16,4 @@
height: 1rem;
width: 1rem;
}
}
}
@@ -1,51 +1,42 @@
import { PlusIcon } from '@heroicons/react/outline';
import * as React from 'react';
import {
HTMLFieldProps,
connectField,
filterDOMProps,
joinName,
useField,
} from 'uniforms';
import { HTMLFieldProps, connectField, filterDOMProps, joinName, useField } from 'uniforms';
import './ListAddField.css';
type ParentFieldType = { initialCount?: number; maxCount?: number };
type ValueType = string | readonly string[] | undefined;
export type ListAddFieldProps = HTMLFieldProps<
unknown,
ValueType,
HTMLSpanElement,
{ initialCount?: number }
>;
function ListAdd({
disabled,
initialCount,
name,
readOnly,
value,
...props
}: ListAddFieldProps) {
function ListAdd({ disabled, initialCount, name, readOnly, value, ...props }: ListAddFieldProps) {
const nameParts = joinName(null, name);
const parentName = joinName(nameParts.slice(0, -1));
const parent = useField<
{ initialCount?: number; maxCount?: number },
unknown[]
>(parentName, { initialCount }, { absoluteName: true })[0];
const parent = {
maxCount: 0,
value: [] as ValueType[],
...useField<ParentFieldType, ValueType[]>(
parentName,
{ initialCount },
{ absoluteName: true }
)[0]
};
const limitNotReached =
!disabled && !(parent.maxCount! <= parent.value!.length);
const limitNotReached = !disabled && !(parent.maxCount <= parent.value.length);
function onAction(event: React.KeyboardEvent | React.MouseEvent) {
if (
limitNotReached &&
!readOnly &&
(!('key' in event) || event.key === 'Enter')
) {
parent.onChange(parent.value!.concat([Object.assign({}, value)]));
if (limitNotReached && !readOnly && (!('key' in event) || event.key === 'Enter')) {
parent.onChange(parent.value.concat([value]));
}
}
return (
<span
className='autoform__list_add_field'
className="autoform__list_add_field"
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{...filterDOMProps(props as any)}
onClick={onAction}
onKeyDown={onAction}
@@ -59,5 +50,5 @@ function ListAdd({
export default connectField<ListAddFieldProps>(ListAdd, {
initialValue: false,
kind: 'leaf',
kind: 'leaf'
});
@@ -1,10 +1,8 @@
.autoform__list_del_field {
display: flex;
width: 100%;
justify-content: center;
margin-top: .5rem;
margin-top: 0.5rem;
&:hover {
border-color: var(--vscode-button-background);
@@ -16,12 +14,12 @@
height: 1px;
background: var(--frontmatter-list-border, var(--vscode-editor-foreground));
width: 100%;
margin-right: .5rem;
margin-top: .5rem;
margin-right: 0.5rem;
margin-top: 0.5rem;
}
svg {
height: 1.25rem;
width: 1.25rem;
}
}
}
@@ -1,12 +1,6 @@
import { TrashIcon } from '@heroicons/react/outline';
import * as React from 'react';
import {
HTMLFieldProps,
connectField,
filterDOMProps,
joinName,
useField,
} from 'uniforms';
import { HTMLFieldProps, connectField, filterDOMProps, joinName, useField } from 'uniforms';
import './ListDelField.css';
export type ListDelFieldProps = HTMLFieldProps<unknown, HTMLSpanElement>;
@@ -15,26 +9,19 @@ function ListDel({ disabled, name, readOnly, ...props }: ListDelFieldProps) {
const nameParts = joinName(null, name);
const nameIndex = +nameParts[nameParts.length - 1];
const parentName = joinName(nameParts.slice(0, -1));
const parent = useField<{ minCount?: number }, unknown[]>(
parentName,
{},
{ absoluteName: true },
)[0];
const parent = {
minCount: 0,
value: [],
...useField<{ minCount?: number }, unknown[]>(parentName, {}, { absoluteName: true })[0]
};
const limitNotReached =
!disabled && !(parent.minCount! >= parent.value!.length);
const limitNotReached = !disabled && !(parent.minCount >= parent.value.length);
function onAction(
event:
| React.KeyboardEvent<HTMLSpanElement>
| React.MouseEvent<HTMLSpanElement, MouseEvent>,
event: React.KeyboardEvent<HTMLSpanElement> | React.MouseEvent<HTMLSpanElement, MouseEvent>
) {
if (
limitNotReached &&
!readOnly &&
(!('key' in event) || event.key === 'Enter')
) {
const value = parent.value!.slice();
if (limitNotReached && !readOnly && (!('key' in event) || event.key === 'Enter')) {
const value = parent.value.slice();
value.splice(nameIndex, 1);
parent.onChange(value);
}
@@ -42,22 +29,20 @@ function ListDel({ disabled, name, readOnly, ...props }: ListDelFieldProps) {
return (
<span
className='autoform__list_del_field'
className="autoform__list_del_field"
{...filterDOMProps(props)}
onClick={onAction}
onKeyDown={onAction}
role="button"
tabIndex={0}
>
<div className='line'></div>
<div className="line"></div>
<TrashIcon />
</span>
);
}
export default connectField<ListDelFieldProps>(ListDel, {
initialValue: false,
kind: 'leaf',
kind: 'leaf'
});
@@ -1,8 +1,6 @@
.autoform__list_field {
margin-bottom: 1rem;
margin-top: 1rem;
padding: 10px;
border: 1px solid var(--frontmatter-list-border, rgba(255, 255, 255, 0.2));
}
}
@@ -7,6 +7,7 @@ import ListItemField from './ListItemField';
import './ListField.css';
import { LabelField } from './LabelField';
import { AutoField } from 'uniforms-unstyled';
export type ListFieldProps = HTMLFieldProps<
unknown[],
@@ -27,15 +28,15 @@ function List({
<LabelField label={label} id={props.id} required={props.required} />
{value?.map((item, itemIndex) =>
Children.map(children, (child, childIndex) =>
Children.map(children as React.ReactElement[], (child: React.ReactElement<any>, childIndex) =>
isValidElement(child)
? cloneElement(child, {
key: `${itemIndex}-${childIndex}`,
name: (child.props.name || "").replace('$', '' + itemIndex),
...itemProps,
})
: child,
),
? cloneElement(child as React.ReactElement<any>, {
key: `${itemIndex}-${childIndex}`,
name: ((child?.props as any)?.name || "").replace('$', '' + itemIndex),
...itemProps,
})
: child
)
)}
<ListAddField initialCount={initialCount} name="$" />
@@ -7,9 +7,7 @@ import ListDelField from './ListDelField';
export type ListItemFieldProps = { children?: ReactNode; value?: unknown };
function ListItem({
children = <AutoField label={null} name="" />,
}: ListItemFieldProps) {
function ListItem({ children = <AutoField label={null} name="" /> }: ListItemFieldProps) {
return (
<div>
<ListDelField name="" />
@@ -19,5 +17,5 @@ function ListItem({
}
export default connectField<ListItemFieldProps>(ListItem, {
initialValue: false,
initialValue: false
});
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Ref } from 'react';
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
import { LabelField } from './LabelField';
export type LongTextFieldProps = HTMLFieldProps<
string,
@@ -22,13 +23,13 @@ function LongText({
}: LongTextFieldProps) {
return (
<div {...filterDOMProps(props)}>
{label && <label htmlFor={id}>{label}</label>}
<LabelField label={label} id={id} required={props.required} />
<textarea
disabled={disabled}
id={id}
name={name}
onChange={event => onChange(event.target.value)}
onChange={(event) => onChange(event.target.value)}
placeholder={placeholder}
readOnly={readOnly}
ref={inputRef}
@@ -4,27 +4,14 @@ import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
import AutoField from './AutoField';
import { LabelField } from './LabelField';
export type NestFieldProps = HTMLFieldProps<
object,
HTMLDivElement,
{ itemProps?: object }
>;
export type NestFieldProps = HTMLFieldProps<object, HTMLDivElement, { itemProps?: object }>;
function Nest({
children,
fields,
itemProps,
label,
...props
}: NestFieldProps) {
function Nest({ children, fields, itemProps, label, ...props }: NestFieldProps) {
return (
<div {...filterDOMProps(props)}>
<LabelField label={label} id={props.id} required={props.required} />
{children ||
fields.map(field => (
<AutoField key={field} name={field} {...itemProps} />
))}
{children || fields.map((field) => <AutoField key={field} name={field} {...itemProps} />)}
</div>
);
}
@@ -35,7 +35,7 @@ function Num({
max={max}
min={min}
name={name}
onChange={event => {
onChange={(event) => {
const parse = decimal ? parseFloat : parseInt;
const value = parse(event.target.value);
onChange(isNaN(value) ? undefined : value);
@@ -1,3 +1,4 @@
/* eslint-disable class-methods-use-this */
import { QuickForm } from 'uniforms';
import AutoField from './AutoField';
@@ -5,6 +6,7 @@ import BaseForm from './BaseForm';
import ErrorsField from './ErrorsField';
import SubmitField from './SubmitField';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function Quick(parent: any) {
class _ extends QuickForm.Quick(parent) {
static Quick = Quick;
@@ -5,7 +5,7 @@ import { LabelField } from './LabelField';
const base64: typeof btoa =
typeof btoa === 'undefined'
? /* istanbul ignore next */ x => Buffer.from(x).toString('base64')
? /* istanbul ignore next */ (x) => Buffer.from(x).toString('base64')
: btoa;
const escape = (x: string) => base64(encodeURIComponent(x)).replace(/=+$/, '');
@@ -35,7 +35,7 @@ function Radio({
<div {...omit(filterDOMProps(props), ['checkboxes'])}>
<LabelField label={label} id={id} required={props.required} />
{allowedValues?.map(item => (
{allowedValues?.map((item) => (
<div key={item}>
<input
checked={item === value}
@@ -50,9 +50,7 @@ function Radio({
type="radio"
/>
<label htmlFor={`${id}-${escape(item)}`}>
{transform ? transform(item) : item}
</label>
<label htmlFor={`${id}-${escape(item)}`}>{transform ? transform(item) : item}</label>
</div>
))}
</div>
@@ -1,5 +1,3 @@
.autoform__select_field {
color: var(--frontmatter-select-foreground, var(--vscode-editor-foreground));
}
}
@@ -7,7 +7,7 @@ import './SelectField.css';
const base64: typeof btoa =
typeof btoa === 'undefined'
? /* istanbul ignore next */ x => Buffer.from(x).toString('base64')
? /* istanbul ignore next */ (x) => Buffer.from(x).toString('base64')
: btoa;
const escape = (x: string) => base64(encodeURIComponent(x)).replace(/=+$/, '');
@@ -24,7 +24,7 @@ export type SelectFieldProps = HTMLFieldProps<
>;
function Select({
allowedValues,
allowedValues = [],
checkboxes,
disabled,
fieldType,
@@ -38,21 +38,19 @@ function Select({
required,
disableItem,
transform,
value,
value = [],
...props
}: SelectFieldProps) {
const multiple = fieldType === Array;
return (
<div className='autoform__select_field' {...filterDOMProps(props)}>
<div className="autoform__select_field" {...filterDOMProps(props)}>
<LabelField label={label} id={id} required={required} />
{checkboxes ? (
allowedValues!.map(item => (
allowedValues.map((item) => (
<div key={item}>
<input
checked={
fieldType === Array ? value!.includes(item) : value === item
}
checked={fieldType === Array ? value.includes(item) : value === item}
disabled={disableItem?.(item) ?? disabled}
id={`${id}-${escape(item)}`}
name={name}
@@ -64,9 +62,7 @@ function Select({
type="checkbox"
/>
<label htmlFor={`${id}-${escape(item)}`}>
{transform ? transform(item) : item}
</label>
<label htmlFor={`${id}-${escape(item)}`}>{transform ? transform(item) : item}</label>
</div>
))
) : (
@@ -75,7 +71,7 @@ function Select({
id={id}
multiple={multiple}
name={name}
onChange={event => {
onChange={(event) => {
if (!readOnly) {
const item = event.target.value;
if (multiple) {
@@ -88,7 +84,7 @@ function Select({
}}
ref={inputRef}
value={value ?? ''}
style={{ width: "100%", padding: "0.5rem" }}
style={{ width: '100%', padding: '0.5rem' }}
>
{(!!placeholder || !required || value === undefined) && !multiple && (
<option value="" disabled={required} hidden={required}>
@@ -96,7 +92,7 @@ function Select({
</option>
)}
{allowedValues?.map(value => (
{allowedValues?.map((value) => (
<option disabled={disableItem?.(value)} key={value} value={value}>
{transform ? transform(value) : value}
</option>
@@ -23,7 +23,6 @@ function Text({
value,
...props
}: TextFieldProps) {
return (
<div {...filterDOMProps(props)}>
<LabelField label={label} id={id} required={props.required} />
@@ -33,7 +32,7 @@ function Text({
disabled={disabled}
id={id}
name={name}
onChange={event => onChange(event.target.value)}
onChange={(event) => onChange(event.target.value)}
placeholder={placeholder}
readOnly={readOnly}
ref={inputRef}
@@ -0,0 +1,37 @@
import { Ref } from 'react';
import * as React from 'react';
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
import { LabelField } from './LabelField';
export type UnknownFieldProps = HTMLFieldProps<
string,
HTMLDivElement,
{ inputRef?: Ref<HTMLInputElement> }
>;
function UnknownField({
autoComplete,
disabled,
id,
inputRef,
label,
name,
onChange,
placeholder,
readOnly,
type,
value,
...props
}: UnknownFieldProps) {
return (
<div {...filterDOMProps(props)}>
<LabelField label={label} id={id} required={props.required} />
<div className={`text-[var(--vscode-errorForeground)]`}>Unknown field</div>
</div>
);
}
UnknownField.defaultProps = { type: 'text' };
export default connectField<UnknownFieldProps>(UnknownField, { kind: 'leaf' });
@@ -2,6 +2,7 @@ import { ValidatedForm } from 'uniforms';
import BaseForm from './BaseForm';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function Validated(parent: any) {
class _ extends ValidatedForm.Validated(parent) {
static Validated = Validated;
+28 -28
View File
@@ -3,46 +3,46 @@ import { ContentType } from './../models/PanelSettings';
export const DEFAULT_CONTENT_TYPE_NAME = 'default';
export const DEFAULT_CONTENT_TYPE: ContentType = {
"name": "default",
"pageBundle": false,
"previewPath": null,
"fields": [
name: 'default',
pageBundle: false,
previewPath: null,
fields: [
{
"title": "Title",
"name": "title",
"type": "string"
title: 'Title',
name: 'title',
type: 'string'
},
{
"title": "Description",
"name": "description",
"type": "string"
title: 'Description',
name: 'description',
type: 'string'
},
{
"title": "Publishing date",
"name": "date",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
title: 'Publishing date',
name: 'date',
type: 'datetime',
default: '{{now}}',
isPublishDate: true
},
{
"title": "Content preview",
"name": "preview",
"type": "image"
title: 'Content preview',
name: 'preview',
type: 'image'
},
{
"title": "Is in draft",
"name": "draft",
"type": "draft"
title: 'Is in draft',
name: 'draft',
type: 'draft'
},
{
"title": "Tags",
"name": "tags",
"type": "tags"
title: 'Tags',
name: 'tags',
type: 'tags'
},
{
"title": "Categories",
"name": "categories",
"type": "categories"
title: 'Categories',
name: 'categories',
type: 'categories'
}
]
};
};
+3
View File
@@ -0,0 +1,3 @@
export const DefaultFieldValues = {
faultyCustomPlaceholder: '<failed to process>'
};
+1 -1
View File
@@ -1,7 +1,7 @@
export const DefaultFields = {
PublishingDate: `date`,
LastModified: `lastmod`,
Description: `description`,
Title: `title`,
Slug: `slug`
};
+1 -3
View File
@@ -1,3 +1 @@
export const DEFAULT_FILE_TYPES = [".md", ".markdown", ".mdx"];
export const DEFAULT_FILE_TYPES = ['.md', '.markdown', '.mdx'];
+66 -48
View File
@@ -1,4 +1,4 @@
const extensionName = "frontMatter";
const extensionName = 'frontMatter';
export const EXTENSION_ID = 'eliostruyf.vscode-front-matter';
export const EXTENSION_BETA_ID = 'eliostruyf.vscode-front-matter-beta';
@@ -8,57 +8,75 @@ export const getCommandName = (command: string) => {
};
export const COMMAND_NAME = {
init: getCommandName("init"),
insertTags: getCommandName("insertTags"),
insertCategories: getCommandName("insertCategories"),
createTag: getCommandName("createTag"),
createCategory: getCommandName("createCategory"),
exportTaxonomy: getCommandName("exportTaxonomy"),
remap: getCommandName("remap"),
setLastModifiedDate: getCommandName("setLastModifiedDate"),
generateSlug: getCommandName("generateSlug"),
createFromTemplate: getCommandName("createFromTemplate"),
toggleDraft: getCommandName("toggleDraft"),
registerFolder: getCommandName("registerFolder"),
unregisterFolder: getCommandName("unregisterFolder"),
createContent: getCommandName("createContent"),
createByContentType: getCommandName("createByContentType"),
createByTemplate: getCommandName("createByTemplate"),
createTemplate: getCommandName("createTemplate"),
initTemplate: getCommandName("initTemplate"),
collapseSections: getCommandName("collapseSections"),
preview: getCommandName("preview"),
dashboard: getCommandName("dashboard"),
dashboardMedia: getCommandName("dashboard.media"),
dashboardSnippets: getCommandName("dashboard.snippets"),
dashboardData: getCommandName("dashboard.data"),
dashboardClose: getCommandName("dashboard.close"),
promote: getCommandName("promoteSettings"),
createFolder: getCommandName("createFolder"),
diagnostics: getCommandName("diagnostics"),
modeSwitch: getCommandName("mode.switch"),
init: getCommandName('init'),
insertTags: getCommandName('insertTags'),
insertCategories: getCommandName('insertCategories'),
createTag: getCommandName('createTag'),
createCategory: getCommandName('createCategory'),
exportTaxonomy: getCommandName('exportTaxonomy'),
remap: getCommandName('remap'),
setLastModifiedDate: getCommandName('setLastModifiedDate'),
generateSlug: getCommandName('generateSlug'),
createFromTemplate: getCommandName('createFromTemplate'),
toggleDraft: getCommandName('toggleDraft'),
registerFolder: getCommandName('registerFolder'),
unregisterFolder: getCommandName('unregisterFolder'),
createContent: getCommandName('createContent'),
createByContentType: getCommandName('createByContentType'),
createByTemplate: getCommandName('createByTemplate'),
createTemplate: getCommandName('createTemplate'),
initTemplate: getCommandName('initTemplate'),
collapseSections: getCommandName('collapseSections'),
preview: getCommandName('preview'),
chatbot: getCommandName('chatbot'),
dashboard: getCommandName('dashboard'),
dashboardMedia: getCommandName('dashboard.media'),
dashboardSnippets: getCommandName('dashboard.snippets'),
dashboardData: getCommandName('dashboard.data'),
dashboardTaxonomy: getCommandName('dashboard.taxonomy'),
dashboardClose: getCommandName('dashboard.close'),
promote: getCommandName('promoteSettings'),
createFolder: getCommandName('createFolder'),
diagnostics: getCommandName('diagnostics'),
modeSwitch: getCommandName('mode.switch'),
showOutputChannel: getCommandName("showOutputChannel"),
showOutputChannel: getCommandName('showOutputChannel'),
// Insert dashboards
insertMedia: getCommandName("insertMedia"),
insertSnippet: getCommandName("insertSnippet"),
insertMedia: getCommandName('insertMedia'),
insertSnippet: getCommandName('insertSnippet'),
// WYSIWYG
bold: getCommandName("markup.bold"),
italic: getCommandName("markup.italic"),
strikethrough: getCommandName("markup.strikethrough"),
code: getCommandName("markup.code"),
codeblock: getCommandName("markup.codeblock"),
heading: getCommandName("markup.heading"),
blockquote: getCommandName("markup.blockquote"),
unorderedlist: getCommandName("markup.unorderedlist"),
orderedlist: getCommandName("markup.orderedlist"),
taskList: getCommandName("markup.tasklist"),
options: getCommandName("markup.options"),
bold: getCommandName('markup.bold'),
italic: getCommandName('markup.italic'),
strikethrough: getCommandName('markup.strikethrough'),
code: getCommandName('markup.code'),
codeblock: getCommandName('markup.codeblock'),
heading: getCommandName('markup.heading'),
blockquote: getCommandName('markup.blockquote'),
unorderedlist: getCommandName('markup.unorderedlist'),
orderedlist: getCommandName('markup.orderedlist'),
taskList: getCommandName('markup.tasklist'),
hyperlink: getCommandName('markup.hyperlink'),
options: getCommandName('markup.options'),
// Content types
generateContentType: getCommandName("contenttype.generate"),
addMissingFields: getCommandName("contenttype.addMissingFields"),
setContentType: getCommandName("contenttype.setContentType"),
};
generateContentType: getCommandName('contenttype.generate'),
addMissingFields: getCommandName('contenttype.addMissingFields'),
setContentType: getCommandName('contenttype.setContentType'),
// Project
switchProject: getCommandName('project.switch'),
// Git
gitSync: getCommandName('git.sync'),
// Authenticate
authenticate: getCommandName('authenticate'),
// Config
reloadConfig: getCommandName('config.reload'),
// Cache
clearCache: getCommandName('cache.clear')
};
+12 -5
View File
@@ -1,4 +1,3 @@
export const ExtensionState = {
PagesView: `frontMatter:Pages:ViewType`,
SelectedFolder: `frontMatter:SelectedFolder`,
@@ -6,22 +5,30 @@ export const ExtensionState = {
SettingPromoted: `frontMatter:Settings:Promoted`,
MoveTemplatesFolder: `frontMatter:Templates:Move`,
Project: {
current: `frontMatter:Project:current`
},
Dashboard: {
Contents: {
Sorting: `frontMatter:Dashboard:Contents:Sorting`,
Sorting: `frontMatter:Dashboard:Contents:Sorting`
},
Media: {
Sorting: `frontMatter:Dashboard:Media:Sorting`,
Sorting: `frontMatter:Dashboard:Media:Sorting`
},
Pages: {
Cache: `frontMatter:Dashboard:Pages:Cache`,
Index: `frontMatter:Dashboard:Pages:Index`,
Index: `frontMatter:Dashboard:Pages:Index`
}
},
Settings: {
Extends: `frontMatter:Settings:Extends`
},
Updates: {
v7_0_0: {
dateFields: `frontMatter:Updates:v7.0.0:dateFields`
}
}
};
};
+14 -13
View File
@@ -1,22 +1,23 @@
export const FEATURE_FLAG = {
panel: {
globalSettings: "panel.globalSettings",
seo: "panel.seo",
actions: "panel.actions",
metadata: "panel.metadata",
recentlyModified: "panel.recentlyModified",
otherActions: "panel.otherActions",
contentType: "panel.contentType",
globalSettings: 'panel.globalSettings',
seo: 'panel.seo',
actions: 'panel.actions',
metadata: 'panel.metadata',
recentlyModified: 'panel.recentlyModified',
otherActions: 'panel.otherActions',
contentType: 'panel.contentType'
},
dashboard: {
snippets: {
view: "dashboard.snippets.view",
manage: "dashboard.snippets.manage",
view: 'dashboard.snippets.view',
manage: 'dashboard.snippets.manage'
},
data: {
view: "dashboard.data.view",
view: 'dashboard.data.view'
},
taxonomy: {
view: 'dashboard.taxonomy.view'
}
}
};
};
+83 -48
View File
@@ -1,89 +1,124 @@
export const FrameworkDetectors = [{
export const FrameworkDetectors = [
{
framework: {
name: "gatsby",
dist: "public",
static: "static",
build: "gatsby build"
name: 'astro',
dist: 'dist',
static: 'public',
build: 'npm run build',
server: 'http://localhost:3000'
},
requiredFiles: ["gatsby-config.js"],
requiredDependencies: ["gatsby"],
requiredFiles: ['astro.config.mjs'],
requiredDependencies: ['astro'],
commands: {
start: "npx gatsby develop"
start: 'npm run dev'
}
},
{
framework: {
name: "hugo",
dist: "public",
static: "static",
build: "hugo"
name: 'gatsby',
dist: 'public',
static: 'static',
build: 'gatsby build',
server: 'http://localhost:8000'
},
requiredFiles: ["config.toml", "config.yaml", "config.yml"],
requiredFiles: ['gatsby-config.js'],
requiredDependencies: ['gatsby'],
commands: {
start: "hugo server -D"
start: 'npx gatsby develop'
}
},
{
framework: {
name: "next",
dist: ".next",
static: "public",
build: "next build"
name: 'hugo',
dist: 'public',
static: 'static',
build: 'hugo',
server: 'http://localhost:1313'
},
requiredFiles: ["next.config.js"],
requiredDependencies: ["next"],
requiredFiles: ['config.toml', 'config.yaml', 'config.yml'],
commands: {
start: "npx next dev"
start: 'hugo server -D'
}
},
{
framework: {
name: "nuxt",
dist: "dist",
static: "static",
build: "nuxt"
name: 'next',
dist: '.next',
static: 'public',
build: 'next build',
server: 'http://localhost:3000'
},
requiredFiles: ["nuxt.config.js"],
requiredDependencies: ["nuxt"],
requiredFiles: ['next.config.js'],
requiredDependencies: ['next'],
commands: {
start: "npx nuxt"
start: 'npx next dev'
}
},
{
framework: {
name: "jekyll",
dist: "_site",
static: "assets",
build: "bundle exec jekyll build"
name: 'nuxt',
dist: 'dist',
static: 'static',
build: 'nuxt',
server: 'http://localhost:3000'
},
requiredFiles: ["Gemfile"],
requiredDependencies: ["jekyll"],
requiredFiles: ['nuxt.config.js'],
requiredDependencies: ['nuxt'],
commands: {
start: "bundle exec jekyll serve --livereload"
start: 'npx nuxt'
}
},
{
framework: {
name: "docusaurus",
dist: "build",
static: "static",
build: "npx docusaurus build"
name: 'jekyll',
dist: '_site',
static: 'assets',
build: 'bundle exec jekyll build',
server: 'http://localhost:4000'
},
requiredFiles: ["docusaurus.config.js"],
requiredDependencies: ["@docusaurus/core"],
requiredFiles: ['Gemfile'],
requiredDependencies: ['jekyll'],
commands: {
start: "npx docusaurus start"
start: 'bundle exec jekyll serve --livereload'
}
},
{
framework: {
name: "11ty",
dist: "_site",
build: "npx @11ty/eleventy"
name: 'docusaurus',
dist: 'build',
static: 'static',
build: 'npx docusaurus build',
server: 'http://localhost:3000'
},
requiredDependencies: ["@11ty/eleventy"],
requiredFiles: ['docusaurus.config.js'],
requiredDependencies: ['@docusaurus/core'],
commands: {
start: "npx @11ty/eleventy --serve"
start: 'npx docusaurus start'
}
},
{
framework: {
name: '11ty',
dist: '_site',
build: 'npx @11ty/eleventy',
server: 'http://localhost:8080'
},
requiredDependencies: ['@11ty/eleventy'],
commands: {
start: 'npx @11ty/eleventy --serve'
}
},
{
framework: {
name: 'hexo',
dist: 'public',
build: 'npx hexo-cli generate',
server: 'http://localhost:4000'
},
requiredFiles: ['_config.js'],
requiredDependencies: ['hexo'],
commands: {
start: 'npx hexo-cli server'
}
}
];
];
+6 -5
View File
@@ -1,10 +1,11 @@
export const GeneralCommands = {
toWebview: {
setMode: "setMode",
setMode: 'setMode',
gitSyncingStart: 'gitSyncingStart',
gitSyncingEnd: 'gitSyncingEnd'
},
toVSCode: {
openLink: "openLink",
openLink: 'openLink',
gitSync: 'gitSync'
}
};
};
+9 -5
View File
@@ -1,6 +1,10 @@
export const GITHUB_LINK = "https://github.com/estruyf/vscode-front-matter";
export const ISSUE_LINK = "https://github.com/estruyf/vscode-front-matter/issues";
export const SPONSOR_LINK = "https://github.com/sponsors/estruyf";
export const REVIEW_LINK = "https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details";
export const GITHUB_LINK = 'https://github.com/estruyf/vscode-front-matter';
export const ISSUE_LINK = 'https://github.com/estruyf/vscode-front-matter/issues';
export const SPONSOR_LINK = 'https://github.com/sponsors/estruyf';
export const REVIEW_LINK =
'https://marketplace.visualstudio.com/items?itemName=eliostruyf.vscode-front-matter&ssr=false#review-details';
export const DOCUMENTATION_LINK = 'https://frontmatter.codes/docs';
export const DOCUMENTATION_SETTINGS_LINK = 'https://frontmatter.codes/docs/settings';
export const SENTRY_LINK = "https://1ac45704bbe74264a7b4674bdc2abf48@o1022172.ingest.sentry.io/5988293";
export const SENTRY_LINK =
'https://1ac45704bbe74264a7b4674bdc2abf48@o1022172.ingest.sentry.io/5988293';
+6 -7
View File
@@ -1,8 +1,7 @@
export const LocalStore = {
rootFolder: ".frontmatter",
contentFolder: "content",
templatesFolder: "templates",
mediaDatabaseFile: "mediaDb.json"
}
rootFolder: '.frontmatter',
contentFolder: 'content',
databaseFolder: 'database',
templatesFolder: 'templates',
mediaDatabaseFile: 'mediaDb.json'
};
+1 -1
View File
@@ -1 +1 @@
export const HOME_PAGE_NAVIGATION_ID = "FrontMatter:RootFolder";
export const HOME_PAGE_NAVIGATION_ID = 'FrontMatter:RootFolder';

Some files were not shown because too many files have changed in this diff Show More