From 46a9d6e6020c6d362e7e8e574cd158d09411b1a6 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 11 Jan 2022 08:55:06 +0100 Subject: [PATCH 01/41] 6.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index de56a859..a6f80078 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-front-matter-beta", - "version": "5.10.0", + "version": "6.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-front-matter-beta", - "version": "5.10.0", + "version": "6.0.0", "license": "MIT", "devDependencies": { "@bendera/vscode-webview-elements": "0.6.2", diff --git a/package.json b/package.json index 5da18629..f2537f6e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter", "description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "5.10.0", + "version": "6.0.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { From 9a91be80251950fbd2bb8dd338f1347aa5f68c6e Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 11 Jan 2022 12:23:46 +0100 Subject: [PATCH 02/41] #197 - Support for multi-dimensional content type fields --- CHANGELOG.md | 6 +++ assets/media/styles.css | 21 ++++++++ package.json | 25 ++++++++- src/explorerView/ExplorerView.ts | 18 ++++--- src/helpers/ContentType.ts | 35 ++++++++---- src/models/PanelSettings.ts | 4 +- src/panelWebView/components/Metadata.tsx | 69 ++++++++++++++++-------- 7 files changed, 137 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dc73ce0..b04f006c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## [6.0.0] - 2022-01-xx + +### 🎨 Enhancements + +- [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. + ## [5.10.0] - 2022-01-10 ### 🎨 Enhancements diff --git a/assets/media/styles.css b/assets/media/styles.css index d9cb4f9f..c193c441 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -446,12 +446,33 @@ input:checked + .field__toggle__slider:before { margin-bottom: 1rem; } +.vscode-dark .metadata_field__box { + background: rgba(255, 255, 255, 0.1); + border: 2px dashed rgba(255, 255, 255, 0.2); +} + +.vscode-light .metadata_field__box { + background: rgba(0, 0, 0, 0.1); + border: 2px dashed rgba(0, 0, 0, 0.2); +} + +.metadata_field__box { + background: rgba(255, 255, 255, 0.1); + border: 2px 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; } diff --git a/package.json b/package.json index f2537f6e..1f90d11d 100644 --- a/package.json +++ b/package.json @@ -417,6 +417,7 @@ "description": "Specifies the type of content you want to create." }, "fields": { + "$id": "#contenttypefield", "type": "array", "description": "Define the fields of the content type", "items": { @@ -435,7 +436,8 @@ "taxonomy", "tags", "categories", - "draft" + "draft", + "object" ], "description": "Define the type of field" }, @@ -447,6 +449,10 @@ "type": "string", "description": "Title to show in the UI" }, + "default": { + "type": "string", + "description": "Default value" + }, "choices": { "type": "array", "description": "Define your choices", @@ -494,7 +500,8 @@ "type": "string", "default": "", "description": "The ID of your taxonomy field" - } + }, + "fields": { "$ref": "#contenttypefield" } }, "additionalProperties": false, "required": [ @@ -529,6 +536,20 @@ "choices" ] } + }, + { + "if": { + "properties": { + "type": { + "const": "object" + } + } + }, + "then": { + "required": [ + "fields" + ] + } } ] } diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index 9d01b868..e278ebe6 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -314,7 +314,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { /** * Update the metadata of the article */ - public async updateMetadata({field, value }: { field: string, value: any, fieldData?: { multiple: boolean, value: string[] } }) { + public async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) { if (!field) { return; } @@ -333,12 +333,18 @@ export class ExplorerView implements WebviewViewProvider, Disposable { const dateFields = contentType.fields.filter((field) => field.type === "datetime"); const imageFields = contentType.fields.filter((field) => field.type === "image" && field.multiple); + // Support multi-level fields + let parentObj = article.data; + for (const parent of parents || []) { + parentObj = parentObj[parent]; + } + for (const dateField of dateFields) { if ((field === dateField.name) && value) { - article.data[field] = Article.formatDate(new Date(value)); + parentObj[field] = Article.formatDate(new Date(value)); } else if (!imageFields.find(f => f.name === field)) { // Only override the field data if it is not an multiselect image field - article.data[field] = value; + parentObj[field] = value; } } @@ -346,15 +352,15 @@ export class ExplorerView implements WebviewViewProvider, Disposable { if (field === imageField.name) { // If value is an array, it means it comes from the explorer view itself (deletion) if (Array.isArray(value)) { - article.data[field] = value || []; + parentObj[field] = value || []; } else { // Otherwise it is coming from the media dashboard (addition) - let fieldValue = article.data[field]; + let fieldValue = parentObj[field]; if (fieldValue && !Array.isArray(fieldValue)) { fieldValue = [fieldValue]; } const crntData = Object.assign([], fieldValue); const allRelPaths = [...(crntData || []), value]; - article.data[field] = [...new Set(allRelPaths)].filter(f => f); + parentObj[field] = [...new Set(allRelPaths)].filter(f => f); } } } diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index a30d174c..900e7ed6 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -1,7 +1,7 @@ import { PagesListener } from './../listeners/PagesListener'; import { ArticleHelper, Settings } from "."; import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; -import { ContentType as IContentType, DraftField } from '../models'; +import { ContentType as IContentType, DraftField, Field } from '../models'; import { Uri, workspace, window, commands } from 'vscode'; import { Folders } from "../commands/Folders"; import { Questions } from "./Questions"; @@ -109,15 +109,7 @@ export class ContentType { return; } - let data: any = {}; - - for (const field of contentType.fields) { - if (field.name === "title") { - data[field.name] = titleValue; - } else { - data[field.name] = null; - } - } + let data: any = this.processFields(contentType, titleValue, {}); data = ArticleHelper.updateDates(Object.assign({}, data)); @@ -136,4 +128,27 @@ export class ContentType { // Trigger a refresh for the dashboard PagesListener.refresh(); } + + /** + * Process all content type fields + * @param contentType + * @param data + */ + private static processFields(obj: IContentType | Field, titleValue: string, data: any) { + if (obj.fields) { + for (const field of obj.fields) { + if (field.name === "title") { + data[field.name] = titleValue; + } else { + if (field.type === "object") { + data[field.name] = this.processFields(field, titleValue, {}); + } else { + data[field.name] = field.default || ""; + } + } + } + } + + return data; + } } \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 942ed702..2bcba5e8 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -34,13 +34,15 @@ export interface ContentType { export interface Field { title?: string; name: string; - type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy"; + type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "object"; choices?: string[] | Choice[]; single?: boolean; multiple?: boolean; isPreviewImage?: boolean; hidden?: boolean; taxonomyId?: string; + default?: string; + fields?: Field[]; } export interface DateInfo { diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index 230325e6..4a7c7161 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -19,10 +19,15 @@ import useContentType from '../../hooks/useContentType'; import { DateHelper } from '../../helpers/DateHelper'; import FieldBoundary from './ErrorBoundary/FieldBoundary'; import { DraftField } from './Fields/DraftField'; +import { VsLabel } from './VscodeComponents'; +import { CogIcon } from '@heroicons/react/outline'; +export interface IMetadata { + [prop: string]: string[] | string | null | IMetadata; +} export interface IMetadataProps { settings: PanelSettings | undefined; - metadata: { [prop: string]: string[] | string | null }; + metadata: IMetadata; focusElm: TagType | null; unsetFocus: () => void; } @@ -30,13 +35,14 @@ export interface IMetadataProps { const Metadata: React.FunctionComponent = ({settings, metadata, focusElm, unsetFocus}: React.PropsWithChildren) => { const contentType = useContentType(settings, metadata); - const sendUpdate = (field: string | undefined, value: any) => { + const sendUpdate = (field: string | undefined, value: any, parents: string[]) => { if (!field) { return; } MessageHelper.sendMessage(CommandToCode.updateMetadata, { field, + parents, value }); }; @@ -50,7 +56,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, return null; } - const renderFields = (ctFields: Field[]) => { + const renderFields = (ctFields: Field[], parent: IMetadata, parentFields: string[] = []) => { if (!ctFields) { return; } @@ -61,7 +67,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, } if (field.type === 'datetime') { - const dateValue = metadata[field.name] ? getDate(metadata[field.name] as string) : null; + const dateValue = parent[field.name] ? getDate(parent[field.name] as string) : null; return ( @@ -69,7 +75,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, label={field.title || field.name} date={dateValue} format={settings?.date?.format} - onChange={(date => sendUpdate(field.name, date))} /> + onChange={(date => sendUpdate(field.name, date, parentFields))} /> ); } else if (field.type === 'boolean') { @@ -78,12 +84,12 @@ const Metadata: React.FunctionComponent = ({settings, metadata, sendUpdate(field.name, checked)} /> + checked={!!parent[field.name] as any} + onChanged={(checked) => sendUpdate(field.name, checked, parentFields)} /> ); } else if (field.type === 'string') { - const textValue = metadata[field.name]; + const textValue = parent[field.name]; let limit = -1; if (field.name === 'title') { @@ -99,12 +105,12 @@ const Metadata: React.FunctionComponent = ({settings, metadata, singleLine={field.single} limit={limit} rows={3} - onChange={(value) => sendUpdate(field.name, value)} + onChange={(value) => sendUpdate(field.name, value, parentFields)} value={textValue as string || null} /> ); } else if (field.type === 'number') { - const fieldValue = metadata[field.name]; + const fieldValue = parent[field.name]; let nrValue: number | null = parseInt(fieldValue as string); if (isNaN(nrValue)) { nrValue = null; @@ -115,7 +121,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, sendUpdate(field.name, value)} + onChange={(value) => sendUpdate(field.name, value, parentFields)} value={nrValue} /> ); @@ -125,15 +131,15 @@ const Metadata: React.FunctionComponent = ({settings, metadata, sendUpdate(field.name, value))} /> + onChange={(value => sendUpdate(field.name, value, parentFields))} /> ); } else if (field.type === 'choice') { const choices = field.choices || []; - const choiceValue = metadata[field.name]; + const choiceValue = parent[field.name]; return ( @@ -142,7 +148,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, selected={choiceValue as string} choices={choices} multiSelect={field.multiple} - onChange={(value => sendUpdate(field.name, value))} /> + onChange={(value => sendUpdate(field.name, value, parentFields))} /> ); } else if (field.type === 'tags') { @@ -152,7 +158,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, type={TagType.tags} label={field.title || field.name} icon={} - crntSelected={metadata[field.name] as string[] || []} + crntSelected={parent[field.name] as string[] || []} options={settings?.tags || []} freeform={settings.freeform} focussed={focusElm === TagType.tags} @@ -161,7 +167,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, ); } else if (field.type === 'taxonomy') { const taxonomyData = settings.customTaxonomy.find(ct => ct.id === field.taxonomyId); - const selectedValues = metadata[field.name] || []; + const selectedValues = parent[field.name] || []; return ( @@ -185,7 +191,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, type={TagType.categories} label={field.title || field.name} icon={} - crntSelected={metadata.categories as string[] || []} + crntSelected={parent.categories as string[] || []} options={settings.categories} freeform={settings.freeform} focussed={focusElm === TagType.categories} @@ -194,7 +200,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, ); } else if (field.type === 'draft') { const draftField = settings?.draftField; - const value = metadata[field.name]; + const value = parent[field.name]; return ( @@ -203,9 +209,28 @@ const Metadata: React.FunctionComponent = ({settings, metadata, type={draftField.type} choices={draftField.choices || []} value={value as boolean | string | null | undefined} - onChanged={(value: boolean | string) => sendUpdate(field.name, value)} /> + onChanged={(value: boolean | string) => sendUpdate(field.name, value, parentFields)} /> ); + } else if (field.type === 'object') { + if (field.fields && parent && parent[field.name]) { + const subMetadata = parent[field.name] as IMetadata; + return ( + +
+ +
+ {field.title || field.name} +
+
+ + { renderFields(field.fields, subMetadata, [...parentFields, field.name]) } +
+
+ ); + } + + return null; } else { return null; } @@ -216,7 +241,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, { - renderFields(contentType?.fields) + renderFields(contentType?.fields, metadata) } { From dee30923ff423be9783af7a74416872719948aee Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Jan 2022 11:25:44 +0100 Subject: [PATCH 03/41] #197 - Update fields type + support for taxonomy and images --- CHANGELOG.md | 1 + assets/media/styles.css | 8 ++ package.json | 4 +- .../components/Media/Item.tsx | 5 +- src/explorerView/ExplorerView.ts | 105 ++++++++++++------ src/helpers/ContentType.ts | 2 +- src/helpers/MediaHelpers.ts | 2 +- src/models/CustomTaxonomyData.ts | 1 + src/models/PanelSettings.ts | 2 +- .../components/Fields/PreviewImageField.tsx | 9 +- src/panelWebView/components/Metadata.tsx | 16 ++- src/panelWebView/components/TagPicker.tsx | 24 +++- 12 files changed, 126 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b04f006c..35434156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### 🎨 Enhancements +- Added default field value for content type fields - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. ## [5.10.0] - 2022-01-10 diff --git a/assets/media/styles.css b/assets/media/styles.css index c193c441..518f898a 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -659,6 +659,14 @@ input:checked + .field__toggle__slider:before { 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; diff --git a/package.json b/package.json index 1f90d11d..72dd3416 100644 --- a/package.json +++ b/package.json @@ -437,7 +437,7 @@ "tags", "categories", "draft", - "object" + "fields" ], "description": "Define the type of field" }, @@ -541,7 +541,7 @@ "if": { "properties": { "type": { - "const": "object" + "const": "fields" } } }, diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index f3706cef..3e837db6 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -77,6 +77,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi image: parseWinPath(relPath) || "", file: viewData?.data?.filePath, fieldName: viewData?.data?.fieldName, + parents: viewData?.data?.parents, multiple: viewData?.data?.multiple, value: viewData?.data?.value, position: viewData?.data?.position || null, @@ -193,6 +194,8 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi const extension = fileInfo?.pop(); const name = fileInfo?.join('.'); + console.log(viewData?.data) + return ( <>
  • @@ -220,7 +223,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi viewData?.data?.filePath ? ( <> diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index e278ebe6..e1d9f8f6 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -9,7 +9,7 @@ import { Command } from "../panelWebView/Command"; import { CommandToCode } from '../panelWebView/CommandToCode'; import { Article } from '../commands'; import { TagType } from '../panelWebView/TagType'; -import { CustomTaxonomyData, DraftField, ScriptType, TaxonomyType } from '../models'; +import { ContentType, CustomTaxonomyData, DraftField, Field, ScriptType, TaxonomyType } from '../models'; import { exec } from 'child_process'; import { fromMarkdown } from 'mdast-util-from-markdown'; import { Content } from 'mdast'; @@ -101,13 +101,13 @@ export class ExplorerView implements WebviewViewProvider, Disposable { Article.toggleDraft(); break; case CommandToCode.updateTags: - this.updateTags(TagType.tags, msg.data || []); + this.updateTags(TagType.tags, msg.data?.values || [], msg.data?.parents || []); break; case CommandToCode.updateCategories: - this.updateTags(TagType.categories, msg.data || []); + this.updateTags(TagType.categories, msg.data?.values || [], msg.data?.parents || []); break; case CommandToCode.updateKeywords: - this.updateTags(TagType.keywords, msg.data || []); + this.updateTags(TagType.keywords, msg.data?.values || [], msg.data?.parents || []); break; case CommandToCode.updateCustomTaxonomy: this.updateCustomTaxonomy(msg.data); @@ -249,32 +249,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { // Get the current content type const contentType = ArticleHelper.getContentType(updatedMetadata); if (contentType) { - const imageFields = contentType.fields.filter((field) => field.type === "image"); - - for (const field of imageFields) { - if (updatedMetadata[field.name]) { - const imageData = ImageHelper.allRelToAbs(field, updatedMetadata[field.name]) - - if (imageData) { - if (field.multiple && imageData instanceof Array) { - const preview = imageData.map(preview => preview && preview.absPath ? ({ - ...preview, - webviewUrl: this.panel?.webview.asWebviewUri(preview.absPath).toString() - }) : null); - - updatedMetadata[field.name] = preview || []; - } else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) { - const preview = this.panel?.webview.asWebviewUri(imageData.absPath); - updatedMetadata[field.name] = { - ...imageData, - webviewUrl: preview ? preview.toString() : null - }; - } - } else { - updatedMetadata[field.name] = field.multiple ? [] : ""; - } - } - } + this.processImageFields(updatedMetadata, contentType.fields) } } @@ -444,6 +419,56 @@ export class ExplorerView implements WebviewViewProvider, Disposable { }); } + /** + * Process the image fields in the content type + * @param updatedMetadata + * @param fields + * @param parents + */ + private processImageFields(updatedMetadata: any, fields: Field[], parents: string[] = []) { + const imageFields = fields.filter((field) => field.type === "image"); + + // Support multi-level fields + let parentObj = updatedMetadata; + for (const parent of parents || []) { + parentObj = parentObj[parent]; + } + + // Process image fields + for (const field of imageFields) { + if (parentObj[field.name]) { + const imageData = ImageHelper.allRelToAbs(field, parentObj[field.name]) + + if (imageData) { + if (field.multiple && imageData instanceof Array) { + const preview = imageData.map(preview => preview && preview.absPath ? ({ + ...preview, + webviewUrl: this.panel?.webview.asWebviewUri(preview.absPath).toString() + }) : null); + + parentObj[field.name] = preview || []; + } else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) { + const preview = this.panel?.webview.asWebviewUri(imageData.absPath); + parentObj[field.name] = { + ...imageData, + webviewUrl: preview ? preview.toString() : null + }; + } + } else { + parentObj[field.name] = field.multiple ? [] : ""; + } + } + } + + // Check if there are sub-fields to process + const subFields = fields.filter((field) => field.type === "fields"); + if (subFields?.length > 0) { + for (const field of subFields) { + this.processImageFields(updatedMetadata, field.fields || [], [...parents, field.name]); + } + } + } + /** * Retrieve the file its front matter */ @@ -464,7 +489,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { * @param tagType * @param values */ - private updateTags(tagType: TagType, values: string[]) { + private updateTags(tagType: TagType, values: string[], parents: string[]) { const editor = window.activeTextEditor; if (!editor) { return ""; @@ -472,7 +497,14 @@ export class ExplorerView implements WebviewViewProvider, Disposable { const article = ArticleHelper.getFrontMatter(editor); if (article && article.data) { - article.data[tagType.toLowerCase()] = values || []; + + // Support multi-level fields + let parentObj = article.data; + for (const parent of parents || []) { + parentObj = parentObj[parent]; + } + + parentObj[tagType.toLowerCase()] = values || []; ArticleHelper.update(editor, article); this.pushMetadata(article!.data); } @@ -494,7 +526,14 @@ export class ExplorerView implements WebviewViewProvider, Disposable { const article = ArticleHelper.getFrontMatter(editor); if (article && article.data) { - article.data[data.name] = data.options || []; + + // Support multi-level fields + let parentObj = article.data; + for (const parent of data.parents || []) { + parentObj = parentObj[parent]; + } + + parentObj[data.name] = data.options || []; ArticleHelper.update(editor, article); this.pushMetadata(article!.data); } diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 900e7ed6..1a94ea63 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -140,7 +140,7 @@ export class ContentType { if (field.name === "title") { data[field.name] = titleValue; } else { - if (field.type === "object") { + if (field.type === "fields") { data[field.name] = this.processFields(field, titleValue, {}); } else { data[field.name] = field.default || ""; diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index c0e69416..579c4326 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -295,7 +295,7 @@ export class MediaHelpers { panel.getMediaSelection(); } else { panel.getMediaSelection(); - panel.updateMetadata({field: data.fieldName, value: data.image }); + panel.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents }); } } } diff --git a/src/models/CustomTaxonomyData.ts b/src/models/CustomTaxonomyData.ts index ad0510db..f9b9f1b1 100644 --- a/src/models/CustomTaxonomyData.ts +++ b/src/models/CustomTaxonomyData.ts @@ -4,4 +4,5 @@ export interface CustomTaxonomyData { name: string | undefined; options?: string[] | undefined; option?: string | undefined; + parents?: string[]; } \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 2bcba5e8..b8edc95a 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -34,7 +34,7 @@ export interface ContentType { export interface Field { title?: string; name: string; - type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "object"; + type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields"; choices?: string[] | Choice[]; single?: boolean; multiple?: boolean; diff --git a/src/panelWebView/components/Fields/PreviewImageField.tsx b/src/panelWebView/components/Fields/PreviewImageField.tsx index c1f41899..e6b45ecb 100644 --- a/src/panelWebView/components/Fields/PreviewImageField.tsx +++ b/src/panelWebView/components/Fields/PreviewImageField.tsx @@ -15,18 +15,21 @@ export interface IPreviewImageFieldProps { fieldName: string; value: PreviewImageValue | PreviewImageValue[] | null; filePath: string | null; + parents?: string[]; multiple?: boolean; onChange: (value: string | string[] | null) => void; } -export const PreviewImageField: React.FunctionComponent = ({label, fieldName, onChange, value, filePath, multiple}: React.PropsWithChildren) => { +export const PreviewImageField: React.FunctionComponent = ({label, fieldName, onChange, value, filePath, multiple, parents}: React.PropsWithChildren) => { const selectImage = () => { MessageHelper.sendMessage(CommandToCode.selectImage, { filePath: filePath, fieldName, value, - multiple + multiple, + metadataInsert: true, + parents }); }; @@ -35,6 +38,8 @@ export const PreviewImageField: React.FunctionComponent onChange(newValue); } + console.log(fieldName, value, filePath, parents) + return (
    diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index 4a7c7161..8cd1bd03 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -131,10 +131,11 @@ const Metadata: React.FunctionComponent = ({settings, metadata, sendUpdate(field.name, value, parentFields))} /> + onChange={(value) => sendUpdate(field.name, value, parentFields)} /> ); } else if (field.type === 'choice') { @@ -162,7 +163,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, options={settings?.tags || []} freeform={settings.freeform} focussed={focusElm === TagType.tags} - unsetFocus={unsetFocus} /> + unsetFocus={unsetFocus} + parents={parentFields} /> ); } else if (field.type === 'taxonomy') { @@ -181,7 +183,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, focussed={focusElm === TagType.custom} unsetFocus={unsetFocus} fieldName={field.name} - taxonomyId={field.taxonomyId} /> + taxonomyId={field.taxonomyId} + parents={parentFields} /> ); } else if (field.type === 'categories') { @@ -195,7 +198,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, options={settings.categories} freeform={settings.freeform} focussed={focusElm === TagType.categories} - unsetFocus={unsetFocus} /> + unsetFocus={unsetFocus} + parents={parentFields} /> ); } else if (field.type === 'draft') { @@ -212,7 +216,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, onChanged={(value: boolean | string) => sendUpdate(field.name, value, parentFields)} /> ); - } else if (field.type === 'object') { + } else if (field.type === 'fields') { if (field.fields && parent && parent[field.name]) { const subMetadata = parent[field.name] as IMetadata; return ( diff --git a/src/panelWebView/components/TagPicker.tsx b/src/panelWebView/components/TagPicker.tsx index 8e02b71d..3ce29307 100644 --- a/src/panelWebView/components/TagPicker.tsx +++ b/src/panelWebView/components/TagPicker.tsx @@ -12,19 +12,21 @@ import { CustomTaxonomyData } from '../../models'; export interface ITagPickerProps { type: TagType; icon: JSX.Element; - label?: string; crntSelected: string[]; options: string[]; freeform: boolean; focussed: boolean; unsetFocus: () => void; + + parents?: string[]; + label?: string; disableConfigurable?: boolean; fieldName?: string; taxonomyId?: string; } const TagPicker: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { label, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable, fieldName, taxonomyId } = props; + const { label, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable, fieldName, taxonomyId, parents } = props; const [ selected, setSelected ] = React.useState([]); const [ inputValue, setInputValue ] = React.useState(""); const prevSelected = usePrevious(crntSelected); @@ -65,16 +67,26 @@ const TagPicker: React.FunctionComponent = (props: React.PropsW */ const sendUpdate = (values: string[]) => { if (type === TagType.tags) { - MessageHelper.sendMessage(CommandToCode.updateTags, values); + MessageHelper.sendMessage(CommandToCode.updateTags, { + values, + parents + }); } else if (type === TagType.categories) { - MessageHelper.sendMessage(CommandToCode.updateCategories, values); + MessageHelper.sendMessage(CommandToCode.updateCategories, { + values, + parents + }); } else if (type === TagType.keywords) { - MessageHelper.sendMessage(CommandToCode.updateKeywords, values); + MessageHelper.sendMessage(CommandToCode.updateKeywords, { + values, + parents + }); } else if (type === TagType.custom) { MessageHelper.sendMessage(CommandToCode.updateCustomTaxonomy, { id: taxonomyId, name: fieldName, - options: values + options: values, + parents } as CustomTaxonomyData); } }; From b83c565e2907ea3221e0210aaa29e99f1e2b11da Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Jan 2022 14:09:59 +0100 Subject: [PATCH 04/41] HMR support for panel development --- CHANGELOG.md | 1 + package.json | 3 + src/commands/Dashboard.ts | 9 ++- .../components/Media/Item.tsx | 2 - src/explorerView/ExplorerView.ts | 35 +++++++-- .../components/Fields/PreviewImageField.tsx | 2 - src/panelWebView/components/Metadata.tsx | 2 +- src/panelWebView/index.tsx | 37 +++++---- webpack/dashboard.config.js | 16 ++-- webpack/extension.config.js | 75 +++---------------- webpack/panel.config.js | 72 ++++++++++++++++++ 11 files changed, 151 insertions(+), 103 deletions(-) create mode 100644 webpack/panel.config.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 35434156..2256c53e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### 🎨 Enhancements - Added default field value for content type fields +- HMR support for panel webview development - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. ## [5.10.0] - 2022-01-10 diff --git a/package.json b/package.json index 72dd3416..5dd18699 100644 --- a/package.json +++ b/package.json @@ -1185,10 +1185,13 @@ "build:ext": "npm run clean && npm-run-all --parallel dev:build:*", "watch:ext": "webpack --mode development --watch --config ./webpack/extension.config.js", "watch:dashboard": "webpack serve --mode development --config ./webpack/dashboard.config.js", + "watch:panel": "webpack serve --mode development --config ./webpack/panel.config.js", "dev:build:ext": "webpack --mode development --config ./webpack/extension.config.js", "dev:build:dashboard": "webpack --mode development --config ./webpack/dashboard.config.js", + "dev:build:panel": "webpack --mode development --config ./webpack/panel.config.js", "prod:ext": "webpack --mode production --config ./webpack/extension.config.js", "prod:dashboard": "webpack --mode production --config ./webpack/dashboard.config.js", + "prod:panel": "webpack --mode production --config ./webpack/panel.config.js", "test-compile": "tsc -p ./", "clean": "rimraf dist", "start:site": "cd ./docs && npm run dev" diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index a54e89f4..5ae6603a 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -175,14 +175,15 @@ export class Dashboard { */ private static getWebviewContent(webView: Webview, extensionPath: Uri): string { const dashboardFile = "dashboardWebView.js"; - const localServerUrl = "http://localhost:9000"; + const localPort = `9000`; + const localServerUrl = `localhost:${localPort}`; let scriptUri = ""; const isProd = Extension.getInstance().isProductionMode; if (isProd) { scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile)).toString(); } else { - scriptUri = `${localServerUrl}/${dashboardFile}`; + scriptUri = `http://${localServerUrl}/${dashboardFile}`; } const nonce = WebviewHelper.getNonce(); @@ -194,10 +195,10 @@ export class Dashboard { 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://localhost:9000 http://0.0.0.0:9000"}`, + `script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`}`, `style-src ${webView.cspSource} 'self' 'unsafe-inline'`, `font-src ${webView.cspSource}`, - `connect-src https://o1022172.ingest.sentry.io ${isProd ? `` : "ws://localhost:9000 ws://0.0.0.0:9000 http://localhost:9000 http://0.0.0.0:9000"}` + `connect-src https://o1022172.ingest.sentry.io ${isProd ? `` : `ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`}` ]; return ` diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 3e837db6..22dc9d76 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -194,8 +194,6 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi const extension = fileInfo?.pop(); const name = fileInfo?.join('.'); - console.log(viewData?.data) - return ( <>
  • diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index e1d9f8f6..ff886207 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -9,7 +9,7 @@ import { Command } from "../panelWebView/Command"; import { CommandToCode } from '../panelWebView/CommandToCode'; import { Article } from '../commands'; import { TagType } from '../panelWebView/TagType'; -import { ContentType, CustomTaxonomyData, DraftField, Field, ScriptType, TaxonomyType } from '../models'; +import { CustomTaxonomyData, DraftField, Field, ScriptType, TaxonomyType } from '../models'; import { exec } from 'child_process'; import { fromMarkdown } from 'mdast-util-from-markdown'; import { Content } from 'mdast'; @@ -737,35 +737,56 @@ export class ExplorerView implements WebviewViewProvider, Disposable { * @param webView */ private getWebviewContent(webView: Webview): string { + const ext = Extension.getInstance(); + const dashboardFile = "panelWebView.js"; + const localPort = `9001`; + const localServerUrl = `localhost:${localPort}`; + const extensionPath = ext.extensionPath; + const styleVSCodeUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'assets/media', 'vscode.css')); const styleResetUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'assets/media', 'reset.css')); const stylesUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'assets/media', 'styles.css')); - const scriptUri = webView.asWebviewUri(Uri.joinPath(this.extPath, 'dist', 'panelWebView.js')); const nonce = WebviewHelper.getNonce(); - const ext = Extension.getInstance(); const version = ext.getVersion(); const isBeta = ext.isBetaVersion(); + let scriptUri = ""; + const isProd = Extension.getInstance().isProductionMode; + if (isProd) { + scriptUri = webView.asWebviewUri(Uri.joinPath(extensionPath, 'dist', dashboardFile)).toString(); + } else { + scriptUri = `http://${localServerUrl}/${dashboardFile}`; + } + + 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}`}`, + `style-src ${webView.cspSource} 'self' 'unsafe-inline'`, + `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}`}` + ]; + return ` - + - Front Matter + Front Matter Panel -
    +
    Daily usage - + `; diff --git a/src/panelWebView/components/Fields/PreviewImageField.tsx b/src/panelWebView/components/Fields/PreviewImageField.tsx index e6b45ecb..11100645 100644 --- a/src/panelWebView/components/Fields/PreviewImageField.tsx +++ b/src/panelWebView/components/Fields/PreviewImageField.tsx @@ -38,8 +38,6 @@ export const PreviewImageField: React.FunctionComponent onChange(newValue); } - console.log(fieldName, value, filePath, parents) - return (
    diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index 8cd1bd03..43f23a34 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -243,7 +243,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, return ( - + { renderFields(contentType?.fields, metadata) } diff --git a/src/panelWebView/index.tsx b/src/panelWebView/index.tsx index 4452e418..360f5120 100644 --- a/src/panelWebView/index.tsx +++ b/src/panelWebView/index.tsx @@ -18,23 +18,32 @@ import '@bendera/vscode-webview-elements/dist/vscode-checkbox.js'; // import '@vscode/webview-ui-toolkit/dist/esm/checkbox'; -const elm = document.querySelector("#app"); -const version = elm?.getAttribute("data-version"); -const environment = elm?.getAttribute("data-environment"); - -Sentry.init({ - dsn: SENTRY_LINK, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: 0, // No performance tracing required - release: version || "", - environment: environment || "", - ignoreErrors: ['ResizeObserver loop limit exceeded'] -}); - declare const acquireVsCodeApi: () => { getState: () => T; setState: (data: T) => void; postMessage: (msg: unknown) => void; }; -render(, elm); +const elm = document.querySelector("#app"); + +if (elm) { + const version = elm?.getAttribute("data-version"); + const environment = elm?.getAttribute("data-environment"); + const isProd = elm?.getAttribute("data-isProd"); + + if (isProd === "true") { + Sentry.init({ + dsn: SENTRY_LINK, + integrations: [new Integrations.BrowserTracing()], + tracesSampleRate: 0, // No performance tracing required + release: version || "", + environment: environment || "", + ignoreErrors: ['ResizeObserver loop limit exceeded'] + }); + } + + render(, elm); +} + +// Webpack HMR +if ((module as any).hot) (module as any).hot.accept(); \ No newline at end of file diff --git a/webpack/dashboard.config.js b/webpack/dashboard.config.js index 17bdd2e1..688ade97 100644 --- a/webpack/dashboard.config.js +++ b/webpack/dashboard.config.js @@ -38,13 +38,7 @@ const config = [ maxEntrypointSize: 400000, maxAssetSize: 400000 }, - plugins: [ - new BundleAnalyzerPlugin({ - analyzerMode: 'static', - reportFilename: "dashboard.html", - openAnalyzer: false - }) - ], + plugins: [], devServer: { compress: true, port: 9000, @@ -60,6 +54,14 @@ const config = [ module.exports = (env, argv) => { for (const configItem of config) { configItem.mode = argv.mode; + + if (argv.mode === 'production') { + configItem.plugins.push(new BundleAnalyzerPlugin({ + analyzerMode: 'static', + reportFilename: "dashboard.html", + openAnalyzer: false + })); + } } return config; diff --git a/webpack/extension.config.js b/webpack/extension.config.js index 5323f3bc..80b7b250 100644 --- a/webpack/extension.config.js +++ b/webpack/extension.config.js @@ -56,78 +56,21 @@ const config = [ } } }, - plugins: [ - new BundleAnalyzerPlugin({ - analyzerMode: 'static', - reportFilename: "extension.html", - openAnalyzer: false - }) - ] - }, - { - name: 'panelWebView', - target: 'web', - entry: './src/panelWebView/index.tsx', - output: { - filename: 'panelWebView.js', - path: path.resolve(__dirname, '../dist') - }, - devtool: 'source-map', - resolve: { - extensions: ['.ts', '.js', '.tsx', '.jsx'] - }, - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - use: [{ - loader: 'ts-loader' - }] - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.m?js/, - resolve: { - fullySpecified: false - } - } - ] - }, - performance: { - maxEntrypointSize: 400000, - maxAssetSize: 400000 - }, - // optimization: { - // splitChunks: { - // cacheGroups: { - // vendors: { - // test: /node_modules/, - // chunks: 'initial', - // filename: 'vendors.[contenthash].js', - // priority: 1, - // maxInitialRequests: 2, // create only one vendor file - // minChunks: 1, - // } - // } - // } - // }, - plugins: [ - new BundleAnalyzerPlugin({ - analyzerMode: 'static', - reportFilename: "viewpanel.html", - openAnalyzer: false - }) - ] + plugins: [] } ]; module.exports = (env, argv) => { for (const configItem of config) { configItem.mode = argv.mode; + + if (argv.mode === 'production') { + configItem.plugins.push(new BundleAnalyzerPlugin({ + analyzerMode: 'static', + reportFilename: "extension.html", + openAnalyzer: false + })); + } } return config; diff --git a/webpack/panel.config.js b/webpack/panel.config.js new file mode 100644 index 00000000..a248ed37 --- /dev/null +++ b/webpack/panel.config.js @@ -0,0 +1,72 @@ +//@ts-check + +'use strict'; + +const path = require('path'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + +const config = [{ + name: 'panel', + target: 'web', + entry: './src/panelWebView/index.tsx', + output: { + filename: 'panelWebView.js', + path: path.resolve(__dirname, '../dist') + }, + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.js', '.tsx', '.jsx'], + fallback: { "path": require.resolve("path-browserify") } + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + use: [{ + loader: 'ts-loader' + }] + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.m?js/, + resolve: { + fullySpecified: false + } + } + ] + }, + performance: { + maxEntrypointSize: 400000, + maxAssetSize: 400000 + }, + plugins: [], + devServer: { + compress: true, + port: 9001, + hot: true, + allowedHosts: "all", + headers: { + "Access-Control-Allow-Origin": "*", + } + } +}]; + +module.exports = (env, argv) => { + for (const configItem of config) { + configItem.mode = argv.mode; + + if (argv.mode === 'production') { + configItem.plugins.push(new BundleAnalyzerPlugin({ + analyzerMode: 'static', + reportFilename: "viewpanel.html", + openAnalyzer: false + })); + } + } + + return config; +}; \ No newline at end of file From 9f7f803e25363ac64529091f75cc6ce169579add Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 12 Jan 2022 14:44:45 +0100 Subject: [PATCH 05/41] #198 - Additional media sort options --- CHANGELOG.md | 1 + .../components/Header/Sorting.tsx | 19 +++++++++- src/dashboardWebView/constants/SortOption.ts | 8 ++++- src/helpers/MediaHelpers.ts | 35 ++++++++++++------- src/helpers/Sorting.ts | 11 ++++++ src/models/SortType.ts | 1 + 6 files changed, 60 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2256c53e..b9a4a46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Added default field value for content type fields - HMR support for panel webview development - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. +- [#198](https://github.com/estruyf/vscode-front-matter/issues/198): Additional media sort options (alt, caption, and size). ## [5.10.0] - 2022-01-10 diff --git a/src/dashboardWebView/components/Header/Sorting.tsx b/src/dashboardWebView/components/Header/Sorting.tsx index 4e35c6f5..79963c62 100644 --- a/src/dashboardWebView/components/Header/Sorting.tsx +++ b/src/dashboardWebView/components/Header/Sorting.tsx @@ -10,6 +10,7 @@ import { NavigationType } from '../../models'; import { SortingOption } from '../../models/SortingOption'; import { SearchSelector, SettingsSelector, SortingAtom } from '../../state'; import { MenuButton, MenuItem, MenuItems } from '../Menu'; +import { Sorting as SortingHelpers } from '../../../helpers/Sorting'; export interface ISortingProps { disableCustomSorting?: boolean; @@ -23,6 +24,15 @@ export const sortOptions: SortingOption[] = [ { name: "By filename (desc)", id: SortOption.FileNameDesc, order: SortOrder.desc, type: SortType.string }, ]; +const mediaSortOptions: SortingOption[] = [ + { name: "Size (asc)", id: SortOption.SizeAsc, order: SortOrder.asc, type: SortType.number }, + { name: "Size (desc)", id: SortOption.SizeDesc, order: SortOrder.desc, type: SortType.number }, + { name: "Caption (asc)", id: SortOption.CaptionAsc, order: SortOrder.asc, type: SortType.string }, + { name: "Caption (desc)", id: SortOption.CaptionDesc, order: SortOrder.desc, type: SortType.string }, + { name: "Alt (asc)", id: SortOption.AltAsc, order: SortOrder.asc, type: SortType.string }, + { name: "Alt (desc)", id: SortOption.AltDesc, order: SortOrder.desc, type: SortType.string }, +]; + export const Sorting: React.FunctionComponent = ({disableCustomSorting, view}: React.PropsWithChildren) => { const [ crntSorting, setCrntSorting ] = useRecoilState(SortingAtom); const searchValue = useRecoilValue(SearchSelector); @@ -38,6 +48,13 @@ export const Sorting: React.FunctionComponent = ({disableCustomSo }; let allOptions = [...sortOptions]; + + if (view === NavigationType.Media) { + allOptions = [...allOptions, ...mediaSortOptions]; + } + + allOptions = allOptions.sort(SortingHelpers.alphabetically("name")) + if (settings?.customSorting && !disableCustomSorting) { allOptions = [...allOptions, ...settings.customSorting.map((s) => ({ title: s.title || s.name, @@ -72,7 +89,7 @@ export const Sorting: React.FunctionComponent = ({disableCustomSo - + {allOptions.map((option) => ( f.mtime !== undefined); + // Sort the files + if (crntSort?.type === SortType.string) { + if (crntSort.id === SortOption.AltAsc || crntSort.id === SortOption.AltDesc) { + files = files.sort(Sorting.alphabetically("alt")); + } else if (crntSort.id === SortOption.CaptionAsc || crntSort.id === SortOption.CaptionDesc) { + files = files.sort(Sorting.alphabetically("caption")); + } else { + files = files.sort(Sorting.alphabetically("fsPath")); + } + } else if (crntSort?.type === SortType.number && (crntSort?.id === SortOption.SizeAsc || crntSort?.id === SortOption.SizeDesc)) { + files = files.sort(Sorting.numerically("size")); + } else if (crntSort?.type === SortType.date) { + files = files.sort(Sorting.dateWithFallback("mtime", "fsPath")); + } else { + files = files.sort(Sorting.alphabetically("fsPath")); + } + + if (crntSort?.order === SortOrder.desc) { + files = files.reverse(); + } + // Retrieve all the folders let allContentFolders: string[] = []; let allFolders: string[] = []; diff --git a/src/helpers/Sorting.ts b/src/helpers/Sorting.ts index fdd6de87..ba514b50 100644 --- a/src/helpers/Sorting.ts +++ b/src/helpers/Sorting.ts @@ -20,6 +20,17 @@ export class Sorting { }; }; + /** + * Sort field value numerically + * @param property + * @returns + */ + public static numerically = (property: string) => { + return (a: any, b: any) => { + return a[property] - b[property]; + }; + } + /** * Sort by date * @param property diff --git a/src/models/SortType.ts b/src/models/SortType.ts index 78b308db..810f7667 100644 --- a/src/models/SortType.ts +++ b/src/models/SortType.ts @@ -1,4 +1,5 @@ export enum SortType { string = 'string', + number = 'number', date = 'date' } \ No newline at end of file From b1674b4b842d8943c4fe86be0fed0c865b90a3aa Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 13 Jan 2022 20:18:32 +0100 Subject: [PATCH 06/41] #193 - Data file dashboard added --- CHANGELOG.md | 6 +- package-lock.json | 489 +++++++++++++----- package.json | 64 ++- postcss.config.js | 1 + src/commands/Dashboard.ts | 4 +- src/commands/Folders.ts | 13 + src/constants/Extension.ts | 1 + src/constants/settings.ts | 2 + src/dashboardWebView/DashboardCommand.ts | 3 +- src/dashboardWebView/DashboardMessage.ts | 2 + src/dashboardWebView/components/Button.tsx | 6 +- src/dashboardWebView/components/Dashboard.tsx | 25 +- .../components/DataView/DataForm.tsx | 67 +++ .../components/DataView/DataFormControls.tsx | 25 + .../components/DataView/DataView.tsx | 202 ++++++++ .../components/DataView/EmptyView.tsx | 13 + .../components/DataView/SortableContainer.tsx | 6 + .../components/DataView/SortableItem.tsx | 65 +++ .../components/DataView/index.ts | 1 + .../components/Header/Header.tsx | 18 +- .../components/Header/Tab.tsx | 25 + .../components/Header/Tabs.tsx | 45 ++ src/dashboardWebView/hooks/useMessages.tsx | 2 + src/dashboardWebView/models/NavigationType.ts | 3 +- src/dashboardWebView/models/Settings.ts | 2 + src/dashboardWebView/styles.css | 68 +++ src/extension.ts | 4 + src/helpers/DashboardSettings.ts | 6 +- src/listeners/DataListener.ts | 61 +++ src/models/DashboardData.ts | 2 +- src/models/DataFile.ts | 7 + tsconfig.json | 3 +- 32 files changed, 1076 insertions(+), 165 deletions(-) create mode 100644 src/dashboardWebView/components/DataView/DataForm.tsx create mode 100644 src/dashboardWebView/components/DataView/DataFormControls.tsx create mode 100644 src/dashboardWebView/components/DataView/DataView.tsx create mode 100644 src/dashboardWebView/components/DataView/EmptyView.tsx create mode 100644 src/dashboardWebView/components/DataView/SortableContainer.tsx create mode 100644 src/dashboardWebView/components/DataView/SortableItem.tsx create mode 100644 src/dashboardWebView/components/DataView/index.ts create mode 100644 src/dashboardWebView/components/Header/Tab.tsx create mode 100644 src/dashboardWebView/components/Header/Tabs.tsx create mode 100644 src/listeners/DataListener.ts create mode 100644 src/models/DataFile.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a4a46d..0517f558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,15 @@ ## [6.0.0] - 2022-01-xx +### ✨ New features + +- [#193](https://github.com/estruyf/vscode-front-matter/issues/193): Support added for editing data files. +- [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. + ### 🎨 Enhancements - Added default field value for content type fields - HMR support for panel webview development -- [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. - [#198](https://github.com/estruyf/vscode-front-matter/issues/198): Additional media sort options (alt, caption, and size). ## [5.10.0] - 2022-01-10 diff --git a/package-lock.json b/package-lock.json index a6f80078..b8acd9f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,8 @@ "@vscode/codicons": "0.0.20", "@vscode/webview-ui-toolkit": "^0.8.1", "@webpack-cli/serve": "^1.6.0", + "ajv": "^8.8.2", + "array-move": "^4.0.0", "autoprefixer": "^10.3.2", "css-loader": "5.2.7", "date-fns": "2.23.0", @@ -47,17 +49,22 @@ "path-browserify": "^1.0.1", "postcss": "^8.3.6", "postcss-loader": "4.3.0", + "postcss-nested": "^5.0.6", "react": "17.0.1", "react-datepicker": "4.2.1", "react-dom": "17.0.1", "react-dropzone": "^11.3.4", + "react-sortable-hoc": "^2.0.0", "recoil": "^0.4.1", "rimraf": "^3.0.2", "style-loader": "2.0.0", "tailwindcss": "^2.2.7", "ts-loader": "8.0.3", "tslint": "6.1.3", - "typescript": "4.0.2", + "typescript": "^4.5.4", + "uniforms": "^3.7.0", + "uniforms-bridge-json-schema": "^3.7.0", + "uniforms-unstyled": "^3.7.0", "url-join-ts": "^1.0.5", "wc-react": "github:estruyf/wc-react", "webpack": "^5.65.0", @@ -218,6 +225,28 @@ "resolve": "~1.19.0" } }, + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -905,14 +934,14 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -937,37 +966,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -1038,6 +1036,18 @@ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, + "node_modules/array-move": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz", + "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3470,6 +3480,15 @@ "node": ">= 0.10" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -3923,9 +3942,9 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/json5": { @@ -5554,22 +5573,22 @@ } }, "node_modules/postcss-nested": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz", - "integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", + "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^6.0.6" }, "engines": { - "node": ">=10.0" + "node": ">=12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/postcss/" }, "peerDependencies": { - "postcss": "^8.1.13" + "postcss": "^8.2.14" } }, "node_modules/postcss-selector-parser": { @@ -5883,6 +5902,22 @@ "react": "^16.8.0 || ^17" } }, + "node_modules/react-sortable-hoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", + "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + }, + "peerDependencies": { + "prop-types": "^15.5.7", + "react": "^16.3.0 || ^17.0.0", + "react-dom": "^16.3.0 || ^17.0.0" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -6204,6 +6239,37 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -6878,6 +6944,25 @@ "node": ">=10.13.0" } }, + "node_modules/tailwindcss/node_modules/postcss-nested": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz", + "integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.1.13" + } + }, "node_modules/tailwindcss/node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -7154,9 +7239,9 @@ } }, "node_modules/typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -7181,6 +7266,74 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uniforms": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms/-/uniforms-3.7.0.tgz", + "integrity": "sha512-FnFXckM09QRWqpREJx9xRUeJJgKmToKW4emaKqwVq7/tiospEuEhSr9kE5uxgNwCfTPQBsX5Ymhzx/fMXEDYDQ==", + "dev": true, + "dependencies": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0" + }, + "funding": { + "url": "https://github.com/vazco/uniforms?sponsor=1" + }, + "peerDependencies": { + "react": "^17.0.0 || ^16.8.0" + } + }, + "node_modules/uniforms-bridge-json-schema": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms-bridge-json-schema/-/uniforms-bridge-json-schema-3.7.0.tgz", + "integrity": "sha512-VWM0tEwcfYXsXfMicxJXIOi0OQoQDcP+WqwDY/OueJpBy9Nw5nsupuS9uUg6ZUkG0m1aCc3znmsZFQXvAaFdTQ==", + "dev": true, + "dependencies": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0", + "uniforms": "^3.7.0" + }, + "funding": { + "url": "https://github.com/vazco/uniforms?sponsor=1" + } + }, + "node_modules/uniforms-bridge-json-schema/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/uniforms-unstyled": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms-unstyled/-/uniforms-unstyled-3.7.0.tgz", + "integrity": "sha512-FIpYl9aPC39FTJIp9sZbUWu2C8cgvTX1wqRSGHvya2qgcjB0RIQ6/xM11DBD9I6V66fEGhE2sBcrNqL7geB0Zg==", + "dev": true, + "dependencies": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0", + "uniforms": "^3.7.0" + }, + "funding": { + "url": "https://github.com/vazco/uniforms?sponsor=1" + }, + "peerDependencies": { + "react": "^17.0.0 || ^16.8.0" + } + }, + "node_modules/uniforms-unstyled/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/uniforms/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/unist-util-stringify-position": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz", @@ -7604,22 +7757,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-server/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -7638,12 +7775,6 @@ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -7986,6 +8117,26 @@ "ajv": "~6.12.6", "jju": "~1.4.0", "resolve": "~1.19.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "@nodelib/fs.scandir": { @@ -8603,14 +8754,14 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, @@ -8621,35 +8772,8 @@ "dev": true, "requires": { "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, "ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -8702,6 +8826,12 @@ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, + "array-move": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz", + "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==", + "dev": true + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -10604,6 +10734,15 @@ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -10914,9 +11053,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "json5": { @@ -12039,12 +12178,12 @@ } }, "postcss-nested": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz", - "integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", + "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^6.0.6" } }, "postcss-selector-parser": { @@ -12281,6 +12420,17 @@ "warning": "^4.0.2" } }, + "react-sortable-hoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", + "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -12519,6 +12669,33 @@ "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "section-matter": { @@ -13075,6 +13252,15 @@ "is-glob": "^4.0.1" } }, + "postcss-nested": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz", + "integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -13267,9 +13453,9 @@ } }, "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true }, "unbox-primitive": { @@ -13284,6 +13470,65 @@ "which-boxed-primitive": "^1.0.2" } }, + "uniforms": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms/-/uniforms-3.7.0.tgz", + "integrity": "sha512-FnFXckM09QRWqpREJx9xRUeJJgKmToKW4emaKqwVq7/tiospEuEhSr9kE5uxgNwCfTPQBsX5Ymhzx/fMXEDYDQ==", + "dev": true, + "requires": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "uniforms-bridge-json-schema": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms-bridge-json-schema/-/uniforms-bridge-json-schema-3.7.0.tgz", + "integrity": "sha512-VWM0tEwcfYXsXfMicxJXIOi0OQoQDcP+WqwDY/OueJpBy9Nw5nsupuS9uUg6ZUkG0m1aCc3znmsZFQXvAaFdTQ==", + "dev": true, + "requires": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0", + "uniforms": "^3.7.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "uniforms-unstyled": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms-unstyled/-/uniforms-unstyled-3.7.0.tgz", + "integrity": "sha512-FIpYl9aPC39FTJIp9sZbUWu2C8cgvTX1wqRSGHvya2qgcjB0RIQ6/xM11DBD9I6V66fEGhE2sBcrNqL7geB0Zg==", + "dev": true, + "requires": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0", + "uniforms": "^3.7.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "unist-util-stringify-position": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz", @@ -13620,18 +13865,6 @@ "ws": "^8.1.0" }, "dependencies": { - "ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -13647,12 +13880,6 @@ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", diff --git a/package.json b/package.json index 5dd18699..5daf1bcd 100644 --- a/package.json +++ b/package.json @@ -329,6 +329,48 @@ "markdownDescription": "Specify if you want to open the dashboard when you start VS Code. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.openonstart)", "scope": "Dashboard" }, + "frontMatter.data.files": { + "type": "array", + "default": [], + "markdownDescription": "Specify the data files you want to use for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.data.files)", + "items": { + "type": "object", + "default": {}, + "properties": { + "id": { + "type": "string", + "description": "Your unique ID you want to use for your data file." + }, + "title": { + "type": "string", + "description": "Title you want to give to your data file." + }, + "labelField": { + "type": "string", + "description": "The field you want to use as label for your data entries." + }, + "file": { + "type": "string", + "description": "Path to the file to load. Only JSON files are supported." + }, + "schema": { + "type": "object", + "default": {}, + "description": "The JSON schema for your data which will be used to render the data form.", + "additionalProperties": true + } + }, + "additionalProperties": false, + "required": [ + "id", + "title", + "file", + "schema", + "labelField" + ] + }, + "scope": "Data" + }, "frontMatter.framework.id": { "type": "string", "default": "", @@ -501,7 +543,9 @@ "default": "", "description": "The ID of your taxonomy field" }, - "fields": { "$ref": "#contenttypefield" } + "fields": { + "$ref": "#contenttypefield" + } }, "additionalProperties": false, "required": [ @@ -864,6 +908,15 @@ "light": "/assets/icons/frontmatter-small-light.svg" } }, + { + "command": "frontMatter.dashboard.data", + "title": "Open data dashboard", + "category": "Front matter", + "icon": { + "dark": "/assets/icons/frontmatter-small-dark.svg", + "light": "/assets/icons/frontmatter-small-light.svg" + } + }, { "command": "frontMatter.dashboard.close", "title": "Close dashboard", @@ -1217,6 +1270,8 @@ "@vscode/codicons": "0.0.20", "@vscode/webview-ui-toolkit": "^0.8.1", "@webpack-cli/serve": "^1.6.0", + "ajv": "^8.8.2", + "array-move": "^4.0.0", "autoprefixer": "^10.3.2", "css-loader": "5.2.7", "date-fns": "2.23.0", @@ -1235,17 +1290,22 @@ "path-browserify": "^1.0.1", "postcss": "^8.3.6", "postcss-loader": "4.3.0", + "postcss-nested": "^5.0.6", "react": "17.0.1", "react-datepicker": "4.2.1", "react-dom": "17.0.1", "react-dropzone": "^11.3.4", + "react-sortable-hoc": "^2.0.0", "recoil": "^0.4.1", "rimraf": "^3.0.2", "style-loader": "2.0.0", "tailwindcss": "^2.2.7", "ts-loader": "8.0.3", "tslint": "6.1.3", - "typescript": "4.0.2", + "typescript": "^4.5.4", + "uniforms": "^3.7.0", + "uniforms-bridge-json-schema": "^3.7.0", + "uniforms-unstyled": "^3.7.0", "url-join-ts": "^1.0.5", "wc-react": "github:estruyf/wc-react", "webpack": "^5.65.0", diff --git a/postcss.config.js b/postcss.config.js index f8e23b97..a44e167f 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -2,6 +2,7 @@ const tailwindcss = require('tailwindcss'); module.exports = { plugins: [ + require('postcss-nested'), tailwindcss('./tailwind.config.js'), require('autoprefixer'), ], diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 5ae6603a..23e4900c 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -11,6 +11,7 @@ import { DashboardData } from '../models/DashboardData'; import { ExplorerView } from '../explorerView/ExplorerView'; import { MediaLibrary } from '../helpers/MediaLibrary'; import { DashboardListener, MediaListener, SettingsListener } from '../listeners'; +import { DataListener } from '../listeners/DataListener'; export class Dashboard { private static webview: WebviewPanel | null = null; @@ -144,6 +145,7 @@ export class Dashboard { MediaListener.process(msg); PagesListener.process(msg); SettingsListener.process(msg); + DataListener.process(msg); }); } @@ -195,7 +197,7 @@ export class Dashboard { 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}`}`, + `script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`} 'unsafe-eval'`, `style-src ${webView.cspSource} 'self' 'unsafe-inline'`, `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}`}` diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index f81e9e57..77e03034 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -274,6 +274,19 @@ export class Folders { path: Folders.absWsFolder(folder, wsFolder) })); } + + /** + * Retrieve the absolute file path + * @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 || "")); + absPath = isWindows ? absPath.split('/').join('\\') : absPath; + return absPath; + } /** * Update the folder settings diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 0afeff0d..561a0541 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -29,6 +29,7 @@ export const COMMAND_NAME = { preview: getCommandName("preview"), dashboard: getCommandName("dashboard"), dashboardMedia: getCommandName("dashboard.media"), + dashboardData: getCommandName("dashboard.data"), dashboardClose: getCommandName("dashboard.close"), promote: getCommandName("promoteSettings"), insertImage: getCommandName("insertImage"), diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 3826ec3d..166fa47e 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -55,6 +55,8 @@ export const SETTINGS_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType"; export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; +export const SETTINGS_DATA_FILES = "data.files"; + export const SETTINGS_FRAMEWORK_ID = "framework.id"; export const SETTING_SITE_BASEURL = "site.baseURL"; diff --git a/src/dashboardWebView/DashboardCommand.ts b/src/dashboardWebView/DashboardCommand.ts index a44b26a0..eea46870 100644 --- a/src/dashboardWebView/DashboardCommand.ts +++ b/src/dashboardWebView/DashboardCommand.ts @@ -4,5 +4,6 @@ export enum DashboardCommand { settings = "settings", media = "media", viewData = "viewData", - mediaUpdate = "mediaUpdate" + mediaUpdate = "mediaUpdate", + dataFileEntries = "dataFileEntries" } \ No newline at end of file diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 23dc81de..bc064e91 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -21,4 +21,6 @@ export enum DashboardMessage { setFramework = 'setFramework', setState = 'setState', runCustomScript = 'runCustomScript', + getDataEntries = 'getDataEntries', + putDataEntries = 'putDataEntries', } \ No newline at end of file diff --git a/src/dashboardWebView/components/Button.tsx b/src/dashboardWebView/components/Button.tsx index 8c7602f6..5ee9e5fe 100644 --- a/src/dashboardWebView/components/Button.tsx +++ b/src/dashboardWebView/components/Button.tsx @@ -1,15 +1,17 @@ import * as React from 'react'; export interface IButtonProps { + secondary?: boolean; disabled?: boolean; + className?: string; onClick: () => void; } -export const Button: React.FunctionComponent = ({onClick, disabled, children}: React.PropsWithChildren) => { +export const Button: React.FunctionComponent = ({onClick, className, disabled, secondary, children}: React.PropsWithChildren) => { return ( +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx new file mode 100644 index 00000000..628d0cc6 --- /dev/null +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -0,0 +1,202 @@ +import * as React from 'react'; +import { Header } from '../Header'; +import { useRecoilValue } from 'recoil'; +import { SettingsSelector } from '../../state'; +import { DataForm } from './DataForm'; +import { useCallback, useEffect, useState } from 'react'; +import { DataFile } from '../../../models/DataFile'; +import { Messenger } from '@estruyf/vscode/dist/client'; +import { DashboardMessage } from '../../DashboardMessage'; +import { SponsorMsg } from '../SponsorMsg'; +import { EventData } from '@estruyf/vscode'; +import { DashboardCommand } from '../../DashboardCommand'; +import { Button } from '../Button'; +import { arrayMoveImmutable } from 'array-move'; +import { EmptyView } from './EmptyView'; +import { Container } from './SortableContainer'; +import { SortableItem } from './SortableItem'; +import { ChevronRightIcon } from '@heroicons/react/outline'; + +export interface IDataViewProps {} + +export const DataView: React.FunctionComponent = (props: React.PropsWithChildren) => { + const [ selectedData, setSelectedData ] = useState(null); + const [ selectedIndex, setSelectedIndex ] = useState(null); + const [ dataEntries, setDataEntries ] = useState(null); + const settings = useRecoilValue(SettingsSelector); + + const setSchema = (dataFile: DataFile) => { + setSelectedData(dataFile); + setSelectedIndex(null); + setDataEntries(null); + + Messenger.send(DashboardMessage.getDataEntries, { ...dataFile }); + }; + + const messageListener = (message: MessageEvent>) => { + if (message.data.command === DashboardCommand.dataFileEntries) { + setDataEntries(message.data.data); + } + }; + + const deleteItem = useCallback((index: number) => { + const dataClone: any[] = Object.assign([], dataEntries); + + if (!selectedData) { + return; + } + + dataClone.splice(index, 1); + + Messenger.send(DashboardMessage.putDataEntries, { + file: selectedData.file, + entries: dataClone + }); + }, [selectedData, dataEntries]); + + const onSubmit = useCallback((data: any) => { + const dataClone: any[] = Object.assign([], dataEntries); + if (selectedIndex !== null && selectedIndex !== undefined) { + dataClone[selectedIndex] = data; + } else { + dataClone.push(data); + } + + if (!selectedData) { + return; + } + + Messenger.send(DashboardMessage.putDataEntries, { + file: selectedData.file, + entries: dataClone + }); + }, [selectedData, dataEntries, selectedIndex]); + + const onSortEnd = useCallback(({ oldIndex, newIndex }: any) => { + if (!dataEntries || dataEntries.length === 0) { + return null; + } + + if (selectedIndex !== null && selectedIndex !== undefined) { + setSelectedIndex(newIndex); + } + + if (!selectedData) { + return; + } + + const newEntries = arrayMoveImmutable(dataEntries, oldIndex, newIndex); + + Messenger.send(DashboardMessage.putDataEntries, { + file: selectedData.file, + entries: newEntries + }); + }, [selectedData, dataEntries, selectedIndex]); + + useEffect(() => { + Messenger.listen(messageListener); + + return () => { + Messenger.unlisten(messageListener); + } + }, []); + + return ( +
    +
    + +
    + +
    + + + +
    + +
    + { + selectedData ? ( + <> +
    +

    Your {selectedData.title.toLowerCase()} data items

    + +
    + { + (dataEntries && dataEntries.length > 0) ? ( + <> + + { + (dataEntries || []).map((dataEntry, idx) => ( + setSelectedIndex(index)} + onDeleteItem={deleteItem} + /> + )) + } + + + + ) : ( +
    +

    No {selectedData.title.toLowerCase()} data entries found

    +
    + ) + } +
    +
    +
    +

    Create or modify your {selectedData.title.toLowerCase()} data

    + { + selectedData ? ( + setSelectedIndex(null)} /> + ) : ( +

    Select a data type to get started

    + ) + } +
    + + ) : ( + + ) + } +
    +
    + + +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/EmptyView.tsx b/src/dashboardWebView/components/DataView/EmptyView.tsx new file mode 100644 index 00000000..975aa98a --- /dev/null +++ b/src/dashboardWebView/components/DataView/EmptyView.tsx @@ -0,0 +1,13 @@ +import { ExclamationCircleIcon } from '@heroicons/react/outline'; +import * as React from 'react'; + +export interface IEmptyViewProps {} + +export const EmptyView: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( +
    + +

    Select your date type first

    +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/SortableContainer.tsx b/src/dashboardWebView/components/DataView/SortableContainer.tsx new file mode 100644 index 00000000..1282a5d5 --- /dev/null +++ b/src/dashboardWebView/components/DataView/SortableContainer.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import { SortableContainer } from 'react-sortable-hoc'; + +export interface ISortableContainerProps {} + +export const Container = SortableContainer(({ children }: React.PropsWithChildren) => (
      {children}
    )); \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/SortableItem.tsx b/src/dashboardWebView/components/DataView/SortableItem.tsx new file mode 100644 index 00000000..5e51c88a --- /dev/null +++ b/src/dashboardWebView/components/DataView/SortableItem.tsx @@ -0,0 +1,65 @@ +import { PencilIcon, SelectorIcon, XIcon } from '@heroicons/react/outline'; +import * as React from 'react'; +import { SortableHandle, SortableElement } from 'react-sortable-hoc'; +import { Alert } from '../Modals/Alert'; + +export interface ISortableItemProps { + value: string; + index: number; + crntIndex: number; + selectedIndex: number | null; + onSelectedIndexChange: (index: number) => void; + onDeleteItem: (index: number) => void; +} + +const DragHandle = SortableHandle(() => ); + +export const SortableItem = SortableElement(({ value, selectedIndex, crntIndex, onSelectedIndexChange, onDeleteItem }: ISortableItemProps) => { + const [ showAlert, setShowAlert ] = React.useState(false); + + const deleteItemConfirm = () => { + setShowAlert(true); + }; + + return ( + <> +
  • +
    + + {value} +
    + +
    + + +
    +
  • + + { + showAlert && ( + setShowAlert(false)} + trigger={() => onDeleteItem(crntIndex)} /> + ) + } + + ); +}); \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/index.ts b/src/dashboardWebView/components/DataView/index.ts new file mode 100644 index 00000000..59faaae1 --- /dev/null +++ b/src/dashboardWebView/components/DataView/index.ts @@ -0,0 +1 @@ +export * from './DataView'; diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index 028f5f2d..b9d3e804 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -13,11 +13,10 @@ import { useRecoilState, useResetRecoilState } from 'recoil'; import { CategoryAtom, DashboardViewAtom, SortingAtom, TagAtom } from '../../state'; import { Messenger } from '@estruyf/vscode/dist/client'; import { ClearFilters } from './ClearFilters'; -import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon'; -import {PhotographIcon} from '@heroicons/react/outline'; import { MediaHeaderTop } from '../Media/MediaHeaderTop'; import { ChoiceButton } from '../ChoiceButton'; import { MediaHeaderBottom } from '../Media/MediaHeaderBottom'; +import { Tabs } from './Tabs'; export interface IHeaderProps { settings: Settings | null; @@ -55,19 +54,8 @@ export const Header: React.FunctionComponent = ({totalPages, folde return (
    -
    -
      -
    • - -
    • -
    • - -
    • -
    +
    +
    { diff --git a/src/dashboardWebView/components/Header/Tab.tsx b/src/dashboardWebView/components/Header/Tab.tsx new file mode 100644 index 00000000..1bfdcfcf --- /dev/null +++ b/src/dashboardWebView/components/Header/Tab.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { useRecoilValue } from 'recoil'; +import { NavigationType } from '../../models'; +import { DashboardViewAtom } from '../../state'; + +export interface ITabProps { + navigationType: NavigationType; + onNavigate: (navigationType: NavigationType) => void; +} + +export const Tab: React.FunctionComponent = ({navigationType, onNavigate, children}: React.PropsWithChildren) => { + const view = useRecoilValue(DashboardViewAtom); + + return ( + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/Tabs.tsx b/src/dashboardWebView/components/Header/Tabs.tsx new file mode 100644 index 00000000..35e664f4 --- /dev/null +++ b/src/dashboardWebView/components/Header/Tabs.tsx @@ -0,0 +1,45 @@ +import { DatabaseIcon, PhotographIcon } from '@heroicons/react/outline'; +import * as React from 'react'; +import { useRecoilValue } from 'recoil'; +import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon'; +import { NavigationType } from '../../models'; +import { SettingsSelector } from '../../state'; +import { Tab } from './Tab'; + +export interface ITabsProps { + onNavigate: (navigationType: NavigationType) => void; +} + +export const Tabs: React.FunctionComponent = ({ onNavigate }: React.PropsWithChildren) => { + const settings = useRecoilValue(SettingsSelector); + + return ( +
      +
    • + + Contents + +
    • +
    • + + Media + +
    • + { + (settings?.dataFiles && settings.dataFiles.length > 0) && ( +
    • + + Data + +
    • + ) + } +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/hooks/useMessages.tsx b/src/dashboardWebView/hooks/useMessages.tsx index fe696d37..fec94a32 100644 --- a/src/dashboardWebView/hooks/useMessages.tsx +++ b/src/dashboardWebView/hooks/useMessages.tsx @@ -26,6 +26,8 @@ export default function useMessages() { setView(NavigationType.Media); } else if (message.data.data?.type === NavigationType.Contents) { setView(NavigationType.Contents); + } else if (message.data.data?.type === NavigationType.Data) { + setView(NavigationType.Data); } break; case DashboardCommand.settings: diff --git a/src/dashboardWebView/models/NavigationType.ts b/src/dashboardWebView/models/NavigationType.ts index da02c4f4..eba82bf9 100644 --- a/src/dashboardWebView/models/NavigationType.ts +++ b/src/dashboardWebView/models/NavigationType.ts @@ -1,4 +1,5 @@ export enum NavigationType { Contents = "contents", - Media = "media" + Media = "media", + Data = "data", } \ No newline at end of file diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index 596b8f44..c8cfaa04 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -3,6 +3,7 @@ import { ContentFolder } from '../../models/ContentFolder'; import { ContentType, CustomScript, DraftField, Framework, SortingSetting } from '../../models'; import { SortingOption } from './SortingOption'; import { DashboardViewType } from '.'; +import { DataFile } from '../../models/DataFile'; export interface Settings { beta: boolean; @@ -24,6 +25,7 @@ export interface Settings { customSorting: SortingSetting[] | undefined; dashboardState: DashboardState; scripts: CustomScript[]; + dataFiles: DataFile[] | undefined; } export interface DashboardState { diff --git a/src/dashboardWebView/styles.css b/src/dashboardWebView/styles.css index fe174556..be67906d 100644 --- a/src/dashboardWebView/styles.css +++ b/src/dashboardWebView/styles.css @@ -29,4 +29,72 @@ top: -1px; left: 2px; color: white; +} + +.autoform { + @apply py-4; + + h2 { + @apply text-sm mb-2; + } + + form { + label { + @apply block; + @apply text-gray-500; + @apply my-2; + } + + input { + @apply w-full text-vulcan-500; + + &::placeholder { + @apply text-gray-500; + } + } + + .errors { + ul { + @apply list-disc mt-4 pl-6 pr-4 py-4 border border-red-300 bg-red-50 bg-opacity-50 text-vulcan-500; + } + + li { + @apply capitalize text-gray-900; + } + } + + input[type="submit"] { + @apply w-auto mt-4 inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium text-white bg-teal-600 cursor-pointer; + + &:hover { + @apply bg-teal-700; + } + + &:focus { + @apply outline-none; + } + + &:disabled { + @apply bg-gray-500 opacity-50; + } + } + } +} + +.vscode-dark .autoform { + form { + label { + @apply text-whisper-900; + } + + input[type="submit"] { + @apply text-vulcan-500 + } + + .errors { + li { + @apply text-white; + } + } + } } \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 5b97b065..f3357687 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -61,6 +61,10 @@ export async function activate(context: vscode.ExtensionContext) { Dashboard.open({ type: "media" }); })); + subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardData, (data?: DashboardData) => { + Dashboard.open({ type: "data" }); + })); + subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardClose, (data?: DashboardData) => { Dashboard.close(); })); diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 35bce516..1c2e2080 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -1,8 +1,9 @@ import { Folders } from "../commands/Folders"; import { Template } from "../commands/Template"; -import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; +import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models"; import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models"; +import { DataFile } from "../models/DataFile"; import { Extension } from "./Extension"; import { FrameworkDetector } from "./FrameworkDetector"; import { Settings } from "./SettingsHelper"; @@ -44,7 +45,8 @@ export class DashboardSettings { defaultSorting: Settings.get(SETTINGS_MEDIA_SORTING_DEFAULT), selectedFolder: await ext.getState(ExtensionState.SelectedFolder, "workspace") } - } + }, + dataFiles: Settings.get(SETTINGS_DATA_FILES) } as ISettings } } \ No newline at end of file diff --git a/src/listeners/DataListener.ts b/src/listeners/DataListener.ts new file mode 100644 index 00000000..a3b41696 --- /dev/null +++ b/src/listeners/DataListener.ts @@ -0,0 +1,61 @@ +import { DataFile } from './../models/DataFile'; +import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; +import { BaseListener } from "./BaseListener"; +import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; +import { Folders } from '../commands/Folders'; +import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; +import { dirname } from 'path'; + + +export class DataListener extends BaseListener { + + public static process(msg: { command: DashboardMessage, data: any }) { + super.process(msg); + + switch(msg.command) { + case (DashboardMessage.getDataEntries): + if (!(msg?.data as DataFile).file) { + this.sendMsg(DashboardCommand.dataFileEntries, []); + } + + this.processDataFile(msg?.data); + break; + case (DashboardMessage.putDataEntries): + this.processDataUpdate(msg?.data); + break; + default: + return; + } + } + + private static processDataUpdate(msgData: any) { + const { file, entries } = msgData as { file: string, entries: any[] }; + + const absPath = Folders.getAbsFilePath(file); + if (!existsSync(absPath)) { + const dirPath = dirname(absPath); + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { recursive: true }); + } + } + writeFileSync(absPath, JSON.stringify(entries, null, 2)); + + this.processDataFile(msgData); + } + + private static async processDataFile(msgData: DataFile) { + const { file } = msgData; + const dataFile = this.getDataFile(file); + const jsonData = dataFile ? JSON.parse(dataFile) : []; + this.sendMsg(DashboardCommand.dataFileEntries, jsonData); + } + + private static getDataFile(file: string) { + const absPath = Folders.getAbsFilePath(file); + if (existsSync(absPath)) { + return readFileSync(absPath, 'utf8'); + } + + return null; + } +} \ No newline at end of file diff --git a/src/models/DashboardData.ts b/src/models/DashboardData.ts index 27318fea..df0f8f93 100644 --- a/src/models/DashboardData.ts +++ b/src/models/DashboardData.ts @@ -1,4 +1,4 @@ export interface DashboardData { - type: "contents" | "media"; + type: "contents" | "media" | "data"; data?: any; } \ No newline at end of file diff --git a/src/models/DataFile.ts b/src/models/DataFile.ts new file mode 100644 index 00000000..dc073595 --- /dev/null +++ b/src/models/DataFile.ts @@ -0,0 +1,7 @@ +export interface DataFile { + id: string; + title: string; + file: string; + labelField: string; + schema: any; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c16f7952..47ede041 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "sourceMap": true, "rootDir": "src", "strict": true, - "jsx": "react" + "jsx": "react", + "strictNullChecks": true }, "exclude": [ "node_modules", From 01921c799c65be8b98bdd2b1c4e435ca78ced3e3 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 14 Jan 2022 11:26:30 +0100 Subject: [PATCH 07/41] #193 - Support for list/array fields --- package-lock.json | 1702 ++++++++++++++++- package.json | 2 + src/commands/Project.ts | 4 +- .../components/DataView/DataForm.tsx | 6 +- .../components/DataView/DataFormControls.tsx | 2 +- .../components/DataView/DataView.tsx | 23 +- src/dashboardWebView/styles.css | 32 +- 7 files changed, 1756 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8acd9f0..c61bd3d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "react-dom": "17.0.1", "react-dropzone": "^11.3.4", "react-sortable-hoc": "^2.0.0", + "react-toastify": "^8.1.0", "recoil": "^0.4.1", "rimraf": "^3.0.2", "style-loader": "2.0.0", @@ -63,6 +64,7 @@ "tslint": "6.1.3", "typescript": "^4.5.4", "uniforms": "^3.7.0", + "uniforms-antd": "^3.7.0", "uniforms-bridge-json-schema": "^3.7.0", "uniforms-unstyled": "^3.7.0", "url-join-ts": "^1.0.5", @@ -76,6 +78,60 @@ "vscode": "^1.63.0" } }, + "node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz", + "integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==", + "dev": true, + "peer": true, + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-util": "^5.9.4" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz", + "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==", + "dev": true, + "peer": true + }, + "node_modules/@ant-design/icons/node_modules/rc-util": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.16.1.tgz", + "integrity": "sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -120,6 +176,16 @@ "lit-element": "^2.5.1" } }, + "node_modules/@ctrl/tinycolor": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", + "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", @@ -1002,6 +1068,740 @@ "node": ">=4" } }, + "node_modules/antd": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-4.18.3.tgz", + "integrity": "sha512-EoCMY8pFKX9IVAc0Bdi3DWR03IIOHa6mTZALOGjrKbPm3kbrcvoBTCNXq4BMeVA1dZbMeoBw46peeJLyMp2avw==", + "dev": true, + "peer": true, + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons": "^4.7.0", + "@ant-design/react-slick": "~0.28.1", + "@babel/runtime": "^7.12.5", + "@ctrl/tinycolor": "^3.4.0", + "array-tree-filter": "^2.1.0", + "classnames": "^2.2.6", + "copy-to-clipboard": "^3.2.0", + "lodash": "^4.17.21", + "memoize-one": "^6.0.0", + "moment": "^2.25.3", + "rc-cascader": "~3.0.0-alpha.3", + "rc-checkbox": "~2.3.0", + "rc-collapse": "~3.1.0", + "rc-dialog": "~8.6.0", + "rc-drawer": "~4.4.2", + "rc-dropdown": "~3.2.0", + "rc-field-form": "~1.22.0-2", + "rc-image": "~5.2.5", + "rc-input-number": "~7.3.0", + "rc-mentions": "~1.6.1", + "rc-menu": "~9.2.1", + "rc-motion": "^2.4.4", + "rc-notification": "~4.5.7", + "rc-pagination": "~3.1.9", + "rc-picker": "~2.5.17", + "rc-progress": "~3.2.1", + "rc-rate": "~2.9.0", + "rc-resize-observer": "^1.1.2", + "rc-select": "~14.0.0-alpha.15", + "rc-slider": "~9.7.4", + "rc-steps": "~4.1.0", + "rc-switch": "~3.2.0", + "rc-table": "~7.22.2", + "rc-tabs": "~11.10.0", + "rc-textarea": "~0.3.0", + "rc-tooltip": "~5.1.1", + "rc-tree": "~5.3.5", + "rc-tree-select": "~5.0.0-alpha.2", + "rc-trigger": "^5.2.10", + "rc-upload": "~4.3.0", + "rc-util": "^5.14.0", + "scroll-into-view-if-needed": "^2.2.25" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/@ant-design/react-slick": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz", + "integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "lodash": "^4.17.21", + "resize-observer-polyfill": "^1.5.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-cascader": { + "version": "3.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.0.0-alpha.7.tgz", + "integrity": "sha512-hHhPJW9ll41vMd36gmZWYem9749KI9cW51tPQWCp/AS98wgdz/m71y0eXzHIsiVrsIq54sAvccmHK/ZwrRi13Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "array-tree-filter": "^2.1.0", + "classnames": "^2.3.1", + "rc-select": "~14.0.0-alpha.8", + "rc-tree": "~5.3.4", + "rc-util": "^5.6.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.3.2.tgz", + "integrity": "sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-collapse": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.1.2.tgz", + "integrity": "sha512-HujcKq7mghk/gVKeI6EjzTbb8e19XUZpakrYazu1MblEZ3Hu3WBMSN4A3QmvbF6n1g7x6lUlZvsHZ5shABWYOQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.2.1", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-dialog": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.6.0.tgz", + "integrity": "sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.6.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-drawer": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.4.3.tgz", + "integrity": "sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.7.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-dropdown": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.2.tgz", + "integrity": "sha512-oA9VYYg+jQaPRdFoYFfBn5EAQk2NlL6H0vR2v6JG/8i4HEfUq8p1TTt6HyQ/dGxLe8lpnK+nM7WCjgZT/cpSRQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-trigger": "^5.0.4" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-field-form": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.22.1.tgz", + "integrity": "sha512-LweU7nBeqmC5r3HDUjRprcOXXobHXp/TGIxD7ppBq5FX6Iptt3ibdpRVg4RSyNulBNGHOuknHlRcguuIpvVMVg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.4", + "async-validator": "^4.0.2", + "rc-util": "^5.8.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">= 16.9.0", + "react-dom": ">= 16.9.0" + } + }, + "node_modules/antd/node_modules/rc-image": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.2.5.tgz", + "integrity": "sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-dialog": "~8.6.0", + "rc-util": "^5.0.6" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-input-number": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.3.4.tgz", + "integrity": "sha512-W9uqSzuvJUnz8H8vsVY4kx+yK51SsAxNTwr8SNH4G3XqQNocLVmKIibKFRjocnYX1RDHMND9FFbgj2h7E7nvGA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.9.8" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-mentions": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-1.6.1.tgz", + "integrity": "sha512-LDzGI8jJVGnkhpTZxZuYBhMz3avcZZqPGejikchh97xPni/g4ht714Flh7DVvuzHQ+BoKHhIjobHnw1rcP8erg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-menu": "^9.0.0", + "rc-textarea": "^0.3.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-menu": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.2.1.tgz", + "integrity": "sha512-UbEtn3rflJ8zS+etYGTVQuzy7Fm+yWXR5c0Rl6ecNTS/dPknRyWAyhJcbeR0Hu1+RdQT+0VCqrUPrgKnm4iY+w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.0", + "rc-trigger": "^5.1.2", + "rc-util": "^5.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-menu/node_modules/rc-overflow": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", + "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-motion": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.4.tgz", + "integrity": "sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.2.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-notification": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.7.tgz", + "integrity": "sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.2.0", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-pagination": { + "version": "3.1.15", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.1.15.tgz", + "integrity": "sha512-4L3fot8g4E+PjWEgoVGX0noFCg+8ZFZmeLH4vsnZpB3O2T2zThtakjNxG+YvSaYtyMVT4B+GLayjKrKbXQpdAg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-picker": { + "version": "2.5.19", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.19.tgz", + "integrity": "sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "date-fns": "2.x", + "dayjs": "1.x", + "moment": "^2.24.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-progress": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.2.4.tgz", + "integrity": "sha512-M9WWutRaoVkPUPIrTpRIDpX0SPSrVHzxHdCRCbeoBFrd9UFWTYNWRlHsruJM5FH1AZI+BwB4wOJUNNylg/uFSw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-rate": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.9.1.tgz", + "integrity": "sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-resize-observer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz", + "integrity": "sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.15.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-select": { + "version": "14.0.0-alpha.22", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.0.0-alpha.22.tgz", + "integrity": "sha512-ScNdwUPMgXQbHlk5EisZchrs+HiqdBLzSh/hcjJh2dOA56DhawcZOGn8URS0rJSW4V3IbE26SVYBH60jV56SwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.0.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-select/node_modules/rc-overflow": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", + "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-select/node_modules/rc-virtual-list": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", + "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", + "dev": true, + "peer": true, + "dependencies": { + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.0.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-slider": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.5.tgz", + "integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-tooltip": "^5.0.1", + "rc-util": "^5.16.1", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-steps": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.4.tgz", + "integrity": "sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "classnames": "^2.2.3", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-switch": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-3.2.2.tgz", + "integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.0.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-table": { + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.22.2.tgz", + "integrity": "sha512-Ng2gNkGi6ybl6dzneRn2H4Gp8XhIbRa5rXQ7ZhZcgWVmfVMok70UHGPXcf68tXW6O0/qckTf/eOVsoviSvK4sw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.14.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tabs": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.10.5.tgz", + "integrity": "sha512-DDuUdV6b9zGRYLtjI5hyejWLKoz1QiLWNgMeBzc3aMeQylZFhTYnFGdDc6HRqj5IYearNTsFPVSA+6VIT8g5cg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "^3.2.0", + "rc-menu": "^9.0.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-textarea": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-0.3.7.tgz", + "integrity": "sha512-yCdZ6binKmAQB13hc/oehh0E/QRwoPP1pjF21aHBxlgXO3RzPF6dUu4LG2R4FZ1zx/fQd2L1faktulrXOM/2rw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.7.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tooltip": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.1.1.tgz", + "integrity": "sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "rc-trigger": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-tree": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.3.8.tgz", + "integrity": "sha512-YuobEryPymqPmHFUOvsoOrYdm24psaj0CrGEUuDUQUeG/nNcTGw6FA2YmF4NsEaNBvNSJUSzwfZnFHrKa/xv0A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.4.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-tree-select": { + "version": "5.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.0.0-alpha.4.tgz", + "integrity": "sha512-jKM8XoN3W/7cQmOP+Ypqcu2b2aa7GS8ZIzbAvdLzHt0h0/pTTuyzsNDpejgrX0+S0D0VkpYaZ1dxJQQ7Tinc1Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-select": "~14.0.0-alpha.8", + "rc-tree": "~5.3.3", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-tree/node_modules/rc-virtual-list": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", + "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", + "dev": true, + "peer": true, + "dependencies": { + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.0.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/antd/node_modules/rc-trigger": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.10.tgz", + "integrity": "sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-align": "^4.0.0", + "rc-motion": "^2.0.0", + "rc-util": "^5.5.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-trigger/node_modules/rc-align": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.11.tgz", + "integrity": "sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "dom-align": "^1.7.0", + "lodash": "^4.17.21", + "rc-util": "^5.3.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-upload": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.3.tgz", + "integrity": "sha512-YoJ0phCRenMj1nzwalXzciKZ9/FAaCrFu84dS5pphwucTC8GUWClcDID/WWNGsLFcM97NqIboDqrV82rVRhW/w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/rc-util": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.16.1.tgz", + "integrity": "sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -1048,6 +1848,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "dev": true, + "peer": true + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1066,6 +1873,13 @@ "lodash": "^4.17.14" } }, + "node_modules/async-validator": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz", + "integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==", + "dev": true, + "peer": true + }, "node_modules/attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -1444,6 +2258,15 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -1546,9 +2369,9 @@ "dev": true }, "node_modules/compute-scroll-into-view": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz", - "integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", + "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==", "dev": true }, "node_modules/concat-map": { @@ -1602,6 +2425,16 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "node_modules/copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "dev": true, + "peer": true, + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1788,6 +2621,13 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dayjs": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", + "dev": true, + "peer": true + }, "node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -1970,6 +2810,13 @@ "buffer-indexof": "^1.0.0" } }, + "node_modules/dom-align": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz", + "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==", + "dev": true, + "peer": true + }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -3947,6 +4794,16 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=", + "dev": true, + "peer": true, + "dependencies": { + "string-convert": "^0.2.0" + } + }, "node_modules/json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -4183,6 +5040,13 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "dev": true, + "peer": true + }, "node_modules/memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -4769,6 +5633,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/mrmime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", @@ -5918,6 +6792,19 @@ "react-dom": "^16.3.0 || ^17.0.0" } }, + "node_modules/react-toastify": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.1.0.tgz", + "integrity": "sha512-M+Q3rTmEw/53Csr7NsV/YnldJe4c7uERcY7Tma9mvLU98QT2VhIkKwjBzzxZkJRk/oBKyUAtkyMjMgO00hx6gQ==", + "dev": true, + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -6108,6 +6995,13 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "dev": true, + "peer": true + }, "node_modules/resolve": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", @@ -6270,6 +7164,16 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.28", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", + "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", + "dev": true, + "peer": true, + "dependencies": { + "compute-scroll-into-view": "^1.0.17" + } + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -6454,6 +7358,13 @@ "node": ">=8" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true, + "peer": true + }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -6672,6 +7583,13 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=", + "dev": true, + "peer": true + }, "node_modules/string.prototype.padend": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", @@ -7115,6 +8033,13 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=", + "dev": true, + "peer": true + }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -7283,6 +8208,33 @@ "react": "^17.0.0 || ^16.8.0" } }, + "node_modules/uniforms-antd": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms-antd/-/uniforms-antd-3.7.0.tgz", + "integrity": "sha512-eo296J58IVDkZ6YrEeaqPGBoJgZDvPI1Y3ZsqSvgeWjJrI/gOBQ3YwKlKfhDhBeCOE1YvK4aHbF3wtucETFVgg==", + "dev": true, + "dependencies": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0", + "uniforms": "^3.7.0", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/vazco/uniforms?sponsor=1" + }, + "peerDependencies": { + "@ant-design/icons": "^4.0.0", + "antd": "^4.0.0", + "react": "^17.0.0 || ^16.8.0" + } + }, + "node_modules/uniforms-antd/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/uniforms-bridge-json-schema": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/uniforms-bridge-json-schema/-/uniforms-bridge-json-schema-3.7.0.tgz", @@ -7986,6 +8938,51 @@ } }, "dependencies": { + "@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "dev": true, + "peer": true, + "requires": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "@ant-design/icons": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz", + "integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==", + "dev": true, + "peer": true, + "requires": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-util": "^5.9.4" + }, + "dependencies": { + "rc-util": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.16.1.tgz", + "integrity": "sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.12.5", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + } + } + } + }, + "@ant-design/icons-svg": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz", + "integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==", + "dev": true, + "peer": true + }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -8030,6 +9027,13 @@ "lit-element": "^2.5.1" } }, + "@ctrl/tinycolor": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz", + "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==", + "dev": true, + "peer": true + }, "@discoveryjs/json-ext": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", @@ -8795,6 +9799,556 @@ "color-convert": "^1.9.0" } }, + "antd": { + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-4.18.3.tgz", + "integrity": "sha512-EoCMY8pFKX9IVAc0Bdi3DWR03IIOHa6mTZALOGjrKbPm3kbrcvoBTCNXq4BMeVA1dZbMeoBw46peeJLyMp2avw==", + "dev": true, + "peer": true, + "requires": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons": "^4.7.0", + "@ant-design/react-slick": "~0.28.1", + "@babel/runtime": "^7.12.5", + "@ctrl/tinycolor": "^3.4.0", + "array-tree-filter": "^2.1.0", + "classnames": "^2.2.6", + "copy-to-clipboard": "^3.2.0", + "lodash": "^4.17.21", + "memoize-one": "^6.0.0", + "moment": "^2.25.3", + "rc-cascader": "~3.0.0-alpha.3", + "rc-checkbox": "~2.3.0", + "rc-collapse": "~3.1.0", + "rc-dialog": "~8.6.0", + "rc-drawer": "~4.4.2", + "rc-dropdown": "~3.2.0", + "rc-field-form": "~1.22.0-2", + "rc-image": "~5.2.5", + "rc-input-number": "~7.3.0", + "rc-mentions": "~1.6.1", + "rc-menu": "~9.2.1", + "rc-motion": "^2.4.4", + "rc-notification": "~4.5.7", + "rc-pagination": "~3.1.9", + "rc-picker": "~2.5.17", + "rc-progress": "~3.2.1", + "rc-rate": "~2.9.0", + "rc-resize-observer": "^1.1.2", + "rc-select": "~14.0.0-alpha.15", + "rc-slider": "~9.7.4", + "rc-steps": "~4.1.0", + "rc-switch": "~3.2.0", + "rc-table": "~7.22.2", + "rc-tabs": "~11.10.0", + "rc-textarea": "~0.3.0", + "rc-tooltip": "~5.1.1", + "rc-tree": "~5.3.5", + "rc-tree-select": "~5.0.0-alpha.2", + "rc-trigger": "^5.2.10", + "rc-upload": "~4.3.0", + "rc-util": "^5.14.0", + "scroll-into-view-if-needed": "^2.2.25" + }, + "dependencies": { + "@ant-design/react-slick": { + "version": "0.28.4", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz", + "integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "lodash": "^4.17.21", + "resize-observer-polyfill": "^1.5.0" + } + }, + "rc-cascader": { + "version": "3.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.0.0-alpha.7.tgz", + "integrity": "sha512-hHhPJW9ll41vMd36gmZWYem9749KI9cW51tPQWCp/AS98wgdz/m71y0eXzHIsiVrsIq54sAvccmHK/ZwrRi13Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.12.5", + "array-tree-filter": "^2.1.0", + "classnames": "^2.3.1", + "rc-select": "~14.0.0-alpha.8", + "rc-tree": "~5.3.4", + "rc-util": "^5.6.1" + } + }, + "rc-checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.3.2.tgz", + "integrity": "sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1" + } + }, + "rc-collapse": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.1.2.tgz", + "integrity": "sha512-HujcKq7mghk/gVKeI6EjzTbb8e19XUZpakrYazu1MblEZ3Hu3WBMSN4A3QmvbF6n1g7x6lUlZvsHZ5shABWYOQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.2.1", + "shallowequal": "^1.1.0" + } + }, + "rc-dialog": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.6.0.tgz", + "integrity": "sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.6.1" + } + }, + "rc-drawer": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.4.3.tgz", + "integrity": "sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.7.0" + } + }, + "rc-dropdown": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.2.tgz", + "integrity": "sha512-oA9VYYg+jQaPRdFoYFfBn5EAQk2NlL6H0vR2v6JG/8i4HEfUq8p1TTt6HyQ/dGxLe8lpnK+nM7WCjgZT/cpSRQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-trigger": "^5.0.4" + } + }, + "rc-field-form": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.22.1.tgz", + "integrity": "sha512-LweU7nBeqmC5r3HDUjRprcOXXobHXp/TGIxD7ppBq5FX6Iptt3ibdpRVg4RSyNulBNGHOuknHlRcguuIpvVMVg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.8.4", + "async-validator": "^4.0.2", + "rc-util": "^5.8.0" + } + }, + "rc-image": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.2.5.tgz", + "integrity": "sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-dialog": "~8.6.0", + "rc-util": "^5.0.6" + } + }, + "rc-input-number": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.3.4.tgz", + "integrity": "sha512-W9uqSzuvJUnz8H8vsVY4kx+yK51SsAxNTwr8SNH4G3XqQNocLVmKIibKFRjocnYX1RDHMND9FFbgj2h7E7nvGA==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.9.8" + } + }, + "rc-mentions": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-1.6.1.tgz", + "integrity": "sha512-LDzGI8jJVGnkhpTZxZuYBhMz3avcZZqPGejikchh97xPni/g4ht714Flh7DVvuzHQ+BoKHhIjobHnw1rcP8erg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-menu": "^9.0.0", + "rc-textarea": "^0.3.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.0.1" + } + }, + "rc-menu": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.2.1.tgz", + "integrity": "sha512-UbEtn3rflJ8zS+etYGTVQuzy7Fm+yWXR5c0Rl6ecNTS/dPknRyWAyhJcbeR0Hu1+RdQT+0VCqrUPrgKnm4iY+w==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.0", + "rc-trigger": "^5.1.2", + "rc-util": "^5.12.0", + "shallowequal": "^1.1.0" + }, + "dependencies": { + "rc-overflow": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", + "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.1" + } + } + } + }, + "rc-motion": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.4.tgz", + "integrity": "sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.2.1" + } + }, + "rc-notification": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.7.tgz", + "integrity": "sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.2.0", + "rc-util": "^5.0.1" + } + }, + "rc-pagination": { + "version": "3.1.15", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.1.15.tgz", + "integrity": "sha512-4L3fot8g4E+PjWEgoVGX0noFCg+8ZFZmeLH4vsnZpB3O2T2zThtakjNxG+YvSaYtyMVT4B+GLayjKrKbXQpdAg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1" + } + }, + "rc-picker": { + "version": "2.5.19", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.19.tgz", + "integrity": "sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "date-fns": "2.x", + "dayjs": "1.x", + "moment": "^2.24.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.4.0", + "shallowequal": "^1.1.0" + } + }, + "rc-progress": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.2.4.tgz", + "integrity": "sha512-M9WWutRaoVkPUPIrTpRIDpX0SPSrVHzxHdCRCbeoBFrd9UFWTYNWRlHsruJM5FH1AZI+BwB4wOJUNNylg/uFSw==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + } + }, + "rc-rate": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.9.1.tgz", + "integrity": "sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + } + }, + "rc-resize-observer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz", + "integrity": "sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.15.0", + "resize-observer-polyfill": "^1.5.1" + } + }, + "rc-select": { + "version": "14.0.0-alpha.22", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.0.0-alpha.22.tgz", + "integrity": "sha512-ScNdwUPMgXQbHlk5EisZchrs+HiqdBLzSh/hcjJh2dOA56DhawcZOGn8URS0rJSW4V3IbE26SVYBH60jV56SwQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.0.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.2.0" + }, + "dependencies": { + "rc-overflow": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz", + "integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.1" + } + }, + "rc-virtual-list": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", + "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", + "dev": true, + "peer": true, + "requires": { + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.0.7" + } + } + } + }, + "rc-slider": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.5.tgz", + "integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-tooltip": "^5.0.1", + "rc-util": "^5.16.1", + "shallowequal": "^1.1.0" + } + }, + "rc-steps": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.4.tgz", + "integrity": "sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.2", + "classnames": "^2.2.3", + "rc-util": "^5.0.1" + } + }, + "rc-switch": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-3.2.2.tgz", + "integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-util": "^5.0.1" + } + }, + "rc-table": { + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.22.2.tgz", + "integrity": "sha512-Ng2gNkGi6ybl6dzneRn2H4Gp8XhIbRa5rXQ7ZhZcgWVmfVMok70UHGPXcf68tXW6O0/qckTf/eOVsoviSvK4sw==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.14.0", + "shallowequal": "^1.1.0" + } + }, + "rc-tabs": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.10.5.tgz", + "integrity": "sha512-DDuUdV6b9zGRYLtjI5hyejWLKoz1QiLWNgMeBzc3aMeQylZFhTYnFGdDc6HRqj5IYearNTsFPVSA+6VIT8g5cg==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "^3.2.0", + "rc-menu": "^9.0.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.0" + } + }, + "rc-textarea": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-0.3.7.tgz", + "integrity": "sha512-yCdZ6binKmAQB13hc/oehh0E/QRwoPP1pjF21aHBxlgXO3RzPF6dUu4LG2R4FZ1zx/fQd2L1faktulrXOM/2rw==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.7.0", + "shallowequal": "^1.1.0" + } + }, + "rc-tooltip": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.1.1.tgz", + "integrity": "sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.11.2", + "rc-trigger": "^5.0.0" + } + }, + "rc-tree": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.3.8.tgz", + "integrity": "sha512-YuobEryPymqPmHFUOvsoOrYdm24psaj0CrGEUuDUQUeG/nNcTGw6FA2YmF4NsEaNBvNSJUSzwfZnFHrKa/xv0A==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.4.1" + }, + "dependencies": { + "rc-virtual-list": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz", + "integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==", + "dev": true, + "peer": true, + "requires": { + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.0.7" + } + } + } + }, + "rc-tree-select": { + "version": "5.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.0.0-alpha.4.tgz", + "integrity": "sha512-jKM8XoN3W/7cQmOP+Ypqcu2b2aa7GS8ZIzbAvdLzHt0h0/pTTuyzsNDpejgrX0+S0D0VkpYaZ1dxJQQ7Tinc1Q==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-select": "~14.0.0-alpha.8", + "rc-tree": "~5.3.3", + "rc-util": "^5.16.1" + } + }, + "rc-trigger": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.10.tgz", + "integrity": "sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "rc-align": "^4.0.0", + "rc-motion": "^2.0.0", + "rc-util": "^5.5.0" + }, + "dependencies": { + "rc-align": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.11.tgz", + "integrity": "sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "dom-align": "^1.7.0", + "lodash": "^4.17.21", + "rc-util": "^5.3.0", + "resize-observer-polyfill": "^1.5.1" + } + } + } + }, + "rc-upload": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.3.tgz", + "integrity": "sha512-YoJ0phCRenMj1nzwalXzciKZ9/FAaCrFu84dS5pphwucTC8GUWClcDID/WWNGsLFcM97NqIboDqrV82rVRhW/w==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + } + }, + "rc-util": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.16.1.tgz", + "integrity": "sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.12.5", + "react-is": "^16.12.0", + "shallowequal": "^1.1.0" + } + } + } + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -8832,6 +10386,13 @@ "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==", "dev": true }, + "array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", + "dev": true, + "peer": true + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -8847,6 +10408,13 @@ "lodash": "^4.17.14" } }, + "async-validator": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz", + "integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==", + "dev": true, + "peer": true + }, "attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -9137,6 +10705,12 @@ "shallow-clone": "^3.0.0" } }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "dev": true + }, "color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -9232,9 +10806,9 @@ } }, "compute-scroll-into-view": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz", - "integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ==", + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", + "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==", "dev": true }, "concat-map": { @@ -9276,6 +10850,16 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "dev": true, + "peer": true, + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -9414,6 +10998,13 @@ "integrity": "sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA==", "dev": true }, + "dayjs": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", + "dev": true, + "peer": true + }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -9558,6 +11149,13 @@ "buffer-indexof": "^1.0.0" } }, + "dom-align": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz", + "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==", + "dev": true, + "peer": true + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -11058,6 +12656,16 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=", + "dev": true, + "peer": true, + "requires": { + "string-convert": "^0.2.0" + } + }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -11252,6 +12860,13 @@ "fs-monkey": "1.0.3" } }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "dev": true, + "peer": true + }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -11595,6 +13210,13 @@ "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==", "dev": true }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "dev": true, + "peer": true + }, "mrmime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", @@ -12431,6 +14053,15 @@ "prop-types": "^15.5.7" } }, + "react-toastify": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.1.0.tgz", + "integrity": "sha512-M+Q3rTmEw/53Csr7NsV/YnldJe4c7uERcY7Tma9mvLU98QT2VhIkKwjBzzxZkJRk/oBKyUAtkyMjMgO00hx6gQ==", + "dev": true, + "requires": { + "clsx": "^1.1.1" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -12583,6 +14214,13 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "dev": true, + "peer": true + }, "resolve": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", @@ -12698,6 +14336,16 @@ } } }, + "scroll-into-view-if-needed": { + "version": "2.2.28", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", + "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", + "dev": true, + "peer": true, + "requires": { + "compute-scroll-into-view": "^1.0.17" + } + }, "section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -12867,6 +14515,13 @@ "kind-of": "^6.0.2" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true, + "peer": true + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -13057,6 +14712,13 @@ "safe-buffer": "~5.1.0" } }, + "string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=", + "dev": true, + "peer": true + }, "string.prototype.padend": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", @@ -13359,6 +15021,13 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=", + "dev": true, + "peer": true + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -13489,6 +15158,27 @@ } } }, + "uniforms-antd": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/uniforms-antd/-/uniforms-antd-3.7.0.tgz", + "integrity": "sha512-eo296J58IVDkZ6YrEeaqPGBoJgZDvPI1Y3ZsqSvgeWjJrI/gOBQ3YwKlKfhDhBeCOE1YvK4aHbF3wtucETFVgg==", + "dev": true, + "requires": { + "invariant": "^2.0.0", + "lodash": "^4.0.0", + "tslib": "^2.2.0", + "uniforms": "^3.7.0", + "warning": "^4.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "uniforms-bridge-json-schema": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/uniforms-bridge-json-schema/-/uniforms-bridge-json-schema-3.7.0.tgz", diff --git a/package.json b/package.json index 5daf1bcd..3d7e2ce5 100644 --- a/package.json +++ b/package.json @@ -1296,6 +1296,7 @@ "react-dom": "17.0.1", "react-dropzone": "^11.3.4", "react-sortable-hoc": "^2.0.0", + "react-toastify": "^8.1.0", "recoil": "^0.4.1", "rimraf": "^3.0.2", "style-loader": "2.0.0", @@ -1304,6 +1305,7 @@ "tslint": "6.1.3", "typescript": "^4.5.4", "uniforms": "^3.7.0", + "uniforms-antd": "^3.7.0", "uniforms-bridge-json-schema": "^3.7.0", "uniforms-unstyled": "^3.7.0", "url-join-ts": "^1.0.5", diff --git a/src/commands/Project.ts b/src/commands/Project.ts index f11d7f90..e46cfc9c 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -10,8 +10,8 @@ import { SETTINGS_CONTENT_DEFAULT_FILETYPE } from "../constants"; export class Project { private static content = `--- -title: "{{name}}" -slug: "/{{kebabCase name}}/" +title: +slug: description: author: date: 2019-08-22T15:20:28.000Z diff --git a/src/dashboardWebView/components/DataView/DataForm.tsx b/src/dashboardWebView/components/DataView/DataForm.tsx index 504eeeba..f5e74956 100644 --- a/src/dashboardWebView/components/DataView/DataForm.tsx +++ b/src/dashboardWebView/components/DataView/DataForm.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import Ajv from 'ajv'; import { useEffect, useState } from 'react'; import { JSONSchemaBridge } from 'uniforms-bridge-json-schema'; -import { AutoFields, AutoForm, ErrorsField, SubmitField } from 'uniforms-unstyled'; +import { AutoFields, AutoForm, ErrorsField } from 'uniforms-antd'; import { ErrorBoundary } from '@sentry/react'; import { DataFormControls } from './DataFormControls'; @@ -53,7 +53,9 @@ export const DataForm: React.FunctionComponent = ({ schema, mode model={model || {}} onSubmit={onSubmit} ref={form => form?.reset()}> - +
    + +
    diff --git a/src/dashboardWebView/components/DataView/DataFormControls.tsx b/src/dashboardWebView/components/DataView/DataFormControls.tsx index 6c41f180..8efd735f 100644 --- a/src/dashboardWebView/components/DataView/DataFormControls.tsx +++ b/src/dashboardWebView/components/DataView/DataFormControls.tsx @@ -12,7 +12,7 @@ export const DataFormControls: React.FunctionComponent = const { formRef } = useForm(); return ( -
    +
    @@ -57,7 +57,10 @@ export const SortableItem = SortableElement(({ value, selectedIndex, crntIndex, okBtnText={`Delete`} cancelBtnText={`Cancel`} dismiss={() => setShowAlert(false)} - trigger={() => onDeleteItem(crntIndex)} /> + trigger={() => { + setShowAlert(false); + onDeleteItem(crntIndex); + }} /> ) } diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index c8cfaa04..92c2363a 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -1,3 +1,4 @@ +import { DataType } from './../../models/DataType'; import { VersionInfo } from '../../models/VersionInfo'; import { ContentFolder } from '../../models/ContentFolder'; import { ContentType, CustomScript, DraftField, Framework, SortingSetting } from '../../models'; @@ -26,6 +27,7 @@ export interface Settings { dashboardState: DashboardState; scripts: CustomScript[]; dataFiles: DataFile[] | undefined; + dataTypes: DataType[] | undefined; } export interface DashboardState { diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 1c2e2080..1d11a876 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -1,9 +1,10 @@ import { Folders } from "../commands/Folders"; import { Template } from "../commands/Template"; -import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; +import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models"; import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models"; import { DataFile } from "../models/DataFile"; +import { DataType } from "../models/DataType"; import { Extension } from "./Extension"; import { FrameworkDetector } from "./FrameworkDetector"; import { Settings } from "./SettingsHelper"; @@ -46,7 +47,8 @@ export class DashboardSettings { selectedFolder: await ext.getState(ExtensionState.SelectedFolder, "workspace") } }, - dataFiles: Settings.get(SETTINGS_DATA_FILES) + dataFiles: Settings.get(SETTINGS_DATA_FILES), + dataTypes: Settings.get(SETTINGS_DATA_TYPES) } as ISettings } } \ No newline at end of file diff --git a/src/models/DataFile.ts b/src/models/DataFile.ts index dc073595..016a632f 100644 --- a/src/models/DataFile.ts +++ b/src/models/DataFile.ts @@ -3,5 +3,6 @@ export interface DataFile { title: string; file: string; labelField: string; - schema: any; + schema?: any; + type?: string; } \ No newline at end of file diff --git a/src/models/DataType.ts b/src/models/DataType.ts new file mode 100644 index 00000000..4469a1f1 --- /dev/null +++ b/src/models/DataType.ts @@ -0,0 +1,4 @@ +export interface DataType { + id: string; + schema: any; +} \ No newline at end of file From 6af54580826c6a29f410be2413cc410e38523cff Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 14 Jan 2022 15:22:51 +0100 Subject: [PATCH 09/41] #193 - YAML support for data files added --- package.json | 9 ++++ .../components/DataView/DataView.tsx | 47 ++++++++----------- src/helpers/Logger.ts | 8 ++++ src/listeners/DataListener.ts | 45 +++++++++++++++--- src/models/DataFile.ts | 1 + 5 files changed, 76 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 752057cb..a75d89fa 100644 --- a/package.json +++ b/package.json @@ -353,6 +353,15 @@ "type": "string", "description": "Path to the file to load. Only JSON files are supported." }, + "fileType": { + "type": "string", + "default": "json", + "enum": [ + "json", + "yaml" + ], + "description": "Defines how you want to parse the file. JSON is the default." + }, "schema": { "type": "object", "default": {}, diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx index 4c107d34..84fdc563 100644 --- a/src/dashboardWebView/components/DataView/DataView.tsx +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -50,11 +50,7 @@ export const DataView: React.FunctionComponent = (props: React.P } dataClone.splice(index, 1); - - Messenger.send(DashboardMessage.putDataEntries, { - file: selectedData.file, - entries: dataClone - }); + updateData(dataClone); }, [selectedData, dataEntries]); @@ -65,25 +61,7 @@ export const DataView: React.FunctionComponent = (props: React.P } else { dataClone.push(data); } - - if (!selectedData) { - return; - } - - Messenger.send(DashboardMessage.putDataEntries, { - file: selectedData.file, - entries: dataClone - }); - - // Show toast message - toast.success("Your entry was submitted", { - position: "top-right", - autoClose: 2000, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: false, - transition: Slide - }); + updateData(dataClone); }, [selectedData, dataEntries, selectedIndex]); @@ -96,18 +74,31 @@ export const DataView: React.FunctionComponent = (props: React.P setSelectedIndex(newIndex); } + const newEntries = arrayMoveImmutable(dataEntries, oldIndex, newIndex); + updateData(newEntries); + }, [selectedData, dataEntries, selectedIndex]); + + const updateData = useCallback((data: any) => { if (!selectedData) { return; } - const newEntries = arrayMoveImmutable(dataEntries, oldIndex, newIndex); - Messenger.send(DashboardMessage.putDataEntries, { file: selectedData.file, - entries: newEntries + fileType: selectedData.fileType, + entries: data }); - }, [selectedData, dataEntries, selectedIndex]); + // Show toast message + toast.success("Updated your data entries", { + position: "top-right", + autoClose: 2000, + hideProgressBar: true, + closeOnClick: true, + pauseOnHover: false, + transition: Slide + }); + }, [selectedData]); useEffect(() => { Messenger.listen(messageListener); diff --git a/src/helpers/Logger.ts b/src/helpers/Logger.ts index 576a769e..18de1796 100644 --- a/src/helpers/Logger.ts +++ b/src/helpers/Logger.ts @@ -25,4 +25,12 @@ export class Logger { Logger.channel?.appendLine(`["${type}" - ${format(new Date(), "HH:MM:ss")}] ${message}`); } + + public static warning(message: string): void { + Logger.info(message, "WARNING"); + } + + public static error(message: string): void { + Logger.info(message, "ERROR"); + } } \ No newline at end of file diff --git a/src/listeners/DataListener.ts b/src/listeners/DataListener.ts index a3b41696..7cc42c91 100644 --- a/src/listeners/DataListener.ts +++ b/src/listeners/DataListener.ts @@ -5,6 +5,9 @@ import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; import { Folders } from '../commands/Folders'; import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; import { dirname } from 'path'; +import * as yaml from 'js-yaml'; +import { Logger, Notifications } from '../helpers'; +import { commands } from 'vscode'; export class DataListener extends BaseListener { @@ -29,7 +32,7 @@ export class DataListener extends BaseListener { } private static processDataUpdate(msgData: any) { - const { file, entries } = msgData as { file: string, entries: any[] }; + const { file, fileType, entries } = msgData as { file: string, fileType: string, entries: any[] }; const absPath = Folders.getAbsFilePath(file); if (!existsSync(absPath)) { @@ -38,18 +41,48 @@ export class DataListener extends BaseListener { mkdirSync(dirPath, { recursive: true }); } } - writeFileSync(absPath, JSON.stringify(entries, null, 2)); + + if (fileType === 'yaml') { + const yamlData = yaml.safeDump(entries); + writeFileSync(absPath, yamlData, 'utf8'); + } else { + writeFileSync(absPath, JSON.stringify(entries, null, 2)); + } this.processDataFile(msgData); } + /** + * Process the file data + * @param msgData + */ private static async processDataFile(msgData: DataFile) { - const { file } = msgData; - const dataFile = this.getDataFile(file); - const jsonData = dataFile ? JSON.parse(dataFile) : []; - this.sendMsg(DashboardCommand.dataFileEntries, jsonData); + try { + const { file } = msgData; + const dataFile = this.getDataFile(file); + + if (msgData.fileType === "yaml") { + const entries = yaml.safeLoad(dataFile || ""); + this.sendMsg(DashboardCommand.dataFileEntries, entries); + } else { + const jsonData = dataFile ? JSON.parse(dataFile) : []; + this.sendMsg(DashboardCommand.dataFileEntries, jsonData); + } + } catch (ex) { + Logger.error((ex as Error).message); + const btnClick = await Notifications.error(`Something went wrong while processing the data file. Check your file and output log for more information.`, 'Open output'); + + if (btnClick && btnClick === 'Open output') { + commands.executeCommand(`workbench.panel.output.focus`); + } + } } + /** + * Retrieve the file data + * @param file + * @returns + */ private static getDataFile(file: string) { const absPath = Folders.getAbsFilePath(file); if (existsSync(absPath)) { diff --git a/src/models/DataFile.ts b/src/models/DataFile.ts index 016a632f..48a375f6 100644 --- a/src/models/DataFile.ts +++ b/src/models/DataFile.ts @@ -2,6 +2,7 @@ export interface DataFile { id: string; title: string; file: string; + fileType: "json" | "yaml"; labelField: string; schema?: any; type?: string; From 2dd129d9bd0cb873d1519eaeb0c037a175d06fb3 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 14 Jan 2022 17:52:06 +0100 Subject: [PATCH 10/41] Remove unused references --- src/helpers/ArticleHelper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 4a76af92..f8de7be8 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -10,13 +10,13 @@ import { Extension, Settings } from '.'; import { format, parse } from 'date-fns'; import { Notifications } from './Notifications'; import { Article } from '../commands'; -import { basename, join } from 'path'; +import { join } from 'path'; import { EditorHelper } from '@estruyf/vscode'; import sanitize from '../helpers/Sanitize'; import { existsSync, mkdirSync } from 'fs'; import { ContentType } from '../models'; import { DateHelper } from './DateHelper'; -import { Diagnostic, DiagnosticSeverity, Position, window, Range } from 'vscode'; +import { DiagnosticSeverity, Position, window, Range } from 'vscode'; export class ArticleHelper { private static notifiedFiles: string[] = []; From 1012e10ddc7097d49c0e2290a3e25d47a228cf32 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 14 Jan 2022 20:23:28 +0100 Subject: [PATCH 11/41] #228 - Add bulk actions --- CHANGELOG.md | 1 + .../components/Header/Header.tsx | 47 +++++++++++++++---- src/helpers/DashboardSettings.ts | 2 +- src/panelWebView/components/Actions.tsx | 3 +- src/panelWebView/components/BaseView.tsx | 13 ++++- 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0517f558..aa54318e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#193](https://github.com/estruyf/vscode-front-matter/issues/193): Support added for editing data files. - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. +- [#228](https://github.com/estruyf/vscode-front-matter/issues/228): Show bulk button actions in panel and dashboard view. ### 🎨 Enhancements diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index b9d3e804..c2ef7782 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -17,6 +17,8 @@ import { MediaHeaderTop } from '../Media/MediaHeaderTop'; import { ChoiceButton } from '../ChoiceButton'; import { MediaHeaderBottom } from '../Media/MediaHeaderBottom'; import { Tabs } from './Tabs'; +import { CustomScript } from '../../../models'; +import { LightningBoltIcon, PlusIcon } from '@heroicons/react/outline'; export interface IHeaderProps { settings: Settings | null; @@ -51,6 +53,20 @@ export const Header: React.FunctionComponent = ({totalPages, folde resetSorting(); } + const runBulkScript = (script: CustomScript) => { + Messenger.send(DashboardMessage.runCustomScript, { script }); + }; + + const customActions: any[] = (settings?.scripts || []).filter(s => s.bulk && (s.type === "content" || !s.type)).map(s => ({ + title: ( +
    + + {s.title} +
    + ), + onClick: () => runBulkScript(s) + })); + return (
    @@ -69,15 +85,28 @@ export const Header: React.FunctionComponent = ({totalPages, folde + + Create by content type +
    + ), + onClick: createByContentType, + disabled: !settings?.initialized + }, { + title: ( +
    + + Create by template +
    + ), + onClick: createByTemplate, + disabled: !settings?.initialized + }, + ...customActions + ]} onClick={createContent} disabled={!settings?.initialized} />
    diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 1d11a876..ccd3a030 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -35,7 +35,7 @@ export class DashboardSettings { contentFolders: Folders.get(), crntFramework: Settings.get(SETTINGS_FRAMEWORK_ID), framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null, - scripts: (Settings.get(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type && s.type !== ScriptType.Content), + scripts: (Settings.get(SETTING_CUSTOM_SCRIPTS) || []), dashboardState: { contents: { sorting: await ext.getState(ExtensionState.Dashboard.Contents.Sorting, "workspace"), diff --git a/src/panelWebView/components/Actions.tsx b/src/panelWebView/components/Actions.tsx index ac5454f3..e4728a3e 100644 --- a/src/panelWebView/components/Actions.tsx +++ b/src/panelWebView/components/Actions.tsx @@ -10,8 +10,7 @@ export interface IActionsProps { settings: PanelSettings; } -const Actions: React.FunctionComponent = (props: React.PropsWithChildren) => { - const { metadata, settings } = props; +const Actions: React.FunctionComponent = ({ metadata, settings }: React.PropsWithChildren) => { if (!metadata || Object.keys(metadata).length === 0 || !settings) { return null; diff --git a/src/panelWebView/components/BaseView.tsx b/src/panelWebView/components/BaseView.tsx index 17c0a51a..51552b2c 100644 --- a/src/panelWebView/components/BaseView.tsx +++ b/src/panelWebView/components/BaseView.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { FolderInfo, PanelSettings } from '../../models'; +import { CustomScript, FolderInfo, PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../../helpers/MessageHelper'; import { Collapsible } from './Collapsible'; @@ -31,6 +31,12 @@ const BaseView: React.FunctionComponent = ({settings, folderAndF MessageHelper.sendMessage(CommandToCode.openPreview); }; + const runBulkScript = (script: CustomScript) => { + MessageHelper.sendMessage(CommandToCode.runCustomScript, { title: script.title, script }); + }; + + const customActions: any[] = (settings?.scripts || []).filter(s => s.bulk && (s.type === "content" || !s.type)); + return (
    @@ -42,6 +48,11 @@ const BaseView: React.FunctionComponent = ({settings, folderAndF + { + customActions.map((script) => ( + + )) + }
    From 90519488c1e21f399a1ad18445f1c1dac82956fc Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 14 Jan 2022 20:31:04 +0100 Subject: [PATCH 12/41] #228 - Added action icon --- src/dashboardWebView/components/ChoiceButton.tsx | 12 +++++++++++- .../components/Media/FolderCreation.tsx | 3 ++- src/dashboardWebView/components/Menu/MenuItem.tsx | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/dashboardWebView/components/ChoiceButton.tsx b/src/dashboardWebView/components/ChoiceButton.tsx index 623052b7..6f00b4db 100644 --- a/src/dashboardWebView/components/ChoiceButton.tsx +++ b/src/dashboardWebView/components/ChoiceButton.tsx @@ -6,6 +6,7 @@ import { MenuItem, MenuItems } from './Menu'; export interface IChoiceButtonProps { title: string; choices: { + icon?: JSX.Element; title: string; disabled?: boolean; onClick: () => void; @@ -39,7 +40,16 @@ export const ChoiceButton: React.FunctionComponent = ({onCli {choices.map((choice) => ( + {choice.icon} + {choice.title} +
    + ) : ( + choice.title + ) + )} value={null} onClick={choice.onClick} disabled={choice.disabled} /> diff --git a/src/dashboardWebView/components/Media/FolderCreation.tsx b/src/dashboardWebView/components/Media/FolderCreation.tsx index f35d6cb0..2258ceb1 100644 --- a/src/dashboardWebView/components/Media/FolderCreation.tsx +++ b/src/dashboardWebView/components/Media/FolderCreation.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {FolderAddIcon} from '@heroicons/react/outline'; +import {FolderAddIcon, LightningBoltIcon} from '@heroicons/react/outline'; import { useRecoilValue } from 'recoil'; import { DashboardMessage } from '../../DashboardMessage'; import { SelectedMediaFolderAtom, SettingsSelector } from '../../state'; @@ -31,6 +31,7 @@ export const FolderCreation: React.FunctionComponent = (pr title={`Create new folder`} choices={scripts.map(s => ({ title: s.title, + icon: , onClick: () => runCustomScript(s) }))} onClick={onFolderCreation} diff --git a/src/dashboardWebView/components/Menu/MenuItem.tsx b/src/dashboardWebView/components/Menu/MenuItem.tsx index 99492359..f7643de5 100644 --- a/src/dashboardWebView/components/Menu/MenuItem.tsx +++ b/src/dashboardWebView/components/Menu/MenuItem.tsx @@ -2,7 +2,7 @@ import { Menu } from '@headlessui/react'; import * as React from 'react'; export interface IMenuItemProps { - title: string; + title: JSX.Element | string; value?: any; isCurrent?: boolean; disabled?: boolean; From 8b1fbcabaa52ae1176b12df23b27185fc6c8c007 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 14 Jan 2022 20:35:48 +0100 Subject: [PATCH 13/41] Fix keys --- src/dashboardWebView/components/ChoiceButton.tsx | 4 ++-- src/dashboardWebView/components/Header/Header.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dashboardWebView/components/ChoiceButton.tsx b/src/dashboardWebView/components/ChoiceButton.tsx index 6f00b4db..84929f4d 100644 --- a/src/dashboardWebView/components/ChoiceButton.tsx +++ b/src/dashboardWebView/components/ChoiceButton.tsx @@ -37,9 +37,9 @@ export const ChoiceButton: React.FunctionComponent = ({onCli
    - {choices.map((choice) => ( + {choices.map((choice, idx) => ( diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index c2ef7782..b665cd94 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -57,9 +57,9 @@ export const Header: React.FunctionComponent = ({totalPages, folde Messenger.send(DashboardMessage.runCustomScript, { script }); }; - const customActions: any[] = (settings?.scripts || []).filter(s => s.bulk && (s.type === "content" || !s.type)).map(s => ({ + const customActions: any[] = (settings?.scripts || []).filter(s => s.bulk && (s.type === "content" || !s.type)).map((s, idx) => ({ title: ( -
    +
    {s.title}
    From 66324fd292037203fe12e433ee477ef14e9f2276 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 15 Jan 2022 19:31:15 +0100 Subject: [PATCH 14/41] #193 - support added for data folders --- package.json | 57 ++++++++++++++++++++++++++++++-- src/constants/settings.ts | 1 + src/helpers/DashboardSettings.ts | 55 ++++++++++++++++++++++++++++-- src/models/DataFolder.ts | 9 +++++ 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/models/DataFolder.ts diff --git a/package.json b/package.json index a75d89fa..25496260 100644 --- a/package.json +++ b/package.json @@ -351,7 +351,7 @@ }, "file": { "type": "string", - "description": "Path to the file to load. Only JSON files are supported." + "description": "Path to the file to load. Only JSON or YAML files are supported." }, "fileType": { "type": "string", @@ -378,8 +378,59 @@ "required": [ "id", "title", - "file", - "labelField" + "file" + ], + "anyOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "type" + ] + } + ] + }, + "scope": "Data" + }, + "frontMatter.data.folders": { + "type": "array", + "default": [], + "markdownDescription": "Specify the data files you want to use for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.data.files)", + "items": { + "type": "object", + "default": {}, + "properties": { + "id": { + "type": "string", + "description": "Your unique ID you want to use for your data folder." + }, + "labelField": { + "type": "string", + "description": "The field you want to use as label for your data entries." + }, + "path": { + "type": "string", + "description": "Path to the folder to load files." + }, + "schema": { + "type": "object", + "default": {}, + "description": "The JSON schema for your data which will be used to render the data form.", + "additionalProperties": true + }, + "type": { + "type": "string", + "default": "content", + "description": "If you are using data types, you can specify your type ID." + } + }, + "additionalProperties": false, + "required": [ + "id", + "path" ], "anyOf": [ { diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 45c612e4..a17cbcb1 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -56,6 +56,7 @@ export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; export const SETTINGS_DATA_FILES = "data.files"; +export const SETTINGS_DATA_FOLDERS = "data.folders"; export const SETTINGS_DATA_TYPES = "data.types"; export const SETTINGS_FRAMEWORK_ID = "framework.id"; diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index ccd3a030..f6f14923 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -1,9 +1,12 @@ +import { basename, join } from "path"; +import { workspace } from "vscode"; import { Folders } from "../commands/Folders"; import { Template } from "../commands/Template"; -import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; +import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_DATA_FOLDERS, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models"; import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models"; import { DataFile } from "../models/DataFile"; +import { DataFolder } from "../models/DataFolder"; import { DataType } from "../models/DataType"; import { Extension } from "./Extension"; import { FrameworkDetector } from "./FrameworkDetector"; @@ -47,8 +50,56 @@ export class DashboardSettings { selectedFolder: await ext.getState(ExtensionState.SelectedFolder, "workspace") } }, - dataFiles: Settings.get(SETTINGS_DATA_FILES), + dataFiles: await this.getDataFiles(), dataTypes: Settings.get(SETTINGS_DATA_TYPES) } as ISettings } + + /** + * Retrieve all the data files + * @returns + */ + private static async getDataFiles(): Promise { + const wsPath = Folders.getWorkspaceFolder()?.fsPath; + const files = Settings.get(SETTINGS_DATA_FILES); + const folders = Settings.get(SETTINGS_DATA_FOLDERS); + + let clonedFiles = Object.assign([], files); + if (folders) { + for (let folder of folders) { + if (!folder.path) { + continue; + } + + const folderPath = Folders.getAbsFilePath(folder.path); + if (!folderPath) { + continue; + } + + let dataFolderPath = join(folderPath.replace((wsPath || ''), '')); + if (dataFolderPath.startsWith('/')) { + dataFolderPath = dataFolderPath.substring(1); + } + + const dataJsonFiles = await workspace.findFiles(join(dataFolderPath, '*.json')); + const dataYmlFiles = await workspace.findFiles(join(dataFolderPath, '*.yml')); + const dataYamlFiles = await workspace.findFiles(join(dataFolderPath, '*.yaml')); + + const dataFiles = [...dataJsonFiles, ...dataYmlFiles, ...dataYamlFiles]; + for (let dataFile of dataFiles) { + clonedFiles.push({ + id: basename(dataFile.fsPath), + title: basename(dataFile.fsPath), + file: dataFile.fsPath, + fileType: dataFile.fsPath.endsWith('.json') ? 'json' : 'yaml', + labelField: folder.labelField, + schema: folder.schema, + type: folder.type + } as DataFile) + } + } + } + + return clonedFiles; + } } \ No newline at end of file diff --git a/src/models/DataFolder.ts b/src/models/DataFolder.ts new file mode 100644 index 00000000..20337ed2 --- /dev/null +++ b/src/models/DataFolder.ts @@ -0,0 +1,9 @@ + + +export interface DataFolder { + id: string; + path: string; + labelField: string; + schema?: any; + type?: string; +} \ No newline at end of file From 3e33383eb12b47de6bf0ad23f1e8a19f934f47b7 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 15 Jan 2022 20:35:06 +0100 Subject: [PATCH 15/41] #225 - Placeholder support for front matter field values (template and content type) --- package.json | 24 ++++++++++++ src/commands/Template.ts | 8 +--- src/constants/settings.ts | 1 + src/helpers/ArticleHelper.ts | 74 +++++++++++++++++++++++++++++++++++- src/helpers/ContentType.ts | 11 +++++- 5 files changed, 108 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 25496260..9340119f 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,30 @@ }, "scope": "Content" }, + "frontMatter.content.placeholders": { + "type": "array", + "default": [], + "markdownDescription": "This array of placeholders defines the placeholders that you can use in your content types and templates for automatically populating your content its front matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.placeholders)", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the placeholder, in your content type or template, use it as follows: {{placeholder}}" + }, + "value": { + "type": "string", + "description": "The placeholder its value" + } + }, + "additionalProperties": false, + "required": [ + "id", + "value" + ] + }, + "scope": "Content" + }, "frontMatter.content.publicFolder": { "type": "string", "default": "", diff --git a/src/commands/Template.ts b/src/commands/Template.ts index d3e4ddc8..c63045c8 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -159,13 +159,7 @@ export class Template { } if (frontMatter.data) { - const fmData = frontMatter.data; - if (typeof fmData.title !== "undefined") { - fmData.title = titleValue; - } - if (typeof fmData.slug !== "undefined") { - fmData.slug = ArticleHelper.sanitize(titleValue); - } + frontMatter.data = ArticleHelper.updatePlaceholders(frontMatter.data, titleValue); frontMatter = Article.updateDate(frontMatter); diff --git a/src/constants/settings.ts b/src/constants/settings.ts index a17cbcb1..a88b8c6a 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -46,6 +46,7 @@ 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_WYSIWYG = "content.wysiwyg"; +export const SETTINGS_CONTENT_PLACEHOLDERS = "content.placeholders"; export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting"; export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting"; diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index f8de7be8..1283dd7c 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -3,7 +3,7 @@ import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ import * as vscode from 'vscode'; import * as matter from "gray-matter"; import * as fs from "fs"; -import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; import { DumpOptions } from 'js-yaml'; import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine'; import { Extension, Settings } from '.'; @@ -274,6 +274,78 @@ export class ArticleHelper { return newFilePath; } + /** + * Update placeholder values in the front matter content + * @param data + * @param title + * @returns + */ + public static updatePlaceholders(data: any, title: string) { + const fmData = Object.assign({}, data); + + for (const fieldName of Object.keys(fmData)) { + const fieldValue = fmData[fieldName]; + + if (fieldName === "title" && (fieldValue === null || fieldValue === "")) { + fmData[fieldName] = title; + } + + if (fieldName === "slug" && (fieldValue === null || fieldValue === "")) { + fmData[fieldName] = ArticleHelper.sanitize(title); + } + + fmData[fieldName] = this.processKnownPlaceholders(fmData[fieldName], title); + fmData[fieldName] = this.processCustomPlaceholders(fmData[fieldName], title); + } + + return fmData; + } + + /** + * Replace the known placeholders + * @param value + * @param title + * @returns + */ + public static processKnownPlaceholders(value: string, title: string) { + if (value && typeof value === "string") { + if (value.includes("{{title}}")) { + const regex = new RegExp("{{title}}", "g"); + value = value.replace(regex, title); + } + + if (value.includes("{{slug}}")) { + const regex = new RegExp("{{slug}}", "g"); + value = value.replace(regex, ArticleHelper.sanitize(title)); + } + } + + return value; + } + + /** + * Replace the custom placeholders + * @param value + * @param title + * @returns + */ + public static processCustomPlaceholders(value: string, title: string) { + if (value && typeof value === "string") { + const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS); + if (placeholders && placeholders.length > 0) { + for (const placeholder of placeholders) { + if (value.includes(`{{${placeholder.id}}}`)) { + const regex = new RegExp(`{{${placeholder.id}}}`, "g"); + const updatedValue = this.processKnownPlaceholders(placeholder.value, title); + value = value.replace(regex, updatedValue); + } + } + } + } + + return value; + } + /** * Parse a markdown file and its front matter * @param fileContents diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 1a94ea63..d9447e24 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -135,15 +135,22 @@ export class ContentType { * @param data */ private static processFields(obj: IContentType | Field, titleValue: string, data: any) { + if (obj.fields) { for (const field of obj.fields) { if (field.name === "title") { - data[field.name] = titleValue; + if (field.default) { + data[field.name] = ArticleHelper.processKnownPlaceholders(field.default, titleValue); + data[field.name] = ArticleHelper.processCustomPlaceholders(data[field.name], titleValue); + } else { + data[field.name] = titleValue; + } } else { if (field.type === "fields") { data[field.name] = this.processFields(field, titleValue, {}); } else { - data[field.name] = field.default || ""; + data[field.name] = field.default ? ArticleHelper.processKnownPlaceholders(field.default, titleValue) : ""; + data[field.name] = field.default ? ArticleHelper.processCustomPlaceholders(data[field.name], titleValue) : ""; } } } From 7d4fe9ca0f95dc19262cfc5ec7614aa6b5209a4e Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 15 Jan 2022 20:35:31 +0100 Subject: [PATCH 16/41] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa54318e..aac2374f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#193](https://github.com/estruyf/vscode-front-matter/issues/193): Support added for editing data files. - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. +- [#225](https://github.com/estruyf/vscode-front-matter/issues/225): Placeholder support for front matter field values (template and content type). - [#228](https://github.com/estruyf/vscode-front-matter/issues/228): Show bulk button actions in panel and dashboard view. ### 🎨 Enhancements From 179b31f67c8aaba7ab917d1fe469472cbe85fb5c Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sun, 16 Jan 2022 19:49:18 +0100 Subject: [PATCH 17/41] #230 - front matter json support added --- CHANGELOG.md | 1 + package.json | 6 ++++-- src/helpers/ArticleHelper.ts | 5 +++-- src/helpers/TomlEngine.ts | 2 +- src/providers/MarkdownFoldingProvider.ts | 13 +++++++++++-- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aac2374f..bc3b919f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Added default field value for content type fields - HMR support for panel webview development - [#198](https://github.com/estruyf/vscode-front-matter/issues/198): Additional media sort options (alt, caption, and size). +- [#230](https://github.com/estruyf/vscode-front-matter/issues/230): JSON front matter support added. ## [5.10.0] - 2022-01-10 diff --git a/package.json b/package.json index 9340119f..95a9f198 100644 --- a/package.json +++ b/package.json @@ -830,12 +830,14 @@ "default": "YAML", "enum": [ "YAML", - "TOML" + "TOML", + "JSON" ], "markdownDescription": "Specify the type of Front Matter to use. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.frontmattertype)", "enumDescriptions": [ "Specifies you want to use YAML markup for the front matter (default)", - "Specifies you want to use TOML markup for the front matter" + "Specifies you want to use TOML markup for the front matter", + "Specifies you want to use JSON markup for the front matter" ], "scope": "Taxonomy" }, diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 1283dd7c..ca7ed4a1 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -6,7 +6,7 @@ import * as fs from "fs"; import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; import { DumpOptions } from 'js-yaml'; import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine'; -import { Extension, Settings } from '.'; +import { Extension, Logger, Settings } from '.'; import { format, parse } from 'date-fns'; import { Notifications } from './Notifications'; import { Article } from '../commands'; @@ -388,6 +388,8 @@ export class ArticleHelper { await EditorHelper.showFile(fileName) } }]; + + Logger.error(error.message); const editor = window.activeTextEditor; if (editor?.document.uri) { @@ -401,7 +403,6 @@ export class ArticleHelper { fmRange = MarkdownFoldingProvider.getFrontMatterRange(editor.document); } - if (fmRange) { Extension.getInstance().diagnosticCollection.set(editor.document.uri, [{ severity: DiagnosticSeverity.Error, diff --git a/src/helpers/TomlEngine.ts b/src/helpers/TomlEngine.ts index 99839e6d..be0f4d97 100644 --- a/src/helpers/TomlEngine.ts +++ b/src/helpers/TomlEngine.ts @@ -11,7 +11,7 @@ export const getFormatOpts = (format: string): { language: string, delimiters: s const formats: { [prop: string]: { language: string, delimiters: string | [string, string] | undefined }} = { yaml: { language: 'yaml', delimiters: '---' }, toml: { language: 'toml', delimiters: '+++' }, - json: { language: 'json', delimiters: ['{', '}'] }, + json: { language: 'json', delimiters: '---' }, }; return formats[format]; diff --git a/src/providers/MarkdownFoldingProvider.ts b/src/providers/MarkdownFoldingProvider.ts index f3e5e6e2..4d00e04f 100644 --- a/src/providers/MarkdownFoldingProvider.ts +++ b/src/providers/MarkdownFoldingProvider.ts @@ -1,6 +1,6 @@ import { TextEditorDecorationType } from 'vscode'; import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, Range, TextDocument, window, Position } from 'vscode'; -import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT } from '../constants'; +import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_FRONTMATTER_TYPE } from '../constants'; import { Settings } from '../helpers'; import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider'; @@ -50,6 +50,15 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider { * @returns */ public static getFrontMatterRange(document?: TextDocument) { + const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML"; + + let lineStart = "---"; + let lineEnd = "---"; + if (language === "TOML") { + lineStart = "+++"; + lineEnd = lineStart; + } + if (document) { const lines = document.getText().split('\n'); @@ -59,7 +68,7 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider { for (let i = 0; i < lines.length; i++) { const line = lines[i]; - if (line.startsWith('---')) { + if (line.startsWith(lineStart) || line.startsWith(lineEnd)) { if (i === 0 && start === null) { start = i; } else if (start !== null && end === null) { From 928afceca7bcf7c9a3f9e369d2fa361c24e80664 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 17 Jan 2022 14:11:04 +0100 Subject: [PATCH 18/41] #231 - Backer support added --- CHANGELOG.md | 1 + package-lock.json | 489 ++++++++++++++++++ package.json | 10 + src/commands/Backers.ts | 71 +++ src/constants/context.ts | 1 + .../components/Contents/Contents.tsx | 2 +- .../components/DataView/DataView.tsx | 2 +- .../components/Media/Media.tsx | 2 +- .../components/SponsorMsg.tsx | 11 +- src/dashboardWebView/models/Settings.ts | 1 + src/explorerView/ExplorerView.ts | 5 +- src/extension.ts | 2 + src/helpers/DashboardSettings.ts | 5 +- src/models/PanelSettings.ts | 1 + src/panelWebView/ViewPanel.tsx | 2 +- src/panelWebView/components/BaseView.tsx | 2 +- src/panelWebView/components/SponsorMsg.tsx | 10 +- src/services/Credentials.ts | 63 +++ 18 files changed, 668 insertions(+), 12 deletions(-) create mode 100644 src/commands/Backers.ts create mode 100644 src/services/Credentials.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3b919f..aa0d1475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. - [#225](https://github.com/estruyf/vscode-front-matter/issues/225): Placeholder support for front matter field values (template and content type). - [#228](https://github.com/estruyf/vscode-front-matter/issues/228): Show bulk button actions in panel and dashboard view. +- [#231](https://github.com/estruyf/vscode-front-matter/issues/231): Once you authenticate via GitHub as a supporter, the support links will be hidden from the UI. ### 🎨 Enhancements diff --git a/package-lock.json b/package-lock.json index c61bd3d2..e50bb758 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,16 @@ "name": "vscode-front-matter-beta", "version": "6.0.0", "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.7" + }, "devDependencies": { "@bendera/vscode-webview-elements": "0.6.2", "@estruyf/vscode": "0.0.2", "@headlessui/react": "^1.4.1", "@heroicons/react": "1.0.4", "@iarna/toml": "2.2.3", + "@octokit/rest": "^18.12.0", "@sentry/react": "^6.13.3", "@sentry/tracing": "^6.13.3", "@tailwindcss/forms": "^0.3.3", @@ -22,6 +26,7 @@ "@types/lodash.uniqby": "4.7.6", "@types/mocha": "^5.2.6", "@types/node": "10.17.48", + "@types/node-fetch": "^2.5.12", "@types/react": "17.0.0", "@types/react-datepicker": "^4.1.7", "@types/react-dom": "17.0.0", @@ -348,6 +353,156 @@ "node": ">= 8" } }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dev": true, + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", + "dev": true + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.34.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.34.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", + "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/request/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dev": true, + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^11.2.0" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -610,6 +765,16 @@ "integrity": "sha512-Agl6xbYP6FOMDeAsr3QVZ+g7Yzg0uhPHWx0j5g4LFdUBHVtqtU+gH660k/lCEe506jJLOGbEzsnqPDTZGJQLag==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -1880,6 +2045,12 @@ "dev": true, "peer": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, "node_modules/attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -1928,6 +2099,12 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, + "node_modules/before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", + "dev": true + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2308,6 +2485,18 @@ "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", @@ -2723,6 +2912,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2732,6 +2930,12 @@ "node": ">= 0.6" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, "node_modules/destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -3482,6 +3686,20 @@ } } }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5729,6 +5947,25 @@ "lodash": "^4.17.21" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -8058,6 +8295,11 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "node_modules/ts-loader": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.3.tgz", @@ -8299,6 +8541,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "dev": true + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -8445,6 +8693,11 @@ "dev": true, "license": "MIT" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, "node_modules/webpack": { "version": "5.65.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.65.0.tgz", @@ -8857,6 +9110,15 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -9169,6 +9431,146 @@ "fastq": "^1.6.0" } }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "dev": true, + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dev": true, + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", + "dev": true + }, + "@octokit/plugin-paginate-rest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", + "dev": true, + "requires": { + "@octokit/types": "^6.34.0" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true, + "requires": {} + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", + "dev": true, + "requires": { + "@octokit/types": "^6.34.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", + "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", + "dev": true, + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dev": true, + "requires": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "dev": true, + "requires": { + "@octokit/openapi-types": "^11.2.0" + } + }, "@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -9397,6 +9799,16 @@ "integrity": "sha512-Agl6xbYP6FOMDeAsr3QVZ+g7Yzg0uhPHWx0j5g4LFdUBHVtqtU+gH660k/lCEe506jJLOGbEzsnqPDTZGJQLag==", "dev": true }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -10415,6 +10827,12 @@ "dev": true, "peer": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, "attr-accept": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", @@ -10447,6 +10865,12 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", + "dev": true + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -10752,6 +11176,15 @@ "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", @@ -11074,12 +11507,24 @@ "slash": "^3.0.0" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -11696,6 +12141,17 @@ "integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==", "dev": true }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -13288,6 +13744,14 @@ "lodash": "^4.17.21" } }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -15040,6 +15504,11 @@ "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", "dev": true }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "ts-loader": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.3.tgz", @@ -15228,6 +15697,12 @@ "@types/unist": "^2.0.0" } }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "dev": true + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -15357,6 +15832,11 @@ "dev": true, "from": "wc-react@github:estruyf/wc-react" }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, "webpack": { "version": "5.65.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.65.0.tgz", @@ -15637,6 +16117,15 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 95a9f198..364783ac 100644 --- a/package.json +++ b/package.json @@ -943,6 +943,11 @@ } }, "commands": [ + { + "command": "frontMatter.authenticate", + "title": "Authenticate", + "category": "Front matter" + }, { "command": "frontMatter.collapseSections", "title": "Collapse sections", @@ -1383,6 +1388,7 @@ "@headlessui/react": "^1.4.1", "@heroicons/react": "1.0.4", "@iarna/toml": "2.2.3", + "@octokit/rest": "^18.12.0", "@sentry/react": "^6.13.3", "@sentry/tracing": "^6.13.3", "@tailwindcss/forms": "^0.3.3", @@ -1391,6 +1397,7 @@ "@types/lodash.uniqby": "4.7.6", "@types/mocha": "^5.2.6", "@types/node": "10.17.48", + "@types/node-fetch": "^2.5.12", "@types/react": "17.0.0", "@types/react-datepicker": "^4.1.7", "@types/react-dom": "17.0.0", @@ -1442,5 +1449,8 @@ "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^4.9.1", "webpack-dev-server": "^4.6.0" + }, + "dependencies": { + "node-fetch": "^2.6.7" } } diff --git a/src/commands/Backers.ts b/src/commands/Backers.ts new file mode 100644 index 00000000..582896cd --- /dev/null +++ b/src/commands/Backers.ts @@ -0,0 +1,71 @@ +import { commands, ExtensionContext } from 'vscode'; +import { CONTEXT } from '../constants'; +import { Extension } from '../helpers'; +import { Credentials } from "../services/Credentials"; +import fetch from "node-fetch"; +import { ExplorerView } from '../explorerView/ExplorerView'; +import { Dashboard } from './Dashboard'; + +export class Backers { + private static creds: Credentials | null = null; + + public static async init(context: ExtensionContext) { + Backers.creds = new Credentials(); + await Backers.creds.initialize(context); + + try { + const username = await Backers.getUsername(); + Backers.validate(username || ""); + } catch (e) { + Backers.validate(""); + } + + context.subscriptions.push( + commands.registerCommand('frontMatter.authenticate', async () => { + const username = await Backers.getUsername(); + Backers.validate(username || ""); + }) + ); + } + + public static async getUsername() { + const octokit = await Backers.creds?.getOctokit(); + const user = await octokit?.users.getAuthenticated(); + + if (user?.data?.login) { + return user?.data?.login; + } + + return; + } + + public static async validate(username: string) { + const ext = Extension.getInstance(); + + if (!username) { + ext.setState(CONTEXT.backer, undefined, 'global'); + } + + const isBeta = ext.isBetaVersion(); + + const response = await fetch(`https://${isBeta ? `beta.` : ``}frontmatter.codes/api/backers?backer=${username}`); + + if (response.ok) { + const prevData = await ext.getState(CONTEXT.backer, 'global'); + await ext.setState(CONTEXT.backer, true, 'global'); + + if (!prevData) { + const explorerView = ExplorerView.getInstance(); + if (explorerView.visible) { + explorerView.getSettings(); + } + + if (Dashboard.isOpen) { + Dashboard.reload(); + } + } + } else { + ext.setState(CONTEXT.backer, false, 'global'); + } + } +} \ No newline at end of file diff --git a/src/constants/context.ts b/src/constants/context.ts index 376640ad..8f029de2 100644 --- a/src/constants/context.ts +++ b/src/constants/context.ts @@ -5,4 +5,5 @@ export const CONTEXT = { isEnabled: "frontMatter:enabled", isDashboardOpen: "frontMatter:dashboard:open", wysiwyg: "frontMatter:markdown:wysiwyg", + backer: "frontMatter:backers:supporter", }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Contents/Contents.tsx b/src/dashboardWebView/components/Contents/Contents.tsx index 16c5b56a..f3f90357 100644 --- a/src/dashboardWebView/components/Contents/Contents.tsx +++ b/src/dashboardWebView/components/Contents/Contents.tsx @@ -30,7 +30,7 @@ export const Contents: React.FunctionComponent = ({pages, loadin { loading ? : }
    - +
    ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx index 84fdc563..27e087a6 100644 --- a/src/dashboardWebView/components/DataView/DataView.tsx +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -222,7 +222,7 @@ export const DataView: React.FunctionComponent = (props: React.P
    - +
    diff --git a/src/dashboardWebView/components/Media/Media.tsx b/src/dashboardWebView/components/Media/Media.tsx index 78062ca1..9b68ddf9 100644 --- a/src/dashboardWebView/components/Media/Media.tsx +++ b/src/dashboardWebView/components/Media/Media.tsx @@ -118,7 +118,7 @@ export const Media: React.FunctionComponent = (props: React.PropsWi - +
    ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/SponsorMsg.tsx b/src/dashboardWebView/components/SponsorMsg.tsx index ef0c3549..b5a610a9 100644 --- a/src/dashboardWebView/components/SponsorMsg.tsx +++ b/src/dashboardWebView/components/SponsorMsg.tsx @@ -6,9 +6,18 @@ import { VersionInfo } from '../../models'; export interface ISponsorMsgProps { beta: boolean | undefined; version: VersionInfo | undefined; + isBacker: boolean | undefined; } -export const SponsorMsg: React.FunctionComponent = ({beta, version}: React.PropsWithChildren) => { +export const SponsorMsg: React.FunctionComponent = ({beta, isBacker, version}: React.PropsWithChildren) => { + if (isBacker) { + return ( +

    + Front Matter{version ? ` (v${version.installedVersion}${!!beta ? ` BETA` : ''})` : ''} +

    + ); + } + return (

    diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index 92c2363a..0dca39c7 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -28,6 +28,7 @@ export interface Settings { scripts: CustomScript[]; dataFiles: DataFile[] | undefined; dataTypes: DataType[] | undefined; + isBacker: boolean | undefined; } export interface DashboardState { diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index ff886207..ed79b248 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -1,6 +1,6 @@ import { DashboardData } from '../models/DashboardData'; import { Template } from '../commands/Template'; -import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM, CONTEXT } from '../constants'; import * as os from 'os'; import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings'; import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode"; @@ -404,7 +404,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable { commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [], contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], dashboardViewData: Dashboard.viewData, - draftField: Settings.get(SETTINGS_CONTENT_DRAFT_FIELD) + draftField: Settings.get(SETTINGS_CONTENT_DRAFT_FIELD), + isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global') } as PanelSettings }); } diff --git a/src/extension.ts b/src/extension.ts index f3357687..102070bb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,6 +19,7 @@ import ContentProvider from './providers/ContentProvider'; import { Wysiwyg } from './commands/Wysiwyg'; import { Diagnostics } from './commands/Diagnostics'; import { PagesListener } from './listeners'; +import { Backers } from './commands/Backers'; let frontMatterStatusBar: vscode.StatusBarItem; let statusDebouncer: { (fnc: any, time: number): void; }; @@ -31,6 +32,7 @@ export async function activate(context: vscode.ExtensionContext) { const { subscriptions, extensionUri, extensionPath } = context; const extension = Extension.getInstance(context); + Backers.init(context); if (!extension.checkIfExtensionCanRun()) { return undefined; diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index f6f14923..de5faaaa 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -2,7 +2,7 @@ import { basename, join } from "path"; import { workspace } from "vscode"; import { Folders } from "../commands/Folders"; import { Template } from "../commands/Template"; -import { ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_DATA_FOLDERS, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; +import { CONTEXT, ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_DATA_FOLDERS, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models"; import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models"; import { DataFile } from "../models/DataFile"; @@ -51,7 +51,8 @@ export class DashboardSettings { } }, dataFiles: await this.getDataFiles(), - dataTypes: Settings.get(SETTINGS_DATA_TYPES) + dataTypes: Settings.get(SETTINGS_DATA_TYPES), + isBacker: await ext.getState(CONTEXT.backer, 'global') } as ISettings } diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index b8edc95a..ac995ecc 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -20,6 +20,7 @@ export interface PanelSettings { contentTypes: ContentType[]; dashboardViewData: DashboardData | undefined; draftField: DraftField; + isBacker: boolean | undefined; } export interface ContentType { diff --git a/src/panelWebView/ViewPanel.tsx b/src/panelWebView/ViewPanel.tsx index fdcab7da..201a9c1b 100644 --- a/src/panelWebView/ViewPanel.tsx +++ b/src/panelWebView/ViewPanel.tsx @@ -59,7 +59,7 @@ export const ViewPanel: React.FunctionComponent = (props: React - + ); }; \ No newline at end of file diff --git a/src/panelWebView/components/BaseView.tsx b/src/panelWebView/components/BaseView.tsx index 51552b2c..4a1a5b29 100644 --- a/src/panelWebView/components/BaseView.tsx +++ b/src/panelWebView/components/BaseView.tsx @@ -61,7 +61,7 @@ const BaseView: React.FunctionComponent = ({settings, folderAndF - + ); }; diff --git a/src/panelWebView/components/SponsorMsg.tsx b/src/panelWebView/components/SponsorMsg.tsx index 6001038b..d8456e0d 100644 --- a/src/panelWebView/components/SponsorMsg.tsx +++ b/src/panelWebView/components/SponsorMsg.tsx @@ -2,9 +2,15 @@ import * as React from 'react'; import { SPONSOR_LINK } from '../../constants/Links'; import { HeartIcon } from './Icons/HeartIcon'; -export interface ISponsorMsgProps {} +export interface ISponsorMsgProps { + isBacker: boolean | undefined; +} + +const SponsorMsg: React.FunctionComponent = ({ isBacker }: React.PropsWithChildren) => { + if (isBacker) { + return null; + } -const SponsorMsg: React.FunctionComponent = (props: React.PropsWithChildren) => { return (

    diff --git a/src/services/Credentials.ts b/src/services/Credentials.ts new file mode 100644 index 00000000..0261141f --- /dev/null +++ b/src/services/Credentials.ts @@ -0,0 +1,63 @@ +import * as Octokit from '@octokit/rest'; +import { authentication, ExtensionContext } from 'vscode'; + +const GITHUB_AUTH_PROVIDER_ID = 'github'; +// The GitHub Authentication Provider accepts the scopes described here: +// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ +const SCOPES = ['user:email']; + +export class Credentials { + private octokit: Octokit.Octokit | undefined; + + async initialize(context: ExtensionContext): Promise { + this.registerListeners(context); + this.setOctokit(); + } + + private async setOctokit() { + /** + * By passing the `createIfNone` flag, a numbered badge will show up on the accounts activity bar icon. + * An entry for the sample extension will be added under the menu to sign in. This allows quietly + * prompting the user to sign in. + * */ + const session = await authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone: false }); + + if (session) { + this.octokit = new Octokit.Octokit({ + auth: session.accessToken + }); + + return; + } + + this.octokit = undefined; + } + + registerListeners(context: ExtensionContext): void { + /** + * Sessions are changed when a user logs in or logs out. + */ + context.subscriptions.push(authentication.onDidChangeSessions(async e => { + if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) { + await this.setOctokit(); + } + })); + } + + async getOctokit(): Promise { + if (this.octokit) { + return this.octokit; + } + + /** + * When the `createIfNone` flag is passed, a modal dialog will be shown asking the user to sign in. + * Note that this can throw if the user clicks cancel. + */ + const session = await authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone: true }); + this.octokit = new Octokit.Octokit({ + auth: session.accessToken + }); + + return this.octokit; + } +} \ No newline at end of file From 1deb969c2013e7d0f999e02a23da1d1414b31218 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 17 Jan 2022 14:48:46 +0100 Subject: [PATCH 19/41] #227 - Added new supported file types setting --- CHANGELOG.md | 1 + package.json | 26 +++++++++++++++++++++++--- src/constants/settings.ts | 1 + src/helpers/ArticleHelper.ts | 5 +++-- src/models/PanelSettings.ts | 2 +- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa0d1475..e287645c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#193](https://github.com/estruyf/vscode-front-matter/issues/193): Support added for editing data files. - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. - [#225](https://github.com/estruyf/vscode-front-matter/issues/225): Placeholder support for front matter field values (template and content type). +- [#227](https://github.com/estruyf/vscode-front-matter/issues/227): Specify the file types to support with the new `frontMatter.content.supportedFileTypes` setting. - [#228](https://github.com/estruyf/vscode-front-matter/issues/228): Show bulk button actions in panel and dashboard view. - [#231](https://github.com/estruyf/vscode-front-matter/issues/231): Once you authenticate via GitHub as a supporter, the support links will be hidden from the UI. diff --git a/package.json b/package.json index 364783ac..c4e8cd95 100644 --- a/package.json +++ b/package.json @@ -97,12 +97,32 @@ "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.supportedFileTypes": { + "type": "array", + "default": [ + "md", + "mdx", + "markdown" + ], + "markdownDescription": "Specify the file types that you want to use in Front Matter. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.supportedfiletypes)", + "items": { + "type": "string" + }, + "scope": "Content" + }, "frontMatter.content.defaultFileType": { "type": "string", "default": "md", - "enum": [ - "md", - "mdx" + "oneOf": [ + { + "enum": [ + "md", + "mdx" + ] + }, + { + "type": "string" + } ], "markdownDescription": "Specify the default file type for the content to create. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.defaultfiletype)", "scope": "Content" diff --git a/src/constants/settings.ts b/src/constants/settings.ts index a88b8c6a..8d69ee64 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -52,6 +52,7 @@ export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting"; export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting"; export const SETTINGS_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType"; +export const SETTINGS_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes"; export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index ca7ed4a1..8f58a3fd 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -3,7 +3,7 @@ import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ import * as vscode from 'vscode'; import * as matter from "gray-matter"; import * as fs from "fs"; -import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; import { DumpOptions } from 'js-yaml'; import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine'; import { Extension, Logger, Settings } from '.'; @@ -145,7 +145,8 @@ export class ArticleHelper { */ public static isMarkdownFile(document: vscode.TextDocument | undefined | null = null) { const supportedLanguages = ["markdown", "mdx"]; - const supportedFileExtensions = [".md", ".mdx"]; + const fileTypes = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES); + const supportedFileExtensions = fileTypes ? fileTypes.map(f => f.startsWith(`.`) ? f : `.${f}`) : [".md", ".mdx"]; const languageId = document?.languageId?.toLowerCase(); const isSupportedLanguage = languageId && supportedLanguages.includes(languageId); document ??= vscode.window.activeTextEditor?.document; diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index ac995ecc..c6981a1b 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -27,7 +27,7 @@ export interface ContentType { name: string; fields: Field[]; - fileType?: "md" | "mdx"; + fileType?: "md" | "mdx" | string; previewPath?: string | null; pageBundle?: boolean; } From 00bb8c638546edc32a93f1d1efb55f39e6a6213b Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 18 Jan 2022 11:02:05 +0100 Subject: [PATCH 20/41] #227 - File type updates --- src/commands/Folders.ts | 23 +++++++++++++++++------ src/constants/DefaultFileTypes.ts | 3 +++ src/helpers/ArticleHelper.ts | 3 ++- src/helpers/isValidFile.ts | 14 +++++++++++--- src/panelWebView/components/FileItem.tsx | 7 +++++-- webpack/panel.config.js | 4 +++- 6 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 src/constants/DefaultFileTypes.ts diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 77e03034..99116690 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -1,5 +1,5 @@ import { Questions } from './../helpers/Questions'; -import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER } from './../constants'; +import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_CONTENT_SUPPORTED_FILETYPES } from './../constants'; import { commands, Uri, workspace, window } from "vscode"; import { basename, join } from "path"; import { ContentFolder, FileInfo, FolderInfo } from "../models"; @@ -13,6 +13,7 @@ import { Dashboard } from './Dashboard'; import { parseWinPath } from '../helpers/parseWinPath'; import { MediaHelpers } from '../helpers/MediaHelpers'; import { MediaListener, PagesListener } from '../listeners'; +import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; export const WORKSPACE_PLACEHOLDER = `[[workspace]]`; @@ -202,6 +203,7 @@ export class Folders { * Get the registered folders information */ public static async getInfo(limit?: number): Promise { + const supportedFiles = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES); const folders = Folders.get(); if (folders && folders.length > 0) { let folderInfo: FolderInfo[] = []; @@ -214,10 +216,15 @@ export class Folders { if (projectStart) { projectStart = projectStart.replace(/\\/g, '/'); projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart; - const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md')); - const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown')); - const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx')); - let files = [...mdFiles, ...markdownFiles, ...mdxFiles]; + + 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/**'); + files = [...files, ...foundFiles]; + } + if (files) { let fileStats: FileInfo[] = []; @@ -331,4 +338,8 @@ export class Folders { absPath = isWindows ? absPath.split('\\').join('/') : absPath; return absPath; } -} \ No newline at end of file +} + +function SETTINGS_CONTENT_SUPPORTED_FILES(SETTINGS_CONTENT_SUPPORTED_FILES: any) { + throw new Error('Function not implemented.'); +} diff --git a/src/constants/DefaultFileTypes.ts b/src/constants/DefaultFileTypes.ts new file mode 100644 index 00000000..a71cdc3d --- /dev/null +++ b/src/constants/DefaultFileTypes.ts @@ -0,0 +1,3 @@ + + +export const DEFAULT_FILE_TYPES = [".md", ".markdown", ".mdx"]; \ No newline at end of file diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 8f58a3fd..a14ad0a1 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -17,6 +17,7 @@ import { existsSync, mkdirSync } from 'fs'; import { ContentType } from '../models'; import { DateHelper } from './DateHelper'; import { DiagnosticSeverity, Position, window, Range } from 'vscode'; +import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; export class ArticleHelper { private static notifiedFiles: string[] = []; @@ -146,7 +147,7 @@ export class ArticleHelper { public static isMarkdownFile(document: vscode.TextDocument | undefined | null = null) { const supportedLanguages = ["markdown", "mdx"]; const fileTypes = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES); - const supportedFileExtensions = fileTypes ? fileTypes.map(f => f.startsWith(`.`) ? f : `.${f}`) : [".md", ".mdx"]; + const supportedFileExtensions = fileTypes ? fileTypes.map(f => f.startsWith(`.`) ? f : `.${f}`) : DEFAULT_FILE_TYPES; const languageId = document?.languageId?.toLowerCase(); const isSupportedLanguage = languageId && supportedLanguages.includes(languageId); document ??= vscode.window.activeTextEditor?.document; diff --git a/src/helpers/isValidFile.ts b/src/helpers/isValidFile.ts index 3e2e874f..80ebdf4c 100644 --- a/src/helpers/isValidFile.ts +++ b/src/helpers/isValidFile.ts @@ -1,7 +1,15 @@ +import { DEFAULT_FILE_TYPES } from './../constants/DefaultFileTypes'; +import { Settings } from "."; +import { SETTINGS_CONTENT_SUPPORTED_FILETYPES } from "../constants"; +import { extname } from 'path'; export const isValidFile = (fileName: string) => { - return fileName.endsWith(`.md`) || - fileName.endsWith(`.markdown`) || - fileName.endsWith(`.mdx`); + let supportedFiles = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES; + supportedFiles = supportedFiles.map(f => f.startsWith(`.`) ? f : `.${f}`); + + // Get the extension of the file path + const extension = extname(fileName); + + return supportedFiles.includes(extension); } \ No newline at end of file diff --git a/src/panelWebView/components/FileItem.tsx b/src/panelWebView/components/FileItem.tsx index 08cd2544..899e84a4 100644 --- a/src/panelWebView/components/FileItem.tsx +++ b/src/panelWebView/components/FileItem.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { isValidFile } from '../../helpers/isValidFile'; +import { DEFAULT_FILE_TYPES } from '../../constants/DefaultFileTypes'; import { MessageHelper } from '../../helpers/MessageHelper'; import { CommandToCode } from '../CommandToCode'; import { FileIcon } from './Icons/FileIcon'; @@ -16,11 +16,14 @@ const FileItem: React.FunctionComponent = ({ name, path }: React MessageHelper.sendMessage(CommandToCode.openInEditor, path); }; + // File extension + const fileExtension = `.${name.split('.').pop()}`; + return (

  • { - (isValidFile(name)) ? ( + (DEFAULT_FILE_TYPES.includes(fileExtension)) ? ( ) : ( diff --git a/webpack/panel.config.js b/webpack/panel.config.js index a248ed37..ae552b0e 100644 --- a/webpack/panel.config.js +++ b/webpack/panel.config.js @@ -16,7 +16,9 @@ const config = [{ devtool: 'source-map', resolve: { extensions: ['.ts', '.js', '.tsx', '.jsx'], - fallback: { "path": require.resolve("path-browserify") } + fallback: { + "path": require.resolve("path-browserify") + } }, module: { rules: [ From e68daa8ac21c475e68295579e4481dd9f97fbdab Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 18 Jan 2022 18:57:06 +0100 Subject: [PATCH 21/41] Styling updates --- .../components/DataView/DataView.tsx | 4 +-- .../components/SponsorMsg.tsx | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx index 27e087a6..3b8204e6 100644 --- a/src/dashboardWebView/components/DataView/DataView.tsx +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -128,10 +128,10 @@ export const DataView: React.FunctionComponent = (props: React.P }).filter(d => d !== null) as DataFile[]; return ( -
    +
    -
    +
    diff --git a/src/dashboardWebView/components/SponsorMsg.tsx b/src/dashboardWebView/components/SponsorMsg.tsx index b5a610a9..a810c72d 100644 --- a/src/dashboardWebView/components/SponsorMsg.tsx +++ b/src/dashboardWebView/components/SponsorMsg.tsx @@ -10,23 +10,24 @@ export interface ISponsorMsgProps { } export const SponsorMsg: React.FunctionComponent = ({beta, isBacker, version}: React.PropsWithChildren) => { - if (isBacker) { - return ( -

    - Front Matter{version ? ` (v${version.installedVersion}${!!beta ? ` BETA` : ''})` : ''} -

    - ); - } return ( -

    - - Support - - Front Matter{version ? ` (v${version.installedVersion}${!!beta ? ` BETA` : ''})` : ''} - - Review - +

    + { + isBacker ? ( + Front Matter{version ? ` (v${version.installedVersion}${!!beta ? ` BETA` : ''})` : ''} + ) : ( + <> + + Support + + Front Matter{version ? ` (v${version.installedVersion}${!!beta ? ` BETA` : ''})` : ''} + + Review + + + ) + }

    ); }; \ No newline at end of file From 42cc53cefc4879f938677167ab1ca97d1098bd13 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 19 Jan 2022 10:15:58 +0100 Subject: [PATCH 22/41] #226 - Allow to start the local server for the framework or SSG --- package.json | 5 ++ src/constants/FrameworkDetectors.ts | 20 ++++-- src/constants/settings.ts | 1 + src/explorerView/ExplorerView.ts | 69 ++++++++++++------- src/models/PanelSettings.ts | 6 ++ src/panelWebView/CommandToCode.ts | 2 + src/panelWebView/components/Actions.tsx | 3 + src/panelWebView/components/BaseView.tsx | 2 + .../components/GlobalSettings.tsx | 29 ++++++++ .../components/StartServerButton.tsx | 22 ++++++ src/panelWebView/hooks/useStartCommand.tsx | 28 ++++++++ 11 files changed, 158 insertions(+), 29 deletions(-) create mode 100644 src/panelWebView/components/StartServerButton.tsx create mode 100644 src/panelWebView/hooks/useStartCommand.tsx diff --git a/package.json b/package.json index c4e8cd95..c2397bc8 100644 --- a/package.json +++ b/package.json @@ -522,6 +522,11 @@ "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.framework.startCommand": { + "type": ["string", "null"], + "default": null, + "markdownDescription": "Specify the command you want to use to start your static site generator or framework. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.startcommand)" + }, "frontMatter.media.defaultSorting": { "type": "string", "default": "", diff --git a/src/constants/FrameworkDetectors.ts b/src/constants/FrameworkDetectors.ts index 5433233d..b15a7925 100644 --- a/src/constants/FrameworkDetectors.ts +++ b/src/constants/FrameworkDetectors.ts @@ -2,20 +2,32 @@ export const FrameworkDetectors = [ { "framework": {"name": "gatsby", "dist": "public", "static": "static", "build": "gatsby build"}, "requiredFiles": ["gatsby-config.js"], - "requiredDependencies": ["gatsby"] + "requiredDependencies": ["gatsby"], + "commands": { + "start": "npx gatsby develop" + } }, { "framework": {"name": "hugo", "dist": "public", "static": "static", "build": "hugo"}, - "requiredFiles": ["config.toml", "config.yaml", "config.yml"] + "requiredFiles": ["config.toml", "config.yaml", "config.yml"], + "commands": { + "start": "hugo server -D" + } }, { "framework": {"name": "next", "dist": ".next", "static": "public", "build": "next build"}, "requiredFiles": ["next.config.js"], - "requiredDependencies": ["next"] + "requiredDependencies": ["next"], + "commands": { + "start": "npx next dev" + } }, { "framework": {"name": "nuxt", "dist": "dist", "static": "static", "build": "nuxt"}, "requiredFiles": ["nuxt.config.js"], - "requiredDependencies": ["nuxt"] + "requiredDependencies": ["nuxt"], + "commands": { + "start": "npx nuxt" + } } ]; \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 8d69ee64..31e2b535 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -62,6 +62,7 @@ export const SETTINGS_DATA_FOLDERS = "data.folders"; export const SETTINGS_DATA_TYPES = "data.types"; export const SETTINGS_FRAMEWORK_ID = "framework.id"; +export const SETTINGS_FRAMEWORK_START = "framework.startCommand"; export const SETTING_SITE_BASEURL = "site.baseURL"; diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index ed79b248..6178369a 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -1,9 +1,9 @@ import { DashboardData } from '../models/DashboardData'; import { Template } from '../commands/Template'; -import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM, CONTEXT } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM, CONTEXT, SETTINGS_FRAMEWORK_ID, SETTINGS_FRAMEWORK_START } from '../constants'; import * as os from 'os'; import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings'; -import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode"; +import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv, ThemeIcon } from "vscode"; import { ArticleHelper, Settings } from "../helpers"; import { Command } from "../panelWebView/Command"; import { CommandToCode } from '../panelWebView/CommandToCode'; @@ -161,13 +161,13 @@ export class ExplorerView implements WebviewViewProvider, Disposable { await commands.executeCommand(COMMAND_NAME.createTemplate); break; case CommandToCode.updateModifiedUpdating: - this.updateModifiedUpdating(msg.data || false); + this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false); break; case CommandToCode.toggleWritingSettings: this.toggleWritingSettings(); break; case CommandToCode.updateFmHighlight: - this.updateFmHighlight((msg.data !== null && msg.data !== undefined) ? msg.data : false); + this.updateSetting(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false); break; case CommandToCode.toggleCenterMode: await commands.executeCommand(`workbench.action.toggleCenteredLayout`); @@ -179,7 +179,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { await commands.executeCommand(COMMAND_NAME.dashboard); break; case CommandToCode.updatePreviewUrl: - this.updatePreviewUrl(msg.data || ""); + this.updateSetting(SETTING_PREVIEW_HOST, msg.data || ""); break; case CommandToCode.openInEditor: openFileInEditor(msg.data); @@ -194,6 +194,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } as DashboardData); this.getMediaSelection(); break; + case CommandToCode.frameworkCommand: + this.openTerminalWithCommand(msg.data.command); + break; + case CommandToCode.updateStartCommand: + await this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || ""); + break; } }); @@ -344,6 +350,29 @@ export class ExplorerView implements WebviewViewProvider, Disposable { this.pushMetadata(article.data); } + /** + * Open a terminal and run the passed command + * @param command + */ + private openTerminalWithCommand(command: string) { + if (command) { + let terminal = window.activeTerminal; + + if (!terminal || (terminal && terminal.state.isInteractedWith === true)) { + terminal = window.createTerminal({ + name: `Starting local server: ${command}`, + iconPath: new ThemeIcon('server-environment'), + message: `Starting local server: ${command}`, + }); + } + + if (terminal) { + terminal.sendText(command); + terminal.show(false); + } + } + } + /** * Run a custom script * @param msg @@ -405,7 +434,11 @@ export class ExplorerView implements WebviewViewProvider, Disposable { contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], dashboardViewData: Dashboard.viewData, draftField: Settings.get(SETTINGS_CONTENT_DRAFT_FIELD), - isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global') + isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global'), + framework: Settings.get(SETTINGS_FRAMEWORK_ID), + commands: { + start: Settings.get(SETTINGS_FRAMEWORK_START) + } } as PanelSettings }); } @@ -702,26 +735,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } /** - * Update the preview URL + * Updates a setting and refreshes the retrieved settings + * @param setting + * @param value */ - private async updatePreviewUrl(previewUrl: string) { - await Settings.update(SETTING_PREVIEW_HOST, previewUrl); - this.getSettings(); - } - - /** - * Toggle the Front Matter highlighting - */ - private async updateFmHighlight(autoUpdate: boolean) { - await Settings.update(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, autoUpdate); - this.getSettings(); - } - - /** - * Toggle the modified auto-update setting - */ - private async updateModifiedUpdating(autoUpdate: boolean) { - await Settings.update(SETTING_AUTO_UPDATE_DATE, autoUpdate); + private async updateSetting(setting: string, value: any) { + await Settings.update(setting, value); this.getSettings(); } diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index c6981a1b..e64c2a9f 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -21,6 +21,12 @@ export interface PanelSettings { dashboardViewData: DashboardData | undefined; draftField: DraftField; isBacker: boolean | undefined; + framework: string | undefined; + commands: FrameworkCommands; +} + +export interface FrameworkCommands { + start: string | undefined; } export interface ContentType { diff --git a/src/panelWebView/CommandToCode.ts b/src/panelWebView/CommandToCode.ts index 339a9456..e55cf729 100644 --- a/src/panelWebView/CommandToCode.ts +++ b/src/panelWebView/CommandToCode.ts @@ -28,4 +28,6 @@ export enum CommandToCode { selectImage = "select-image", updateCustomTaxonomy = "updateCustomTaxonomy", addToCustomTaxonomy = "addToCustomTaxonomy", + frameworkCommand = "framework-command", + updateStartCommand = "update-start-command", } \ No newline at end of file diff --git a/src/panelWebView/components/Actions.tsx b/src/panelWebView/components/Actions.tsx index e4728a3e..9a51d496 100644 --- a/src/panelWebView/components/Actions.tsx +++ b/src/panelWebView/components/Actions.tsx @@ -4,6 +4,7 @@ import { Collapsible } from './Collapsible'; import { CustomScript } from './CustomScript'; import { Preview } from './Preview'; import { SlugAction } from './SlugAction'; +import { StartServerButton } from './StartServerButton'; export interface IActionsProps { metadata: any; @@ -24,6 +25,8 @@ const Actions: React.FunctionComponent = ({ metadata, settings }: { settings?.preview?.host && } + + { (settings && settings.scripts && settings.scripts.length > 0) && ( settings.scripts.map((value, idx) => ( diff --git a/src/panelWebView/components/BaseView.tsx b/src/panelWebView/components/BaseView.tsx index 4a1a5b29..ad8d6003 100644 --- a/src/panelWebView/components/BaseView.tsx +++ b/src/panelWebView/components/BaseView.tsx @@ -7,6 +7,7 @@ import { GlobalSettings } from './GlobalSettings'; import { OtherActions } from './OtherActions'; import { FolderAndFiles } from './FolderAndFiles'; import { SponsorMsg } from './SponsorMsg'; +import { StartServerButton } from './StartServerButton'; export interface IBaseViewProps { settings: PanelSettings | undefined; @@ -45,6 +46,7 @@ const BaseView: React.FunctionComponent = ({settings, folderAndF
    + diff --git a/src/panelWebView/components/GlobalSettings.tsx b/src/panelWebView/components/GlobalSettings.tsx index fe503b15..ddcfce9b 100644 --- a/src/panelWebView/components/GlobalSettings.tsx +++ b/src/panelWebView/components/GlobalSettings.tsx @@ -5,6 +5,7 @@ import { MessageHelper } from '../../helpers/MessageHelper'; import { useDebounce } from '../../hooks/useDebounce'; import { Collapsible } from './Collapsible'; import { VsCheckbox, VsLabel } from './VscodeComponents'; +import useStartCommand from '../hooks/useStartCommand'; export interface IGlobalSettingsProps { settings: PanelSettings | undefined; @@ -14,7 +15,11 @@ export interface IGlobalSettingsProps { const GlobalSettings: React.FunctionComponent = ({settings, isBase}: React.PropsWithChildren) => { const { modifiedDateUpdate, fmHighlighting } = settings || {}; const [ previewUrl, setPreviewUrl ] = React.useState(""); + const [ startCommandValue, setStartCommandValue ] = React.useState(null); const [ isDirty, setIsDirty ] = React.useState(false); + const { startCommand } = useStartCommand(settings); + + const debounceStartCommand = useDebounce(startCommandValue, 1000); const debouncePreviewUrl = useDebounce(previewUrl, 1000); const onDateCheck = () => { @@ -30,12 +35,21 @@ const GlobalSettings: React.FunctionComponent = ({settings setPreviewUrl(e.currentTarget.value); }; + const updateStartCommand = (e: React.ChangeEvent) => { + setIsDirty(true); + setStartCommandValue(e.currentTarget.value); + }; + React.useEffect(() => { if (settings?.preview.host) { setPreviewUrl(settings.preview.host); } }, [settings?.preview.host]); + React.useEffect(() => { + setStartCommandValue(startCommand); + }, [startCommand]); + React.useEffect(() => { if (isDirty) { setIsDirty(false); @@ -43,6 +57,13 @@ const GlobalSettings: React.FunctionComponent = ({settings } }, [debouncePreviewUrl]); + React.useEffect(() => { + if (isDirty) { + setIsDirty(false); + MessageHelper.sendMessage(CommandToCode.updateStartCommand, debounceStartCommand); + } + }, [debounceStartCommand]); + return ( <> @@ -62,6 +83,14 @@ const GlobalSettings: React.FunctionComponent = ({settings value={previewUrl} onChange={previewChange} />
    +
    + Local server command + +
    ); diff --git a/src/panelWebView/components/StartServerButton.tsx b/src/panelWebView/components/StartServerButton.tsx new file mode 100644 index 00000000..6ab35f20 --- /dev/null +++ b/src/panelWebView/components/StartServerButton.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import { FrameworkDetectors } from '../../constants/FrameworkDetectors'; +import { MessageHelper } from '../../helpers/MessageHelper'; +import { PanelSettings } from '../../models'; +import { CommandToCode } from '../CommandToCode'; +import useStartCommand from '../hooks/useStartCommand'; + +export interface IStartServerButtonProps { + settings: PanelSettings | undefined; +} + +export const StartServerButton: React.FunctionComponent = ({settings}: React.PropsWithChildren) => { + const { startCommand } = useStartCommand(settings); + + const startLocalServer = (command: string) => { + MessageHelper.sendMessage(CommandToCode.frameworkCommand, { command }); + }; + + return ( + startCommand ? : null + ); +}; \ No newline at end of file diff --git a/src/panelWebView/hooks/useStartCommand.tsx b/src/panelWebView/hooks/useStartCommand.tsx new file mode 100644 index 00000000..e8045393 --- /dev/null +++ b/src/panelWebView/hooks/useStartCommand.tsx @@ -0,0 +1,28 @@ +import { useState, useEffect } from 'react'; +import { FrameworkDetectors } from '../../constants/FrameworkDetectors'; +import { PanelSettings } from '../../models'; + +export default function useStartCommand(settings?: PanelSettings) { + const [startCommand, setStartCommand] = useState(null); + + useEffect(() => { + if (settings?.commands?.start) { + setStartCommand(settings?.commands?.start); + return; + } + + let command: string = ''; + if (settings?.framework) { + const framework = FrameworkDetectors.find(f => f.framework.name === settings.framework); + if (framework?.commands?.start) { + command = framework.commands.start; + } + } + + setStartCommand(command); + }, [settings?.framework, settings?.commands?.start]); + + return { + startCommand + }; +} \ No newline at end of file From 7d6d60039e32e148a2235414a12b0cd89cc79ec6 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 19 Jan 2022 12:59:25 +0100 Subject: [PATCH 23/41] Optimized styling for data dashboard --- CHANGELOG.md | 2 + package.json | 5 +- src/dashboardWebView/styles.css | 174 +++++++++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e287645c..f73c2a2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#193](https://github.com/estruyf/vscode-front-matter/issues/193): Support added for editing data files. - [#197](https://github.com/estruyf/vscode-front-matter/issues/197): Support for multi-dimensional content type fields on content creation and editing. - [#225](https://github.com/estruyf/vscode-front-matter/issues/225): Placeholder support for front matter field values (template and content type). +- [#226](https://github.com/estruyf/vscode-front-matter/issues/226): Ability to specify the local server start command and trigger it from the UI. - [#227](https://github.com/estruyf/vscode-front-matter/issues/227): Specify the file types to support with the new `frontMatter.content.supportedFileTypes` setting. - [#228](https://github.com/estruyf/vscode-front-matter/issues/228): Show bulk button actions in panel and dashboard view. - [#231](https://github.com/estruyf/vscode-front-matter/issues/231): Once you authenticate via GitHub as a supporter, the support links will be hidden from the UI. @@ -15,6 +16,7 @@ - Added default field value for content type fields - HMR support for panel webview development +- [#187](https://github.com/estruyf/vscode-front-matter/issues/187): Svelte support with the [#227](https://github.com/estruyf/vscode-front-matter/issues/227) features has been added. - [#198](https://github.com/estruyf/vscode-front-matter/issues/198): Additional media sort options (alt, caption, and size). - [#230](https://github.com/estruyf/vscode-front-matter/issues/230): JSON front matter support added. diff --git a/package.json b/package.json index c2397bc8..a62cbc67 100644 --- a/package.json +++ b/package.json @@ -523,7 +523,10 @@ "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.framework.startCommand": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "default": null, "markdownDescription": "Specify the command you want to use to start your static site generator or framework. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.startcommand)" }, diff --git a/src/dashboardWebView/styles.css b/src/dashboardWebView/styles.css index c521e8e7..e0f051aa 100644 --- a/src/dashboardWebView/styles.css +++ b/src/dashboardWebView/styles.css @@ -46,7 +46,7 @@ } input { - @apply w-full text-vulcan-500; + @apply w-full text-vulcan-500 px-2 py-1; &::placeholder { @apply text-gray-500; @@ -56,13 +56,22 @@ .ant-form-item-has-error .ant-form-item-control-input { @apply relative; } + + .ant-form-item-has-error { + label { + &::after { + content: ' *'; + @apply text-red-400; + } + } + } .ant-form-item-has-error input { @apply border border-red-400; } .ant-form-item-has-error .ant-form-item-children-icon { - @apply text-red-400 absolute right-1 top-2; + @apply text-red-400 absolute right-1 top-1; svg { @apply w-4 h-4; @@ -103,7 +112,158 @@ .fields {} .ant-list.ant-list-bordered { - @apply border-t border-gray-300; + @apply border border-gray-300; + } + + .ant-btn-dashed { + @apply border border-gray-300 border-dashed flex items-center justify-center py-1 mt-2; + + &:hover { + @apply text-teal-900 border-teal-900; + } + } + + .ant-input:hover, .ant-input-focused, .ant-input:focus { + @apply border-teal-600; + } + + .ant-btn-ghost:focus, .ant-btn-ghost:hover { + @apply text-teal-600 border-teal-700; + } + + .ant-switch-checked { + @apply bg-teal-500; + } + + .ant-switch { + @apply bg-gray-400; + + margin: 0; + padding: 0; + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5715; + list-style: none; + font-feature-settings: "tnum"; + position: relative; + display: inline-block; + box-sizing: border-box; + min-width: 44px; + height: 22px; + line-height: 22px; + vertical-align: middle; + border: 0; + border-radius: 100px; + cursor: pointer; + transition: all .2s; + user-select: none; + + &.ant-switch-checked { + @apply bg-teal-500; + + .ant-switch-handle { + left: calc(100% - 20px); + } + } + + .ant-switch-handle { + position: absolute; + top: 2px; + left: 2px; + width: 18px; + height: 18px; + transition: all .2s ease-in-out; + + &::before { + position: absolute; + inset: 0; + background-color: #fff; + border-radius: 9px; + box-shadow: 0 2px 4px #00230b33; + transition: all .2s ease-in-out; + content: ""; + } + } + + .ant-switch-inner { + display: block; + margin: 0 7px 0 25px; + color: #fff; + font-size: 12px; + transition: margin .2s; + + svg { + display: none; + } + } + } + + .ant-input-number { + @apply border; + + box-sizing: border-box; + font-variant: tabular-nums; + list-style: none; + font-feature-settings: "tnum"; + position: relative; + width: 100%; + min-width: 0; + color: #000000d9; + font-size: 14px; + line-height: 1.5715; + background-color: #fff; + background-image: none; + transition: all .3s; + display: inline-block; + width: 90px; + margin: 0; + padding: 0; + + &.ant-input-number-focused { + @apply border-teal-600; + } + } + + .ant-input-number-handler { + position: relative; + display: block; + width: 100%; + height: 50%; + overflow: hidden; + color: #00000073; + font-weight: 700; + line-height: 0; + text-align: center; + border-left: 1px solid #d9d9d9; + transition: all .1s linear; + } + + .ant-input-number-handler-wrap { + position: absolute; + top: 0; + right: 0; + width: 22px; + height: 100%; + background: #fff; + opacity: 0; + transition: opacity .24s linear .1s; + } + + .ant-input-number-input { + @apply px-2 py-1; + + width: 100%; + height: 30px; + text-align: left; + background-color: transparent; + border: 0; + outline: 0; + transition: all .3s linear; + appearance: textfield !important; + } + + .ant-input-number:hover .ant-input-number-handler-wrap, .ant-input-number-focused .ant-input-number-handler-wrap { + opacity: 1; } } @@ -127,4 +287,12 @@ .ant-list.ant-list-bordered { @apply border-vulcan-100; } + + .ant-btn-dashed { + @apply border-vulcan-50; + + &:hover { + @apply text-teal-400 border-teal-900; + } + } } \ No newline at end of file From d7a0f71552b0b94f891b8be546e6ac5288c9de18 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 19 Jan 2022 13:53:19 +0100 Subject: [PATCH 24/41] #225 - Optimize slug will now process all fields with `{{slug}}` --- src/commands/Article.ts | 33 +++++++++++++++++++++++++++------ src/helpers/ArticleHelper.ts | 11 ++++++++--- src/helpers/ContentType.ts | 2 +- src/helpers/SlugHelper.ts | 2 +- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 5e4a228f..681753fb 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -1,7 +1,7 @@ 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 } from './../constants'; +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, SETTINGS_CONTENT_PLACEHOLDERS } from './../constants'; import * as vscode from 'vscode'; -import { TaxonomyType } from "../models"; +import { Field, TaxonomyType } from "../models"; import { format } from "date-fns"; import { ArticleHelper, Settings, SlugHelper } from '../helpers'; import matter = require('gray-matter'); @@ -180,11 +180,32 @@ export class Article { return; } - const articleTitle: string = article.data["title"]; - let slug = SlugHelper.createSlug(articleTitle); + const contentType = ArticleHelper.getContentType(article.data); + const titleField = "title"; + const articleTitle: string = article.data[titleField]; + + const slug = SlugHelper.createSlug(articleTitle); if (slug) { - slug = `${prefix}${slug}${suffix}`; - article.data["slug"] = slug; + let slugFieldValue = `${prefix}${slug}${suffix}`; + article.data["slug"] = slugFieldValue; + + if (contentType) { + let fieldsToUpdate: Field[] = contentType.fields.filter(f => f.default === "{{slug}}"); + for (const field of fieldsToUpdate) { + article.data[field.name] = slug; + } + + const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS); + const customPlaceholders = placeholders?.filter(p => p.value.includes("{{slug}}")); + 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] = ArticleHelper.processKnownPlaceholders(article.data[pField.name], articleTitle); + } + } + } + ArticleHelper.update(editor, article); // Check if the file name should be updated by the slug diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index a14ad0a1..d70293ba 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -6,7 +6,7 @@ import * as fs from "fs"; import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; import { DumpOptions } from 'js-yaml'; import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine'; -import { Extension, Logger, Settings } from '.'; +import { Extension, Logger, Settings, SlugHelper } from '.'; import { format, parse } from 'date-fns'; import { Notifications } from './Notifications'; import { Article } from '../commands'; @@ -293,7 +293,7 @@ export class ArticleHelper { } if (fieldName === "slug" && (fieldValue === null || fieldValue === "")) { - fmData[fieldName] = ArticleHelper.sanitize(title); + fmData[fieldName] = SlugHelper.createSlug(title); } fmData[fieldName] = this.processKnownPlaceholders(fmData[fieldName], title); @@ -318,7 +318,12 @@ export class ArticleHelper { if (value.includes("{{slug}}")) { const regex = new RegExp("{{slug}}", "g"); - value = value.replace(regex, ArticleHelper.sanitize(title)); + value = value.replace(regex, SlugHelper.createSlug(title) || ""); + } + + if (value.includes("{{now}}")) { + const regex = new RegExp("{{now}}", "g"); + value = value.replace(regex, Article.formatDate(new Date())); } } diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index d9447e24..c54e3e9e 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -138,7 +138,7 @@ export class ContentType { if (obj.fields) { for (const field of obj.fields) { - if (field.name === "title") { + if (field.name === "title") { if (field.default) { data[field.name] = ArticleHelper.processKnownPlaceholders(field.default, titleValue); data[field.name] = ArticleHelper.processCustomPlaceholders(data[field.name], titleValue); diff --git a/src/helpers/SlugHelper.ts b/src/helpers/SlugHelper.ts index 70ffc0a7..7de5ca97 100644 --- a/src/helpers/SlugHelper.ts +++ b/src/helpers/SlugHelper.ts @@ -13,7 +13,7 @@ export class SlugHelper { } // Remove punctuation from input string, and split it into words. - let cleanTitle = this.removePunctuation(articleTitle); + let cleanTitle = this.removePunctuation(articleTitle).trim(); if (cleanTitle) { cleanTitle = cleanTitle.toLowerCase(); // Split into words From 61b46bc5acb2f5ff1ee360e76b722167624c8ab5 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 19 Jan 2022 14:00:05 +0100 Subject: [PATCH 25/41] Update comments --- src/commands/Article.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 681753fb..cfd5e11f 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -190,11 +190,13 @@ export class Article { article.data["slug"] = slugFieldValue; if (contentType) { + // Update the fields containing the slug placeholder let fieldsToUpdate: Field[] = contentType.fields.filter(f => f.default === "{{slug}}"); for (const field of fieldsToUpdate) { article.data[field.name] = slug; } + // Update the fields containing a custom placeholder that depends on slug const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS); const customPlaceholders = placeholders?.filter(p => p.value.includes("{{slug}}")); for (const customPlaceholder of (customPlaceholders || [])) { From c5523f7aaf5ed09e6d6a50621604fef24007b7af Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 19 Jan 2022 14:08:17 +0100 Subject: [PATCH 26/41] Update overflow on data view --- src/dashboardWebView/components/DataView/DataView.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx index 3b8204e6..52cbe75f 100644 --- a/src/dashboardWebView/components/DataView/DataView.tsx +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -131,11 +131,11 @@ export const DataView: React.FunctionComponent = (props: React.P
    -
    +
    -