diff --git a/.vscodeignore b/.vscodeignore index 5d0c0da3..0c1c68c9 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -17,4 +17,8 @@ postcss.config.js .templates .github scripts -.all-contributorsrc \ No newline at end of file +.all-contributorsrc +assets/v2.* +assets/v3.* +assets/v4.* +assets/sponsors diff --git a/CHANGELOG.md b/CHANGELOG.md index f692c820..b7a38790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Change Log +## [5.6.0] - 2021-11-23 + +### 🎨 Enhancements + +- Updated camera icon from VS Code to media icon +- Updated the media card actions to show it within a menu. This will give a better experience with custom scripts. +- [#97](https://github.com/estruyf/vscode-front-matter/issues/97): Custom Script support for media files and folders +- [#178](https://github.com/estruyf/vscode-front-matter/issues/178): Sorting added to the media dashboard +- [#179](https://github.com/estruyf/vscode-front-matter/issues/179): Updated the `open dashboard` icon to make it easier to spot it +- [#180](https://github.com/estruyf/vscode-front-matter/issues/180): Added `{filename}` as placeholder for media snippets +- [#181](https://github.com/estruyf/vscode-front-matter/issues/181): Support for custom taxonomy fields added + +### 🐞 Fixes + +- [#183](https://github.com/estruyf/vscode-front-matter/issues/183): Fix type error on the `frontMatter.content.sorting` setting + ## [5.5.0] - 2021-11-15 As from this version onwards, the extension will be published to [open-vsx.org](https://open-vsx.org/). diff --git a/assets/icons/frontmatter-small-dark.svg b/assets/icons/frontmatter-small-dark.svg new file mode 100644 index 00000000..4154f969 --- /dev/null +++ b/assets/icons/frontmatter-small-dark.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/assets/icons/frontmatter-small-light.svg b/assets/icons/frontmatter-small-light.svg new file mode 100644 index 00000000..30527cad --- /dev/null +++ b/assets/icons/frontmatter-small-light.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/assets/icons/frontmatter-small-teal.svg b/assets/icons/frontmatter-small-teal.svg new file mode 100644 index 00000000..61c61bd9 --- /dev/null +++ b/assets/icons/frontmatter-small-teal.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/assets/icons/media-dark.svg b/assets/icons/media-dark.svg new file mode 100644 index 00000000..65377af6 --- /dev/null +++ b/assets/icons/media-dark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/icons/media-light.svg b/assets/icons/media-light.svg new file mode 100644 index 00000000..a382f5cc --- /dev/null +++ b/assets/icons/media-light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c2c7d87f..0037f791 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,145 +1,9 @@ { "name": "vscode-front-matter-beta", - "version": "5.5.0", + "version": "5.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { - "@algolia/autocomplete-core": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.2.2.tgz", - "integrity": "sha512-JOQaURze45qVa8OOFDh+ozj2a/ObSRsVyz6Zd0aiBeej+RSTqrr1hDVpGNbbXYLW26G5ujuc9QIdH+rBHn95nw==", - "requires": { - "@algolia/autocomplete-shared": "1.2.2" - } - }, - "@algolia/autocomplete-preset-algolia": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.2.2.tgz", - "integrity": "sha512-AZkh+bAMaJDzMZTelFOXJTJqkp5VPGH8W3n0B+Ggce7DdozlMRsDLguKTCQAkZ0dJ1EbBPyFL5ztL/JImB137Q==", - "requires": { - "@algolia/autocomplete-shared": "1.2.2" - } - }, - "@algolia/autocomplete-shared": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.2.2.tgz", - "integrity": "sha512-mLTl7d2C1xVVazHt/bqh9EE/u2lbp5YOxLDdcjILXmUqOs5HH1D4SuySblXaQG1uf28FhTqMGp35qE5wJQnqAw==" - }, - "@algolia/cache-browser-local-storage": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.5.tgz", - "integrity": "sha512-cfX2rEKOtuuljcGI5DMDHClwZHdDqd2nT2Ohsc8aHtBiz6bUxKVyIqxr2gaC6tU8AgPtrTVBzcxCA+UavXpKww==", - "requires": { - "@algolia/cache-common": "4.10.5" - } - }, - "@algolia/cache-common": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.10.5.tgz", - "integrity": "sha512-1mClwdmTHll+OnHkG+yeRoFM17kSxDs4qXkjf6rNZhoZGXDvfYLy3YcZ1FX4Kyz0DJv8aroq5RYGBDsWkHj6Tw==" - }, - "@algolia/cache-in-memory": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.10.5.tgz", - "integrity": "sha512-+ciQnfIGi5wjMk02XhEY8fmy2pzy+oY1nIIfu8LBOglaSipCRAtjk6WhHc7/KIbXPiYzIwuDbM2K1+YOwSGjwA==", - "requires": { - "@algolia/cache-common": "4.10.5" - } - }, - "@algolia/client-account": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.10.5.tgz", - "integrity": "sha512-I9UkSS2glXm7RBZYZIALjBMmXSQbw/fI/djPcBHxiwXIheNIlqIFl2SNPkvihpPF979BSkzjqdJNRPhE1vku3Q==", - "requires": { - "@algolia/client-common": "4.10.5", - "@algolia/client-search": "4.10.5", - "@algolia/transporter": "4.10.5" - } - }, - "@algolia/client-analytics": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.10.5.tgz", - "integrity": "sha512-h2owwJSkovPxzc+xIsjY1pMl0gj+jdVwP9rcnGjlaTY2fqHbSLrR9yvGyyr6305LvTppxsQnfAbRdE/5Z3eFxw==", - "requires": { - "@algolia/client-common": "4.10.5", - "@algolia/client-search": "4.10.5", - "@algolia/requester-common": "4.10.5", - "@algolia/transporter": "4.10.5" - } - }, - "@algolia/client-common": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.10.5.tgz", - "integrity": "sha512-21FAvIai5qm8DVmZHm2Gp4LssQ/a0nWwMchAx+1hIRj1TX7OcdW6oZDPyZ8asQdvTtK7rStQrRnD8a95SCUnzA==", - "requires": { - "@algolia/requester-common": "4.10.5", - "@algolia/transporter": "4.10.5" - } - }, - "@algolia/client-personalization": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.10.5.tgz", - "integrity": "sha512-nH+IyFKBi8tCyzGOanJTbXC5t4dspSovX3+ABfmwKWUYllYzmiQNFUadpb3qo+MLA3jFx5IwBesjneN6dD5o3w==", - "requires": { - "@algolia/client-common": "4.10.5", - "@algolia/requester-common": "4.10.5", - "@algolia/transporter": "4.10.5" - } - }, - "@algolia/client-search": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.10.5.tgz", - "integrity": "sha512-1eQFMz9uodrc5OM+9HeT+hHcfR1E1AsgFWXwyJ9Q3xejA2c1c4eObGgOgC9ZoshuHHdptaTN1m3rexqAxXRDBg==", - "requires": { - "@algolia/client-common": "4.10.5", - "@algolia/requester-common": "4.10.5", - "@algolia/transporter": "4.10.5" - } - }, - "@algolia/logger-common": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.10.5.tgz", - "integrity": "sha512-gRJo9zt1UYP4k3woEmZm4iuEBIQd/FrArIsjzsL/b+ihNoOqIxZKTSuGFU4UUZOEhvmxDReiA4gzvQXG+TMTmA==" - }, - "@algolia/logger-console": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.10.5.tgz", - "integrity": "sha512-4WfIbn4253EDU12u9UiYvz+QTvAXDv39mKNg9xSoMCjKE5szcQxfcSczw2byc6pYhahOJ9PmxPBfs1doqsdTKQ==", - "requires": { - "@algolia/logger-common": "4.10.5" - } - }, - "@algolia/requester-browser-xhr": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.10.5.tgz", - "integrity": "sha512-53/MURQEqtK+bGdfq4ITSPwTh5hnADU99qzvpAINGQveUFNSFGERipJxHjTJjIrjFz3vxj5kKwjtxDnU6ygO9g==", - "requires": { - "@algolia/requester-common": "4.10.5" - } - }, - "@algolia/requester-common": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.10.5.tgz", - "integrity": "sha512-UkVa1Oyuj6NPiAEt5ZvrbVopEv1m/mKqjs40KLB+dvfZnNcj+9Fry4Oxnt15HMy/HLORXsx4UwcthAvBuOXE9Q==" - }, - "@algolia/requester-node-http": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.10.5.tgz", - "integrity": "sha512-aNEKVKXL4fiiC+bS7yJwAHdxln81ieBwY3tsMCtM4zF9f5KwCzY2OtN4WKEZa5AAADVcghSAUdyjs4AcGUlO5w==", - "requires": { - "@algolia/requester-common": "4.10.5" - } - }, - "@algolia/transporter": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.10.5.tgz", - "integrity": "sha512-F8DLkmIlvCoMwSCZA3FKHtmdjH3o5clbt0pi2ktFStVNpC6ZDmY307HcK619bKP5xW6h8sVJhcvrLB775D2cyA==", - "requires": { - "@algolia/cache-common": "4.10.5", - "@algolia/logger-common": "4.10.5", - "@algolia/requester-common": "4.10.5" - } - }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -184,31 +48,6 @@ "lit-element": "^2.5.1" } }, - "@docsearch/css": { - "version": "3.0.0-alpha.40", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0-alpha.40.tgz", - "integrity": "sha512-PrOTPgJMl+Iji1zOH0+J0PEDMriJ1teGxbgll7o4h8JrvJW6sJGqQw7/bLW7enWiFaxbJMK76w1yyPNLFHV7Qg==" - }, - "@docsearch/js": { - "version": "3.0.0-alpha.40", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.0.0-alpha.40.tgz", - "integrity": "sha512-0ysRM0jk1KAbw/QsHPHIKoa7OeCm2Mwz0JgcPnhWRvvA28dzP+f6OIsL6eGu3VJR043tH9OrvVf/FnvLtTtZtw==", - "requires": { - "@docsearch/react": "3.0.0-alpha.40", - "preact": "^10.0.0" - } - }, - "@docsearch/react": { - "version": "3.0.0-alpha.40", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0-alpha.40.tgz", - "integrity": "sha512-aKxnu7sgpP1R7jtgOV/pZdJEHXx6Ts+jnS9U/ejSUS2BMUpwQI5SA3oLs1BA5TA9kIViJ5E+rrjh0VsbcsJ6sQ==", - "requires": { - "@algolia/autocomplete-core": "1.2.2", - "@algolia/autocomplete-preset-algolia": "1.2.2", - "@docsearch/css": "3.0.0-alpha.40", - "algoliasearch": "^4.0.0" - } - }, "@estruyf/vscode": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/@estruyf/vscode/-/vscode-0.0.2.tgz", @@ -892,27 +731,6 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "algoliasearch": { - "version": "4.10.5", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.10.5.tgz", - "integrity": "sha512-KmH2XkiN+8FxhND4nWFbQDkIoU6g2OjfeU9kIv4Lb+EiOOs3Gpp7jvd+JnatsCisAZsnWQdjd7zVlW7I/85QvQ==", - "requires": { - "@algolia/cache-browser-local-storage": "4.10.5", - "@algolia/cache-common": "4.10.5", - "@algolia/cache-in-memory": "4.10.5", - "@algolia/client-account": "4.10.5", - "@algolia/client-analytics": "4.10.5", - "@algolia/client-common": "4.10.5", - "@algolia/client-personalization": "4.10.5", - "@algolia/client-search": "4.10.5", - "@algolia/logger-common": "4.10.5", - "@algolia/logger-console": "4.10.5", - "@algolia/requester-browser-xhr": "4.10.5", - "@algolia/requester-common": "4.10.5", - "@algolia/requester-node-http": "4.10.5", - "@algolia/transporter": "4.10.5" - } - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -4849,11 +4667,6 @@ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, - "preact": { - "version": "10.5.14", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz", - "integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ==" - }, "pretty-error": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", diff --git a/package.json b/package.json index b209ecce..8e3831e4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter", "description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "5.5.0", + "version": "5.6.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -97,6 +97,25 @@ "markdownDescription": "Specify if you want to automatically update the modified date of your article/page. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.autoupdatedate)", "scope": "Content" }, + "frontMatter.content.defaultSorting": { + "type": "string", + "default": "", + "oneOf": [ + { + "enum": [ + "LastModifiedAsc", + "LastModifiedDesc", + "FileNameAsc", + "FileNameDesc" + ] + }, + { + "type": "string" + } + ], + "markdownDescription": "Specify the default sorting option for the content dashboard. You can use one of the values from the enum or define your own ID. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.sorting.default)", + "scope": "Content" + }, "frontMatter.content.draftField": { "type": "object", "markdownDescription": "Define the draft field you want to use to manage your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.draftField)", @@ -176,12 +195,16 @@ "scope": "Content" }, "frontMatter.content.sorting": { - "type": "object", + "type": "array", "default": [], "markdownDescription": "Define the sorting options for your dashboard content. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.sorting)", "items": { "type": "object", "properties": { + "id": { + "type": "string", + "description": "The ID of the sorting option. This will be used for the storing the last used sorting option or the default option." + }, "title": { "type": "string", "description": "Name of the sorting label" @@ -251,6 +274,16 @@ "outputType": { "type": "string", "description": "The type of output for the editor panel. Can be used to change it to 'markdown' for example" + }, + "type": { + "type": "string", + "default": "content", + "enum": [ + "content", + "mediaFolder", + "mediaFile" + ], + "description": "The type for which the script will be used." } }, "additionalProperties": false, @@ -285,6 +318,18 @@ "default": "", "markdownDescription": "Specify the ID of your static site generator or framework you are using for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.framework.id)" }, + "frontMatter.media.defaultSorting": { + "type": "string", + "default": "", + "enum": [ + "LastModifiedAsc", + "LastModifiedDesc", + "FileNameAsc", + "FileNameDesc" + ], + "markdownDescription": "Specify the default sorting option for the media dashboard. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.media.sorting.default)", + "scope": "Content" + }, "frontMatter.panel.freeform": { "type": "boolean", "default": true, @@ -362,6 +407,7 @@ "boolean", "image", "choice", + "taxonomy", "tags", "categories", "draft" @@ -418,12 +464,47 @@ "type": "boolean", "default": false, "description": "Do you want to hide the field from the metadata section?" + }, + "taxonomyId": { + "type": "string", + "default": "", + "description": "The ID of your taxonomy field" } }, "additionalProperties": false, "required": [ "type", "name" + ], + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "taxonomy" + } + } + }, + "then": { + "required": [ + "taxonomyId" + ] + } + }, + { + "if": { + "properties": { + "type": { + "const": "choice" + } + } + }, + "then": { + "required": [ + "choices" + ] + } + } ] } }, @@ -492,6 +573,32 @@ ], "scope": "Taxonomy" }, + "frontMatter.taxonomy.customTaxonomy": { + "type": "array", + "markdownDescription": "Specify the custom taxonomy field data. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.tags)", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID for your taxonomy field" + }, + "options": { + "type": "array", + "description": "Options from which you can pick", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "id", + "options" + ] + }, + "scope": "Taxonomy" + }, "frontMatter.taxonomy.dateField": { "type": "string", "default": "date", @@ -660,7 +767,10 @@ "command": "frontMatter.insertImage", "title": "Insert image into your content", "category": "Front matter", - "icon": "$(device-camera)" + "icon": { + "dark": "/assets/icons/media-dark.svg", + "light": "/assets/icons/media-light.svg" + } }, { "command": "frontMatter.insertTags", @@ -676,7 +786,19 @@ "command": "frontMatter.dashboard", "title": "Open dashboard", "category": "Front matter", - "icon": "$(preview)" + "icon": { + "dark": "/assets/icons/frontmatter-small-dark.svg", + "light": "/assets/icons/frontmatter-small-light.svg" + } + }, + { + "command": "frontMatter.dashboard.close", + "title": "Close dashboard", + "category": "Front matter", + "icon": { + "dark": "/assets/icons/frontmatter-small-teal.svg", + "light": "/assets/icons/frontmatter-small-teal.svg" + } }, { "command": "frontMatter.preview", @@ -709,7 +831,12 @@ { "command": "frontMatter.dashboard", "group": "navigation@-98", - "when": "frontMatter:enabled == true" + "when": "frontMatter:enabled == true && frontMatter:dashboard:open == false" + }, + { + "command": "frontMatter.dashboard.close", + "group": "navigation@-98", + "when": "frontMatter:enabled == true && frontMatter:dashboard:open == true" } ], "explorer/context": [ @@ -757,6 +884,10 @@ { "command": "frontMatter.createFromTemplate", "when": "false" + }, + { + "command": "frontMatter.dashboard.close", + "when": "false" } ], "view/title": [ @@ -836,7 +967,5 @@ "webpack": "4.44.2", "webpack-cli": "3.3.12" }, - "dependencies": { - "@docsearch/js": "^3.0.0-alpha.40" - } + "dependencies": {} } \ No newline at end of file diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index ff31c1c3..a765d3d7 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -1,10 +1,10 @@ -import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING } from '../constants'; +import { SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTING_TAXONOMY_CONTENT_TYPES, DefaultFields, HOME_PAGE_NAVIGATION_ID, ExtensionState, COMMAND_NAME, SETTINGS_FRAMEWORK_ID, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, CONTEXT, SETTING_CUSTOM_SCRIPTS, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_MEDIA_SORTING_DEFAULT } from '../constants'; import { ArticleHelper } from './../helpers/ArticleHelper'; import { basename, dirname, extname, join, parse } from "path"; import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs"; import { commands, Uri, ViewColumn, Webview, WebviewPanel, window, workspace, env, Position } from "vscode"; import { Settings as SettingsHelper } from '../helpers'; -import { DraftField, Framework, SortingSetting, TaxonomyType } from '../models'; +import { CustomScript as ICustomScript, DraftField, Framework, ScriptType, SortingSetting, SortOrder, SortType, TaxonomyType } from '../models'; import { Folders } from './Folders'; import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; import { DashboardMessage } from '../dashboardWebView/DashboardMessage'; @@ -21,11 +21,14 @@ import { decodeBase64Image } from '../helpers/decodeBase64Image'; import { DashboardData } from '../models/DashboardData'; import { ExplorerView } from '../explorerView/ExplorerView'; import { MediaLibrary } from '../helpers/MediaLibrary'; -import imageSize from 'image-size'; import { parseWinPath } from '../helpers/parseWinPath'; import { DateHelper } from '../helpers/DateHelper'; import { FrameworkDetector } from '../helpers/FrameworkDetector'; import { ContentType } from '../helpers/ContentType'; +import { SortingOption } from '../dashboardWebView/models'; +import { Sorting } from '../helpers/Sorting'; +import imageSize from 'image-size'; +import { CustomScript } from '../helpers/CustomScript'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -62,6 +65,8 @@ export class Dashboard { } else { Dashboard.create(); } + + await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, true); } /** @@ -79,6 +84,10 @@ export class Dashboard { Dashboard.webview.reveal(); } } + + public static close() { + Dashboard.webview?.dispose(); + } /** * Create the dashboard webview @@ -105,19 +114,22 @@ export class Dashboard { Dashboard.webview.webview.html = Dashboard.getWebviewContent(Dashboard.webview.webview, extensionUri); - Dashboard.webview.onDidChangeViewState(() => { + Dashboard.webview.onDidChangeViewState(async () => { if (!this.webview?.visible) { Dashboard._viewData = undefined; const panel = ExplorerView.getInstance(extensionUri); panel.getMediaSelection(); } + + await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, this.webview?.visible); }); - Dashboard.webview.onDidDispose(() => { + Dashboard.webview.onDidDispose(async () => { Dashboard.isDisposed = true; Dashboard._viewData = undefined; const panel = ExplorerView.getInstance(extensionUri); panel.getMediaSelection(); + await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false); }); SettingsHelper.onConfigChange((global?: any) => { @@ -165,7 +177,7 @@ export class Dashboard { Extension.getInstance().setState(ExtensionState.PagesView, msg.data, "workspace"); break; case DashboardMessage.getMedia: - Dashboard.getMedia(msg?.data?.page, msg?.data?.folder); + Dashboard.getMedia(msg?.data?.page, msg?.data?.folder, msg?.data?.sorting); break; case DashboardMessage.copyToClipboard: env.clipboard.writeText(msg.data); @@ -192,6 +204,9 @@ export class Dashboard { case DashboardMessage.setFramework: Dashboard.setFramework(msg?.data); break; + case DashboardMessage.runCustomScript: + CustomScript.run(msg?.data?.script, msg?.data?.path); + break; case DashboardMessage.setState: if (msg?.data?.key && msg?.data?.value) { Extension.getInstance().setState(msg?.data?.key, msg?.data?.value, "workspace"); @@ -212,6 +227,20 @@ export class Dashboard { Dashboard.resetMedia(); Dashboard.getMedia(0, folderPath); } + + /** + * Post data to the dashboard + * @param msg + */ + public static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) { + if (Dashboard.isDisposed) { + return; + } + + if (Dashboard.webview) { + Dashboard.webview?.webview.postMessage(msg); + } + } /** * Insert an image into the front matter or contents @@ -289,8 +318,16 @@ export class Dashboard { contentFolders: Folders.get(), crntFramework: SettingsHelper.get(SETTINGS_FRAMEWORK_ID), framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null, + scripts: (SettingsHelper.get(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type && s.type !== ScriptType.Content), dashboardState: { - sorting: await ext.getState(ExtensionState.Dashboard.Sorting, "workspace") + contents: { + sorting: await ext.getState(ExtensionState.Dashboard.Contents.Sorting, "workspace"), + defaultSorting: SettingsHelper.get(SETTINGS_CONTENT_SORTING_DEFAULT) + }, + media: { + sorting: await ext.getState(ExtensionState.Dashboard.Media.Sorting, "workspace"), + defaultSorting: SettingsHelper.get(SETTINGS_MEDIA_SORTING_DEFAULT) + } } } as Settings }); @@ -325,16 +362,19 @@ export class Dashboard { /** * Retrieve all media files */ - private static async getMedia(page: number = 0, requestedFolder: string = '') { + private static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) { const wsFolder = Folders.getWorkspaceFolder(); const staticFolder = SettingsHelper.get(SETTINGS_CONTENT_STATIC_FOLDER); const contentFolders = Folders.get(); const viewData = Dashboard.viewData; let selectedFolder = requestedFolder; + const ext = Extension.getInstance(); + const crntSort = sort === null ? await ext.getState(ExtensionState.Dashboard.Media.Sorting, "workspace") : sort; + // If the static folder is not set, retreive the last opened location if (!selectedFolder) { - const stateValue = await Extension.getInstance().getState(ExtensionState.SelectedFolder, "workspace"); + const stateValue = await ext.getState(ExtensionState.SelectedFolder, "workspace"); if (stateValue !== HOME_PAGE_NAVIGATION_ID) { // Support for page bundles @@ -366,13 +406,14 @@ export class Dashboard { if (relSelectedFolderPath) { const files = await workspace.findFiles(join(relSelectedFolderPath, '/*')); - const media = Dashboard.filterMedia(files); + const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files)); + allMedia = [...media]; } else { if (staticFolder) { const folderSearch = join(staticFolder || "", '/*'); const files = await workspace.findFiles(folderSearch); - const media = Dashboard.filterMedia(files); + const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files)); allMedia = [...media]; } @@ -383,22 +424,24 @@ export class Dashboard { const relFolderPath = contentFolder.path.substring(wsFolder.fsPath.length + 1); const folderSearch = relSelectedFolderPath ? join(relSelectedFolderPath, '/*') : join(relFolderPath, '/*'); const files = await workspace.findFiles(folderSearch); - const media = Dashboard.filterMedia(files); + const media = await Dashboard.updateMediaData(Dashboard.filterMedia(files)); allMedia = [...allMedia, ...media]; } } } - allMedia = allMedia.sort((a, b) => { - if (b.fsPath < a.fsPath) { - return -1; - } - if (b.fsPath > a.fsPath) { - return 1; - } - return 0; - }); + if (crntSort?.type === SortType.string) { + allMedia = allMedia.sort(Sorting.alphabetically("fsPath")); + } else if (crntSort?.type === SortType.date) { + allMedia = allMedia.sort(Sorting.date("mtime")); + } else { + allMedia = allMedia.sort(Sorting.alphabetically("fsPath")); + } + + if (crntSort?.order === SortOrder.desc) { + allMedia = allMedia.reverse(); + } Dashboard.media = Object.assign([], allMedia); @@ -415,15 +458,14 @@ export class Dashboard { return { ...file, - stats: statSync(file.fsPath), dimensions: imageSize(file.fsPath), ...metadata }; } catch (e) { - return {...file, stats: undefined}; + return {...file}; } }); - files = files.filter(f => f.stats !== undefined); + files = files.filter(f => f.mtime !== undefined); // Retrieve all the folders let allContentFolders: string[] = []; @@ -450,18 +492,46 @@ export class Dashboard { // Store the last opened folder await Extension.getInstance().setState(ExtensionState.SelectedFolder, requestedFolder === HOME_PAGE_NAVIGATION_ID ? HOME_PAGE_NAVIGATION_ID : selectedFolder, "workspace"); + + let sortedFolders = [...allContentFolders, ...allFolders]; + sortedFolders = sortedFolders.sort((a, b) => { + if (a.toLowerCase() < b.toLowerCase()) { + return -1; + } + if (a.toLowerCase() > b.toLowerCase()) { + return 1; + } + return 0; + }); + + if (crntSort?.order === SortOrder.desc) { + sortedFolders = sortedFolders.reverse(); + } Dashboard.postWebviewMessage({ command: DashboardCommand.media, data: { media: files, total: total, - folders: [...allContentFolders, ...allFolders], + folders: sortedFolders, selectedFolder } as MediaPaths }); } + /** + * Update the metadata of the retrieved files + * @param files + */ + private static async updateMediaData(files: MediaInfo[]) { + files = files.map((m: MediaInfo) => { + const stats = statSync(m.fsPath); + return Object.assign({}, m, stats); + }); + + return Object.assign([], files); + } + /** * Retrieve all the markdown pages */ @@ -642,20 +712,6 @@ export class Dashboard { Dashboard.getMedia(page || 0, folder || ""); } - - /** - * Post data to the dashboard - * @param msg - */ - private static postWebviewMessage(msg: { command: DashboardCommand, data?: any }) { - if (Dashboard.isDisposed) { - return; - } - - if (Dashboard.webview) { - Dashboard.webview?.webview.postMessage(msg); - } - } /** * Retrieve the webview HTML contents diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 9a586a11..19653ab2 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -6,7 +6,7 @@ import { ContentFolder, FileInfo, FolderInfo } from "../models"; import uniqBy = require("lodash.uniqby"); import { Template } from "./Template"; import { Notifications } from "../helpers/Notifications"; -import { Settings } from "../helpers"; +import { FilesHelper, Settings } from "../helpers"; import { existsSync, mkdirSync } from 'fs'; import { format } from 'date-fns'; import { Dashboard } from './Dashboard'; @@ -219,7 +219,9 @@ export class Folders { for (const file of files) { try { const fileName = basename(file.fsPath); + const stats = await workspace.fs.stat(file); + fileStats.push({ filePath: file.fsPath, fileName, diff --git a/src/components/icons/CompressIcon.tsx b/src/components/icons/CompressIcon.tsx new file mode 100644 index 00000000..35ee5d66 --- /dev/null +++ b/src/components/icons/CompressIcon.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; + +export interface ICompressIconProps { + className?: string; +} + +export const CompressIcon: React.FunctionComponent = ({className}: React.PropsWithChildren) => { + return ( + + ); +}; \ No newline at end of file diff --git a/src/constants/ContentType.ts b/src/constants/ContentType.ts index daf1e852..a2ea1003 100644 --- a/src/constants/ContentType.ts +++ b/src/constants/ContentType.ts @@ -23,7 +23,7 @@ export const DEFAULT_CONTENT_TYPE: ContentType = { "type": "datetime" }, { - "title": "Article preview", + "title": "Content preview", "name": "preview", "type": "image" }, diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 05c71422..49ce954d 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -28,6 +28,7 @@ export const COMMAND_NAME = { collapseSections: getCommandName("collapseSections"), preview: getCommandName("preview"), dashboard: getCommandName("dashboard"), + dashboardClose: getCommandName("dashboard.close"), promote: getCommandName("promoteSettings"), insertImage: getCommandName("insertImage"), createFolder: getCommandName("createFolder"), diff --git a/src/constants/ExtensionState.ts b/src/constants/ExtensionState.ts index d486d45a..36139f50 100644 --- a/src/constants/ExtensionState.ts +++ b/src/constants/ExtensionState.ts @@ -7,6 +7,11 @@ export const ExtensionState = { MoveTemplatesFolder: `frontMatter:Templates:Move`, Dashboard: { - Sorting: `frontMatter:Dashboard:Sorting`, + Contents: { + Sorting: `frontMatter:Dashboard:Contents:Sorting`, + }, + Media: { + Sorting: `frontMatter:Dashboard:Media:Sorting`, + } } }; \ No newline at end of file diff --git a/src/constants/context.ts b/src/constants/context.ts index 3adfb320..a8754720 100644 --- a/src/constants/context.ts +++ b/src/constants/context.ts @@ -2,5 +2,6 @@ export const CONTEXT = { canInit: "frontMatterCanInit", canOpenPreview: "frontMatterCanOpenPreview", canOpenDashboard: "frontMatterCanOpenDashboard", - isEnabled: "frontMatter:enabled" + isEnabled: "frontMatter:enabled", + isDashboardOpen: "frontMatter:dashboard:open", }; \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index b0a0f2e2..06eb3bb0 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -4,6 +4,8 @@ export const CONFIG_KEY = "frontMatter"; export const SETTING_TAXONOMY_TAGS = "taxonomy.tags"; export const SETTING_TAXONOMY_CATEGORIES = "taxonomy.categories"; +export const SETTING_TAXONOMY_CUSTOM = "taxonomy.customTaxonomy"; + export const SETTING_DATE_FORMAT = "taxonomy.dateFormat"; export const SETTING_COMMA_SEPARATED_FIELDS = "taxonomy.commaSeparatedFields"; export const SETTING_TAXONOMY_CONTENT_TYPES = "taxonomy.contentTypes"; @@ -42,6 +44,9 @@ export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight"; export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField"; export const SETTINGS_CONTENT_SORTING = "content.sorting"; +export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting"; +export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting"; + export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; diff --git a/src/dashboardWebView/DashboardCommand.ts b/src/dashboardWebView/DashboardCommand.ts index 42442972..a44b26a0 100644 --- a/src/dashboardWebView/DashboardCommand.ts +++ b/src/dashboardWebView/DashboardCommand.ts @@ -3,5 +3,6 @@ export enum DashboardCommand { pages = "pages", settings = "settings", media = "media", - viewData = "viewData" + viewData = "viewData", + mediaUpdate = "mediaUpdate" } \ No newline at end of file diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 46ecabcd..23dc81de 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -20,4 +20,5 @@ export enum DashboardMessage { createMediaFolder = 'createMediaFolder', setFramework = 'setFramework', setState = 'setState', + runCustomScript = 'runCustomScript', } \ No newline at end of file diff --git a/src/dashboardWebView/components/ChoiceButton.tsx b/src/dashboardWebView/components/ChoiceButton.tsx index 430d0b6c..c027d36b 100644 --- a/src/dashboardWebView/components/ChoiceButton.tsx +++ b/src/dashboardWebView/components/ChoiceButton.tsx @@ -28,7 +28,7 @@ export const ChoiceButton: React.FunctionComponent = ({onCli Open options