forked from iarv/vscode-front-matter
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71072d9520 | ||
|
|
b64dd8f88a | ||
|
|
173c89d86f | ||
|
|
f5f558d5bc | ||
|
|
c9c38ef10b | ||
|
|
c30f401c4f | ||
|
|
9b92050af8 | ||
|
|
31a41e2a66 | ||
|
|
baa56bc246 | ||
|
|
f53e81e0cb | ||
|
|
f454266846 | ||
|
|
0ba3c22795 | ||
|
|
ff38cf361c | ||
|
|
57e93b91c5 | ||
|
|
c1161b95ed | ||
|
|
32dc63b62a | ||
|
|
0c1198c802 | ||
|
|
ed4b78cfdc | ||
|
|
65f77baf2b | ||
|
|
eabdf00d3d | ||
|
|
c084a15e08 | ||
|
|
e577ba591e | ||
|
|
b17c7f888a | ||
|
|
0ed41b7d7e | ||
|
|
2e1faaa34f | ||
|
|
63f02f4f0e | ||
|
|
489fc5ec9e | ||
|
|
4c8ecdb344 | ||
|
|
8d705ff6c5 | ||
|
|
cfe68e65e8 | ||
|
|
0e179f5fd7 | ||
|
|
6cabd6283b | ||
|
|
6135e38fce | ||
|
|
935b2230af | ||
|
|
6dcd89e9cd | ||
|
|
2775b2051f | ||
|
|
5ebb2d7370 | ||
|
|
c9488e6661 | ||
|
|
442261e655 | ||
|
|
1aa2d41c95 | ||
|
|
e5a2194c23 | ||
|
|
cf6f051ee8 | ||
|
|
bebde4de68 | ||
|
|
174c4b7734 | ||
|
|
1f7519ee60 | ||
|
|
b6482546a5 | ||
|
|
0decd84f7f | ||
|
|
a1dbda0b23 | ||
|
|
427245f211 | ||
|
|
4678189eab | ||
|
|
15d89e34cf | ||
|
|
cbb0d8f72b | ||
|
|
131150f5a6 | ||
|
|
a31bca73e7 | ||
|
|
1d5f940c94 | ||
|
|
70ea6a5a16 | ||
|
|
849af69ce2 | ||
|
|
754570a9ec | ||
|
|
f7f6f26997 | ||
|
|
946d84a7a9 | ||
|
|
781ab6ac40 | ||
|
|
df86d02e8b | ||
|
|
19e468c908 | ||
|
|
5a81ea19b8 | ||
|
|
64a38e56b9 | ||
|
|
fca0528a7e | ||
|
|
936916acf8 | ||
|
|
61e9fc0308 | ||
|
|
2356623d7a | ||
|
|
ee70acebb6 |
39
CHANGELOG.md
39
CHANGELOG.md
@@ -1,6 +1,43 @@
|
||||
# Change Log
|
||||
|
||||
## [6.0.0] - 2022-01-xx - [Release Notes](https://beta.frontmatter.codes/updates/v6.0.0)
|
||||
## [6.1.1] - 2022-03-02
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#275](https://github.com/estruyf/vscode-front-matter/issues/275): Fix for rendering the panel when content contains an invalid markdown syntax tree
|
||||
|
||||
## [6.1.0] - 2022-02-28 - [Release notes](https://beta.frontmatter.codes/updates/v6.1.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
- [#176](https://github.com/estruyf/vscode-front-matter/issues/176): New `block` field type that allows you to you to define a group of fields which can be used to create a list of data
|
||||
|
||||
### 🎨 Enhancements
|
||||
|
||||
- Updated the activity bar icon for better visibility
|
||||
- Storing the panel collapse section states
|
||||
- [#241](https://github.com/estruyf/vscode-front-matter/issues/241): Added taxonomy limit field property which allows you to limit the number of selections
|
||||
- [#242](https://github.com/estruyf/vscode-front-matter/issues/242): Keep comments at the root of the front matter
|
||||
- [#248](https://github.com/estruyf/vscode-front-matter/issues/248): Added support for front matter highlighting to all file types specified in `frontMatter.content.supportedFileTypes`
|
||||
- [#255](https://github.com/estruyf/vscode-front-matter/issues/255): Added support for default values on block fields / data creation
|
||||
- [#257](https://github.com/estruyf/vscode-front-matter/issues/257): Allow preview images to be used in multi-dimensional fields
|
||||
- [#271](https://github.com/estruyf/vscode-front-matter/issues/271): Added image size placeholders for media snippets
|
||||
|
||||
### ⚡️ Optimizations
|
||||
|
||||
- Show the data item its details when clicking on the record
|
||||
- Refactoring of the explorer view panel listeners
|
||||
- Added `{{now}}` placeholder to the publishing date for content creation
|
||||
- [#243](https://github.com/estruyf/vscode-front-matter/issues/243): Refactoring front matter parsing
|
||||
|
||||
### 🐞 Fixes
|
||||
|
||||
- [#247](https://github.com/estruyf/vscode-front-matter/issues/247): Fix the front matter highlighting in markdown documents
|
||||
- [#261](https://github.com/estruyf/vscode-front-matter/issues/261): Fix to allow that tag and category fields can be renamed
|
||||
- [#264](https://github.com/estruyf/vscode-front-matter/issues/264): Fix for Windows paths on content folder registration
|
||||
- [#268](https://github.com/estruyf/vscode-front-matter/issues/268): Fix for panel which only shows loading indicator
|
||||
|
||||
## [6.0.0] - 2022-01-25 - [Release Notes](https://beta.frontmatter.codes/updates/v6.0.0)
|
||||
|
||||
### ✨ New features
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
}
|
||||
|
||||
.inherit {
|
||||
position: inherit !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.z-10 { z-index: 10 !important; }
|
||||
@@ -143,6 +143,7 @@
|
||||
}
|
||||
|
||||
.article__tags {
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -165,6 +166,10 @@
|
||||
border: 1px solid var(--vscode-inputValidation-infoBorder);
|
||||
}
|
||||
|
||||
.article__tags__input input:disabled {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.article__tags__input.freeform {
|
||||
position: relative;
|
||||
}
|
||||
@@ -448,17 +453,17 @@ input:checked + .field__toggle__slider:before {
|
||||
|
||||
.vscode-dark .metadata_field__box {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 2px dashed rgba(255, 255, 255, 0.2);
|
||||
border: 1px 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);
|
||||
border: 1px 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);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.2);
|
||||
margin-bottom: .5rem;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
@@ -634,7 +639,7 @@ input:checked + .field__toggle__slider:before {
|
||||
|
||||
.metadata_field__preview_image__button {
|
||||
background-color: transparent;
|
||||
border: 2px dashed var(--vscode-button-background);
|
||||
border: 1px dashed var(--vscode-button-background);
|
||||
padding: 1.5rem;
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
1352
package-lock.json
generated
1352
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
143
package.json
143
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": "6.0.0",
|
||||
"version": "6.1.1",
|
||||
"preview": false,
|
||||
"publisher": "eliostruyf",
|
||||
"galleryBanner": {
|
||||
@@ -45,26 +45,15 @@
|
||||
"url": "https://github.com/estruyf/vscode-front-matter"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*",
|
||||
"onCommand:frontMatter.insertTags",
|
||||
"onCommand:frontMatter.insertCategories",
|
||||
"onCommand:frontMatter.createTag",
|
||||
"onCommand:frontMatter.createCategory",
|
||||
"onCommand:frontMatter.exportTaxonomy",
|
||||
"onCommand:frontMatter.remap",
|
||||
"onCommand:frontMatter.setLastModifiedDate",
|
||||
"onCommand:frontMatter.generateSlug",
|
||||
"onCommand:frontMatter.createFromTemplate",
|
||||
"onCommand:frontMatter.registerFolder",
|
||||
"onCommand:frontMatter.unregisterFolder",
|
||||
"onCommand:frontMatter.createContent",
|
||||
"workspaceContains:**/.frontmatter",
|
||||
"workspaceContains:**/frontmatter.json",
|
||||
"onCommand:frontMatter.init",
|
||||
"onCommand:frontMatter.collapseSections",
|
||||
"onCommand:frontMatter.preview",
|
||||
"onCommand:frontMatter.dashboard",
|
||||
"onCommand:frontMatter.promoteSettings",
|
||||
"onCommand:frontMatter.insertImage",
|
||||
"onView:frontMatter.explorer"
|
||||
"onCommand:frontMatter.dashboard.data",
|
||||
"onCommand:frontMatter.dashboard.media",
|
||||
"onCommand:workbench.view.extension.frontmatter-explorer",
|
||||
"onView:frontMatter.explorer",
|
||||
"onStartupFinished"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
@@ -73,7 +62,7 @@
|
||||
{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FrontMatter",
|
||||
"icon": "assets/frontmatter.svg"
|
||||
"icon": "assets/frontmatter-short-min.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -82,7 +71,7 @@
|
||||
{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "FrontMatter",
|
||||
"icon": "assets/frontmatter.svg",
|
||||
"icon": "assets/frontmatter-short-min.svg",
|
||||
"contextualTitle": "FrontMatter",
|
||||
"type": "webview"
|
||||
}
|
||||
@@ -360,7 +349,7 @@
|
||||
"markdownDescription": "Specify the a snippet for your custom media insert markup. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.mediasnippet)",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The parts of your snippet. Use `{mediaUrl}` as placeholder where the path of the image needs to be inserted."
|
||||
"description": "Use the `{mediaUrl}`, `{caption}`, `{alt}`, `{filename}`, `{mediaHeight}`, and `{mediaWidth}` placeholders in your snippet to automatically insert the media information."
|
||||
},
|
||||
"scope": "dashboard"
|
||||
},
|
||||
@@ -517,6 +506,12 @@
|
||||
},
|
||||
"scope": "Data"
|
||||
},
|
||||
"frontMatter.file.preserveCasing": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"markdownDescription": "Specify if you want to preserve the casing of your file names from the title. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.file.preservecasing)",
|
||||
"scope": "File"
|
||||
},
|
||||
"frontMatter.framework.id": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
@@ -651,7 +646,9 @@
|
||||
"tags",
|
||||
"categories",
|
||||
"draft",
|
||||
"fields"
|
||||
"fields",
|
||||
"json",
|
||||
"block"
|
||||
],
|
||||
"description": "Define the type of field"
|
||||
},
|
||||
@@ -717,6 +714,33 @@
|
||||
},
|
||||
"fields": {
|
||||
"$ref": "#contenttypefield"
|
||||
},
|
||||
"fieldGroup": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"default": [],
|
||||
"description": "The ID(s) of your field group(s) defined in the `frontMatter.taxonomy.fieldGroups` setting",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"dataType": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"default": [],
|
||||
"description": "The ID(s) of your data type(s) defined in the `frontMatter.data.types` setting",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"taxonomyLimit": {
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"description": "Limit the number of taxonomies to select. Set to 0 to allow unlimited."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -766,6 +790,34 @@
|
||||
"fields"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "block"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"fieldGroup"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"dataType"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -808,7 +860,8 @@
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime"
|
||||
"type": "datetime",
|
||||
"default": "{{now}}"
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
@@ -871,6 +924,28 @@
|
||||
"markdownDescription": "Specify the date format for your articles. Check [date-fns formating](https://date-fns.org/v2.0.1/docs/format) for more information. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.taxonomy.dateformat)",
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.fieldGroups": {
|
||||
"type": "array",
|
||||
"markdownDescription": "Define the field groups you want to use for your block fields. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.taxonomy.fieldgroups)",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The name of the field group"
|
||||
},
|
||||
"fields": {
|
||||
"$ref": "#contenttypefield"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"fields"
|
||||
]
|
||||
}
|
||||
},
|
||||
"frontMatter.taxonomy.frontMatterType": {
|
||||
"type": "string",
|
||||
"default": "YAML",
|
||||
@@ -956,6 +1031,11 @@
|
||||
},
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.telemetry.disable": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"markdownDescription": "Specify if you want to disable the telemetry. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.telemetry.disable)"
|
||||
},
|
||||
"frontMatter.templates.folder": {
|
||||
"type": "string",
|
||||
"default": ".frontmatter/templates",
|
||||
@@ -1421,8 +1501,11 @@
|
||||
"@sentry/tracing": "^6.13.3",
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"@types/glob": "7.1.3",
|
||||
"@types/invariant": "^2.2.35",
|
||||
"@types/js-yaml": "3.12.1",
|
||||
"@types/lodash.omit": "^4.5.6",
|
||||
"@types/lodash.uniqby": "4.7.6",
|
||||
"@types/lodash.xor": "^4.5.6",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/node": "10.17.48",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
@@ -1431,7 +1514,8 @@
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "^1.63.0",
|
||||
"@vscode/codicons": "0.0.20",
|
||||
"@vscode/webview-ui-toolkit": "^0.8.1",
|
||||
"@vscode/extension-telemetry": "^0.4.7",
|
||||
"@vscode/webview-ui-toolkit": "^0.9.1",
|
||||
"@webpack-cli/serve": "^1.6.0",
|
||||
"ajv": "^8.8.2",
|
||||
"array-move": "^4.0.0",
|
||||
@@ -1445,8 +1529,11 @@
|
||||
"html-loader": "1.3.2",
|
||||
"html-webpack-plugin": "4.5.0",
|
||||
"image-size": "^1.0.0",
|
||||
"invariant": "^2.2.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"lodash.uniqby": "4.7.0",
|
||||
"lodash.xor": "^4.5.0",
|
||||
"mdast-util-from-markdown": "1.0.0",
|
||||
"node-json-db": "^1.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
@@ -1476,9 +1563,11 @@
|
||||
"webpack": "^5.65.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.6.0"
|
||||
"webpack-dev-server": "^4.6.0",
|
||||
"yaml": "^1.10.2",
|
||||
"yawn-yaml": "^1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
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, SETTINGS_CONTENT_PLACEHOLDERS } 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, TelemetryEvent } from './../constants';
|
||||
import * as vscode from 'vscode';
|
||||
import { Field, TaxonomyType } from "../models";
|
||||
import { format } from "date-fns";
|
||||
import { ArticleHelper, Settings, SlugHelper } from '../helpers';
|
||||
import matter = require('gray-matter');
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
import { extname, basename, parse, dirname } from 'path';
|
||||
import { COMMAND_NAME, DefaultFields } from '../constants';
|
||||
import { DashboardData } from '../models/DashboardData';
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { DateHelper } from '../helpers/DateHelper';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
import { MediaListener } from '../listeners/panel';
|
||||
|
||||
|
||||
export class Article {
|
||||
@@ -102,7 +103,7 @@ export class Article {
|
||||
* Update the date in the front matter
|
||||
* @param article
|
||||
*/
|
||||
public static updateDate(article: matter.GrayMatterFile<string>, forceCreate: boolean = false) {
|
||||
public static updateDate(article: ParsedFrontMatter, forceCreate: boolean = false) {
|
||||
article.data = ArticleHelper.updateDates(article.data);
|
||||
return article;
|
||||
}
|
||||
@@ -124,7 +125,7 @@ export class Article {
|
||||
|
||||
ArticleHelper.update(
|
||||
editor,
|
||||
updatedArticle as matter.GrayMatterFile<string>
|
||||
updatedArticle as ParsedFrontMatter
|
||||
);
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ export class Article {
|
||||
|
||||
private static setLastModifiedDateInner(
|
||||
document: vscode.TextDocument
|
||||
): matter.GrayMatterFile<string> | undefined {
|
||||
): ParsedFrontMatter | undefined {
|
||||
const article = ArticleHelper.getFrontMatterFromDocument(document);
|
||||
|
||||
if (!article) {
|
||||
@@ -165,6 +166,8 @@ export class Article {
|
||||
* Generate the slug based on the article title
|
||||
*/
|
||||
public static async generateSlug() {
|
||||
Telemetry.send(TelemetryEvent.generateSlug);
|
||||
|
||||
const prefix = Settings.get(SETTING_SLUG_PREFIX) as string;
|
||||
const suffix = Settings.get(SETTING_SLUG_SUFFIX) as string;
|
||||
const updateFileName = Settings.get(SETTING_SLUG_UPDATE_FILE_NAME) as string;
|
||||
@@ -334,13 +337,13 @@ export class Article {
|
||||
} as DashboardData);
|
||||
|
||||
// Let the editor panel know you are selecting an image
|
||||
ExplorerView.getInstance().getMediaSelection();
|
||||
MediaListener.getMediaSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current article
|
||||
*/
|
||||
private static getCurrent(): matter.GrayMatterFile<string> | undefined {
|
||||
private static getCurrent(): ParsedFrontMatter | undefined {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
@@ -361,7 +364,7 @@ export class Article {
|
||||
* @param field
|
||||
* @param forceCreate
|
||||
*/
|
||||
private static articleDate(article: matter.GrayMatterFile<string>, field: string, forceCreate: boolean) {
|
||||
private static articleDate(article: ParsedFrontMatter, field: string, forceCreate: boolean) {
|
||||
if (typeof article.data[field] !== "undefined" || forceCreate) {
|
||||
article.data[field] = Article.formatDate(new Date());
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Credentials } from "../services/Credentials";
|
||||
import fetch from "node-fetch";
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { Dashboard } from './Dashboard';
|
||||
import { SettingsListener } from '../listeners/panel';
|
||||
|
||||
export class Backers {
|
||||
private static creds: Credentials | null = null;
|
||||
@@ -60,7 +61,7 @@ export class Backers {
|
||||
if (!prevData) {
|
||||
const explorerView = ExplorerView.getInstance();
|
||||
if (explorerView.visible) {
|
||||
explorerView.getSettings();
|
||||
SettingsListener.getSettings();
|
||||
}
|
||||
|
||||
if (Dashboard.isOpen) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { PagesListener } from './../listeners/PagesListener';
|
||||
import { ExtensionListener } from './../listeners/ExtensionListener';
|
||||
import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
|
||||
import { join } from "path";
|
||||
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode";
|
||||
@@ -10,8 +8,8 @@ import { WebviewHelper } from '@estruyf/vscode';
|
||||
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';
|
||||
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener } from '../listeners/dashboard';
|
||||
import { MediaListener as PanelMediaListener } from '../listeners/panel'
|
||||
|
||||
export class Dashboard {
|
||||
private static webview: WebviewPanel | null = null;
|
||||
@@ -116,8 +114,7 @@ export class Dashboard {
|
||||
Dashboard.webview.onDidChangeViewState(async () => {
|
||||
if (!this.webview?.visible) {
|
||||
Dashboard._viewData = undefined;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
panel.getMediaSelection();
|
||||
PanelMediaListener.getMediaSelection();
|
||||
|
||||
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: null });
|
||||
}
|
||||
@@ -128,8 +125,7 @@ export class Dashboard {
|
||||
Dashboard.webview.onDidDispose(async () => {
|
||||
Dashboard.isDisposed = true;
|
||||
Dashboard._viewData = undefined;
|
||||
const panel = ExplorerView.getInstance(extensionUri);
|
||||
panel.getMediaSelection();
|
||||
PanelMediaListener.getMediaSelection();
|
||||
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false);
|
||||
});
|
||||
|
||||
@@ -146,6 +142,7 @@ export class Dashboard {
|
||||
PagesListener.process(msg);
|
||||
SettingsListener.process(msg);
|
||||
DataListener.process(msg);
|
||||
TelemetryListener.process(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Questions } from './../helpers/Questions';
|
||||
import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_CONTENT_SUPPORTED_FILETYPES } from './../constants';
|
||||
import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants';
|
||||
import { commands, Uri, workspace, window } from "vscode";
|
||||
import { basename, join } from "path";
|
||||
import { ContentFolder, FileInfo, FolderInfo } from "../models";
|
||||
@@ -12,8 +12,9 @@ import { format } from 'date-fns';
|
||||
import { Dashboard } from './Dashboard';
|
||||
import { parseWinPath } from '../helpers/parseWinPath';
|
||||
import { MediaHelpers } from '../helpers/MediaHelpers';
|
||||
import { MediaListener, PagesListener } from '../listeners';
|
||||
import { MediaListener, PagesListener } from '../listeners/dashboard';
|
||||
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
|
||||
export const WORKSPACE_PLACEHOLDER = `[[workspace]]`;
|
||||
|
||||
@@ -68,6 +69,8 @@ export class Folders {
|
||||
MediaHelpers.resetMedia();
|
||||
MediaListener.sendMediaFiles(0, folderName);
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.addMediaFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,6 +125,8 @@ export class Folders {
|
||||
await Folders.update(folders);
|
||||
|
||||
Notifications.info(`Folder registered`);
|
||||
|
||||
Telemetry.send(TelemetryEvent.registerFolder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +139,8 @@ export class Folders {
|
||||
let folders = Folders.get();
|
||||
folders = folders.filter(f => f.path !== folder.fsPath);
|
||||
await Folders.update(folders);
|
||||
|
||||
Telemetry.send(TelemetryEvent.unregisterFolder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,19 +288,6 @@ 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
|
||||
@@ -313,6 +307,19 @@ export class Folders {
|
||||
PagesListener.startWatchers();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the absolute URL for the workspace
|
||||
* @param folder
|
||||
@@ -321,7 +328,7 @@ export class Folders {
|
||||
*/
|
||||
private static absWsFolder(folder: ContentFolder, wsFolder?: Uri) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ""));
|
||||
let absPath = folder.path.replace(WORKSPACE_PLACEHOLDER, parseWinPath(wsFolder?.fsPath || ""));
|
||||
absPath = isWindows ? absPath.split('/').join('\\') : absPath;
|
||||
return absPath;
|
||||
}
|
||||
@@ -334,12 +341,8 @@ export class Folders {
|
||||
*/
|
||||
private static relWsFolder(folder: ContentFolder, wsFolder?: Uri) {
|
||||
const isWindows = process.platform === 'win32';
|
||||
let absPath = folder.path.replace(parseWinPath(wsFolder?.fsPath || ""), WORKSPACE_PLACEHOLDER);
|
||||
let absPath = parseWinPath(folder.path).replace(parseWinPath(wsFolder?.fsPath || ""), WORKSPACE_PLACEHOLDER);
|
||||
absPath = isWindows ? absPath.split('\\').join('/') : absPath;
|
||||
return absPath;
|
||||
}
|
||||
}
|
||||
|
||||
function SETTINGS_CONTENT_SUPPORTED_FILES<T>(SETTINGS_CONTENT_SUPPORTED_FILES: any) {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME, CONTEXT } from './../constants';
|
||||
import { Telemetry } from './../helpers/Telemetry';
|
||||
import { SETTING_PREVIEW_HOST, SETTING_PREVIEW_PATHNAME, CONTEXT, TelemetryEvent } from './../constants';
|
||||
import { ArticleHelper } from './../helpers/ArticleHelper';
|
||||
import { join } from "path";
|
||||
import { commands, env, Uri, ViewColumn, window } from "vscode";
|
||||
@@ -133,6 +134,8 @@ export class Preview {
|
||||
<iframe src="${urlJoin(localhostUrl.toString(), slug || '')}" >
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
Telemetry.send(TelemetryEvent.openPreview);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Telemetry } from './../helpers/Telemetry';
|
||||
import { workspace, Uri } from "vscode";
|
||||
import { join } from "path";
|
||||
import * as fs from "fs";
|
||||
@@ -5,7 +6,7 @@ import { Notifications } from "../helpers/Notifications";
|
||||
import { Template } from "./Template";
|
||||
import { Folders } from "./Folders";
|
||||
import { Settings } from "../helpers";
|
||||
import { SETTINGS_CONTENT_DEFAULT_FILETYPE } from "../constants";
|
||||
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants";
|
||||
|
||||
export class Project {
|
||||
|
||||
@@ -47,6 +48,8 @@ categories: []
|
||||
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
|
||||
Notifications.info("Project initialized successfully.");
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.initialization)
|
||||
} catch (err: any) {
|
||||
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as matter from 'gray-matter';
|
||||
import * as fs from 'fs';
|
||||
import { TaxonomyType } from "../models";
|
||||
import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, EXTENSION_NAME } from '../constants';
|
||||
import { ArticleHelper, Settings as SettingsHelper, FilesHelper } from '../helpers';
|
||||
import { TomlEngine, getFmLanguage, getFormatOpts } from '../helpers/TomlEngine';
|
||||
import { FrontMatterParser } from '../parsers';
|
||||
import { DumpOptions } from 'js-yaml';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
|
||||
@@ -90,10 +89,6 @@ export class Settings {
|
||||
const progressNr = allMdFiles.length/100;
|
||||
progress.report({ increment: 0});
|
||||
|
||||
// Get language options
|
||||
const language = getFmLanguage();
|
||||
const langOpts = getFormatOpts(language);
|
||||
|
||||
let i = 0;
|
||||
for (const file of allMdFiles) {
|
||||
progress.report({ increment: (++i/progressNr) });
|
||||
@@ -102,10 +97,7 @@ export class Settings {
|
||||
const txtData = mdFile.getText();
|
||||
if (txtData) {
|
||||
try {
|
||||
const article = matter(txtData, {
|
||||
...TomlEngine,
|
||||
...langOpts
|
||||
});
|
||||
const article = FrontMatterParser.fromFile(txtData);
|
||||
if (article && article.data) {
|
||||
const { data } = article;
|
||||
const mdTags = data["tags"];
|
||||
@@ -218,13 +210,8 @@ export class Settings {
|
||||
progress.report({ increment: (++i/progressNr) });
|
||||
const mdFile = fs.readFileSync(file.path, { encoding: "utf8" });
|
||||
if (mdFile) {
|
||||
const language = getFmLanguage();
|
||||
const langOpts = getFormatOpts(language);
|
||||
try {
|
||||
const article = matter(mdFile, {
|
||||
...TomlEngine,
|
||||
...langOpts
|
||||
});
|
||||
const article = FrontMatterParser.fromFile(mdFile);
|
||||
if (article && article.data) {
|
||||
const { data } = article;
|
||||
let taxonomies: string[] = data[matterProp];
|
||||
@@ -239,9 +226,7 @@ export class Settings {
|
||||
data[matterProp] = [...new Set(taxonomies)].sort();
|
||||
const spaces = vscode.window.activeTextEditor?.options?.tabSize;
|
||||
// Update the file
|
||||
fs.writeFileSync(file.path, matter.stringify(article.content, article.data, {
|
||||
...TomlEngine,
|
||||
...langOpts,
|
||||
fs.writeFileSync(file.path, FrontMatterParser.toFile(article.content, article.data, {
|
||||
indent: spaces || 2
|
||||
} as DumpOptions as any), { encoding: "utf8" });
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ArticleHelper, SeoHelper, Settings } from '../helpers';
|
||||
import { ExplorerView } from '../explorerView/ExplorerView';
|
||||
import { DefaultFields } from '../constants';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { DataListener } from '../listeners/panel';
|
||||
|
||||
export class StatusListener {
|
||||
|
||||
@@ -58,7 +59,7 @@ export class StatusListener {
|
||||
|
||||
const panel = ExplorerView.getInstance();
|
||||
if (panel && panel.visible) {
|
||||
panel.pushMetadata(article!.data);
|
||||
DataListener.pushMetadata(article!.data);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -68,7 +69,7 @@ export class StatusListener {
|
||||
} else {
|
||||
const panel = ExplorerView.getInstance();
|
||||
if (panel && panel.visible) {
|
||||
panel.pushMetadata(null);
|
||||
DataListener.pushMetadata(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Questions } from './../helpers/Questions';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, SETTING_TEMPLATES_PREFIX } from '../constants';
|
||||
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants';
|
||||
import { ArticleHelper, Settings } from '../helpers';
|
||||
import { Article } from '.';
|
||||
import { Notifications } from '../helpers/Notifications';
|
||||
@@ -11,8 +11,9 @@ import { Project } from './Project';
|
||||
import { Folders } from './Folders';
|
||||
import { ContentType } from '../helpers/ContentType';
|
||||
import { ContentType as IContentType } from '../models';
|
||||
import { PagesListener } from '../listeners';
|
||||
import { PagesListener } from '../listeners/dashboard';
|
||||
import { extname } from 'path';
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
|
||||
export class Template {
|
||||
|
||||
@@ -175,6 +176,8 @@ export class Template {
|
||||
|
||||
Notifications.info(`Your new content has been created.`);
|
||||
|
||||
Telemetry.send(TelemetryEvent.createContentFromTemplate);
|
||||
|
||||
// Trigger a refresh for the dashboard
|
||||
PagesListener.refresh();
|
||||
}
|
||||
|
||||
41
src/components/uniforms-frontmatter/AutoField.tsx
Normal file
41
src/components/uniforms-frontmatter/AutoField.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as invariant from 'invariant';
|
||||
import { createAutoField } from 'uniforms';
|
||||
import { PreviewImageField } from '../../panelWebView/components/Fields/PreviewImageField';
|
||||
export { AutoFieldProps } from 'uniforms';
|
||||
|
||||
import BoolField from './BoolField';
|
||||
import DateField from './DateField';
|
||||
import ListField from './ListField';
|
||||
import NestField from './NestField';
|
||||
import NumField from './NumField';
|
||||
import RadioField from './RadioField';
|
||||
import SelectField from './SelectField';
|
||||
import TextField from './TextField';
|
||||
|
||||
const AutoField = createAutoField(props => {
|
||||
|
||||
if (props.allowedValues) {
|
||||
return props.checkboxes && props.fieldType !== Array
|
||||
? RadioField
|
||||
: SelectField;
|
||||
}
|
||||
|
||||
switch (props.fieldType) {
|
||||
case Array:
|
||||
return ListField;
|
||||
case Boolean:
|
||||
return BoolField;
|
||||
case Date:
|
||||
return DateField;
|
||||
case Number:
|
||||
return NumField;
|
||||
case Object:
|
||||
return NestField;
|
||||
case String:
|
||||
return TextField;
|
||||
}
|
||||
|
||||
return invariant(false, 'Unsupported field type: %s', props.fieldType);
|
||||
});
|
||||
|
||||
export default AutoField;
|
||||
29
src/components/uniforms-frontmatter/AutoFields.tsx
Normal file
29
src/components/uniforms-frontmatter/AutoFields.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ComponentType, createElement, Fragment } from 'react';
|
||||
import { useForm } from 'uniforms';
|
||||
|
||||
import AutoField from './AutoField';
|
||||
|
||||
export type AutoFieldsProps = {
|
||||
autoField?: ComponentType<{ name: string }>;
|
||||
element?: ComponentType | string;
|
||||
fields?: string[];
|
||||
omitFields?: string[];
|
||||
};
|
||||
|
||||
export default function AutoFields({
|
||||
autoField = AutoField,
|
||||
element = Fragment,
|
||||
fields,
|
||||
omitFields = [],
|
||||
...props
|
||||
}: AutoFieldsProps) {
|
||||
const { schema } = useForm();
|
||||
|
||||
return createElement(
|
||||
element,
|
||||
props,
|
||||
(fields ?? schema.getSubfields())
|
||||
.filter(field => !omitFields.includes(field))
|
||||
.map(field => createElement(autoField, { key: field, name: field })),
|
||||
);
|
||||
}
|
||||
13
src/components/uniforms-frontmatter/AutoForm.tsx
Normal file
13
src/components/uniforms-frontmatter/AutoForm.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { AutoForm } from 'uniforms';
|
||||
|
||||
import ValidatedQuickForm from './ValidatedQuickForm';
|
||||
|
||||
function Auto(parent: any) {
|
||||
class _ extends AutoForm.Auto(parent) {
|
||||
static Auto = Auto;
|
||||
}
|
||||
|
||||
return _ as unknown as AutoForm;
|
||||
}
|
||||
|
||||
export default Auto(ValidatedQuickForm);
|
||||
13
src/components/uniforms-frontmatter/BaseForm.tsx
Normal file
13
src/components/uniforms-frontmatter/BaseForm.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { BaseForm } from 'uniforms';
|
||||
|
||||
function Unstyled(parent: any) {
|
||||
class _ extends parent {
|
||||
static Unstyled = Unstyled;
|
||||
|
||||
static displayName = `Unstyled${parent.displayName}`;
|
||||
}
|
||||
|
||||
return _ as unknown as typeof BaseForm;
|
||||
}
|
||||
|
||||
export default Unstyled(BaseForm);
|
||||
52
src/components/uniforms-frontmatter/BoolField.css
Normal file
52
src/components/uniforms-frontmatter/BoolField.css
Normal file
@@ -0,0 +1,52 @@
|
||||
.field__toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.field__toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.field__toggle__slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--frontmatter-toggle-secondaryBackground, var(--vscode-button-secondaryBackground));
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.field__toggle__slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .field__toggle__slider {
|
||||
background-color: var(--frontmatter-toggle-background, var(--vscode-button-background));
|
||||
}
|
||||
|
||||
input:focus + .field__toggle__slider {
|
||||
box-shadow: 0 0 1px var(--frontmatter-toggle-background, var(--vscode-button-background));
|
||||
}
|
||||
|
||||
input:checked + .field__toggle__slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
44
src/components/uniforms-frontmatter/BoolField.tsx
Normal file
44
src/components/uniforms-frontmatter/BoolField.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import * as React from 'react';
|
||||
import { Ref } from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
import './BoolField.css';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
export type BoolFieldProps = HTMLFieldProps<
|
||||
boolean,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement> }
|
||||
>;
|
||||
|
||||
function Bool({
|
||||
disabled,
|
||||
id,
|
||||
inputRef,
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
readOnly,
|
||||
value,
|
||||
...props
|
||||
}: BoolFieldProps) {
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<label className="field__toggle">
|
||||
<input
|
||||
checked={value || false}
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
name={name}
|
||||
onChange={() => !disabled && !readOnly && onChange(!value)}
|
||||
ref={inputRef}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span className="field__toggle__slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<BoolFieldProps>(Bool, { kind: 'leaf' });
|
||||
57
src/components/uniforms-frontmatter/DateField.tsx
Normal file
57
src/components/uniforms-frontmatter/DateField.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import * as React from 'react';
|
||||
import { Ref } from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
|
||||
/* istanbul ignore next */
|
||||
const DateConstructor = (typeof global === 'object' ? global : window).Date;
|
||||
const dateFormat = (value?: Date) => value?.toISOString().slice(0, -8);
|
||||
|
||||
export type DateFieldProps = HTMLFieldProps<
|
||||
Date,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement>; max?: Date; min?: Date }
|
||||
>;
|
||||
|
||||
function Date({
|
||||
disabled,
|
||||
id,
|
||||
inputRef,
|
||||
label,
|
||||
max,
|
||||
min,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
readOnly,
|
||||
value,
|
||||
...props
|
||||
}: DateFieldProps) {
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
{label && <label htmlFor={id}>{label}</label>}
|
||||
|
||||
<input
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
max={dateFormat(max)}
|
||||
min={dateFormat(min)}
|
||||
name={name}
|
||||
onChange={event => {
|
||||
const date = new DateConstructor(event.target.valueAsNumber);
|
||||
if (date.getFullYear() < 10000) {
|
||||
onChange(date);
|
||||
} else if (isNaN(event.target.valueAsNumber)) {
|
||||
onChange(undefined);
|
||||
}
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
ref={inputRef}
|
||||
type="datetime-local"
|
||||
value={dateFormat(value) ?? ''}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<DateFieldProps>(Date, { kind: 'leaf' });
|
||||
19
src/components/uniforms-frontmatter/ErrorField.tsx
Normal file
19
src/components/uniforms-frontmatter/ErrorField.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import { HTMLProps } from 'react';
|
||||
import { Override, connectField, filterDOMProps } from 'uniforms';
|
||||
|
||||
export type ErrorFieldProps = Override<
|
||||
Omit<HTMLProps<HTMLDivElement>, 'onChange'>,
|
||||
{ error?: any; errorMessage?: string }
|
||||
>;
|
||||
|
||||
function Error({ children, error, errorMessage, ...props }: ErrorFieldProps) {
|
||||
return !error ? null : (
|
||||
<div {...filterDOMProps(props)}>{children || errorMessage}</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<ErrorFieldProps>(Error, {
|
||||
initialValue: false,
|
||||
kind: 'leaf',
|
||||
});
|
||||
18
src/components/uniforms-frontmatter/ErrorsField.css
Normal file
18
src/components/uniforms-frontmatter/ErrorsField.css
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
.autoform-error {
|
||||
background-color: var(--frontmatter-error-background, var(--vscode-inputValidation-errorBackground));
|
||||
border: 1px solid var(--frontmatter-error-border, var(--vscode-inputValidation-errorBorder));
|
||||
border-radius: 2px;
|
||||
margin: 20px 0px;
|
||||
padding: 10px;
|
||||
color: var(--frontmatter-error-foreground, var(--vscode-editor-foreground));
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
23
src/components/uniforms-frontmatter/ErrorsField.tsx
Normal file
23
src/components/uniforms-frontmatter/ErrorsField.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { HTMLProps } from 'react';
|
||||
import { filterDOMProps, useForm } from 'uniforms';
|
||||
import './ErrorsField.css';
|
||||
|
||||
export type ErrorsFieldProps = HTMLProps<HTMLDivElement>;
|
||||
|
||||
export default function ErrorsField(props: ErrorsFieldProps) {
|
||||
const { error, schema } = useForm();
|
||||
return !error && !props.children ? null : (
|
||||
<div className='autoform-error'>
|
||||
<div {...filterDOMProps(props)}>
|
||||
{props.children}
|
||||
|
||||
<ul>
|
||||
{schema.getErrorMessages(error).map((message, index) => (
|
||||
<li key={index}>{message}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
src/components/uniforms-frontmatter/HiddenField.tsx
Normal file
35
src/components/uniforms-frontmatter/HiddenField.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import { HTMLProps, Ref, useEffect } from 'react';
|
||||
import { Override, filterDOMProps, useField } from 'uniforms';
|
||||
|
||||
export type HiddenFieldProps = Override<
|
||||
HTMLProps<HTMLInputElement>,
|
||||
{
|
||||
inputRef?: Ref<HTMLInputElement>;
|
||||
name: string;
|
||||
noDOM?: boolean;
|
||||
value?: any;
|
||||
}
|
||||
>;
|
||||
|
||||
export default function HiddenField({ value, ...rawProps }: HiddenFieldProps) {
|
||||
const props = useField(rawProps.name, rawProps, { initialValue: false })[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== undefined && value !== props.value) {
|
||||
props.onChange(value);
|
||||
}
|
||||
});
|
||||
|
||||
return props.noDOM ? null : (
|
||||
<input
|
||||
disabled={props.disabled}
|
||||
name={props.name}
|
||||
readOnly={props.readOnly}
|
||||
ref={props.inputRef}
|
||||
type="hidden"
|
||||
value={value ?? props.value ?? ''}
|
||||
{...filterDOMProps(props)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
14
src/components/uniforms-frontmatter/LabelField.css
Normal file
14
src/components/uniforms-frontmatter/LabelField.css
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
.autoform__label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
line-height: 16px;
|
||||
font-weight: bold;
|
||||
|
||||
.autoform__label__required {
|
||||
color: var(--vscode-inputValidation-errorBorder);
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
20
src/components/uniforms-frontmatter/LabelField.tsx
Normal file
20
src/components/uniforms-frontmatter/LabelField.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import './LabelField.css';
|
||||
|
||||
export interface ILabelFieldProps {
|
||||
id: string;
|
||||
label: string | ReactNode;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export const LabelField: React.FunctionComponent<ILabelFieldProps> = ({ label, id, required }: React.PropsWithChildren<ILabelFieldProps>) => {
|
||||
return (
|
||||
label ? (
|
||||
<label className="autoform__label" htmlFor={id}>
|
||||
{label}
|
||||
{required && <span title='Required field' className='autoform__label__required'>*</span>}
|
||||
</label>
|
||||
) : null
|
||||
);
|
||||
};
|
||||
21
src/components/uniforms-frontmatter/ListAddField.css
Normal file
21
src/components/uniforms-frontmatter/ListAddField.css
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
.autoform__list_add_field {
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
border: 1px dashed var(--frontmatter-field-border, var(--vscode-editor-foreground));
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-top: .5rem;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--frontmatter-field-borderActive, var(--vscode-button-background));
|
||||
color: var(--frontmatter-field-borderActive, var(--vscode-button-background));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
||||
63
src/components/uniforms-frontmatter/ListAddField.tsx
Normal file
63
src/components/uniforms-frontmatter/ListAddField.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { PlusIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
HTMLFieldProps,
|
||||
connectField,
|
||||
filterDOMProps,
|
||||
joinName,
|
||||
useField,
|
||||
} from 'uniforms';
|
||||
import './ListAddField.css';
|
||||
|
||||
export type ListAddFieldProps = HTMLFieldProps<
|
||||
unknown,
|
||||
HTMLSpanElement,
|
||||
{ initialCount?: number }
|
||||
>;
|
||||
|
||||
function ListAdd({
|
||||
disabled,
|
||||
initialCount,
|
||||
name,
|
||||
readOnly,
|
||||
value,
|
||||
...props
|
||||
}: ListAddFieldProps) {
|
||||
const nameParts = joinName(null, name);
|
||||
const parentName = joinName(nameParts.slice(0, -1));
|
||||
const parent = useField<
|
||||
{ initialCount?: number; maxCount?: number },
|
||||
unknown[]
|
||||
>(parentName, { initialCount }, { absoluteName: true })[0];
|
||||
|
||||
const limitNotReached =
|
||||
!disabled && !(parent.maxCount! <= parent.value!.length);
|
||||
|
||||
function onAction(event: React.KeyboardEvent | React.MouseEvent) {
|
||||
if (
|
||||
limitNotReached &&
|
||||
!readOnly &&
|
||||
(!('key' in event) || event.key === 'Enter')
|
||||
) {
|
||||
parent.onChange(parent.value!.concat([Object.assign({}, value)]));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className='autoform__list_add_field'
|
||||
{...filterDOMProps(props as any)}
|
||||
onClick={onAction}
|
||||
onKeyDown={onAction}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<PlusIcon />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<ListAddFieldProps>(ListAdd, {
|
||||
initialValue: false,
|
||||
kind: 'leaf',
|
||||
});
|
||||
27
src/components/uniforms-frontmatter/ListDelField.css
Normal file
27
src/components/uniforms-frontmatter/ListDelField.css
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
|
||||
.autoform__list_del_field {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-top: .5rem;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--vscode-button-background);
|
||||
color: var(--vscode-button-background);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 1px;
|
||||
background: var(--frontmatter-list-border, var(--vscode-editor-foreground));
|
||||
width: 100%;
|
||||
margin-right: .5rem;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
}
|
||||
63
src/components/uniforms-frontmatter/ListDelField.tsx
Normal file
63
src/components/uniforms-frontmatter/ListDelField.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { TrashIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
HTMLFieldProps,
|
||||
connectField,
|
||||
filterDOMProps,
|
||||
joinName,
|
||||
useField,
|
||||
} from 'uniforms';
|
||||
import './ListDelField.css';
|
||||
|
||||
export type ListDelFieldProps = HTMLFieldProps<unknown, HTMLSpanElement>;
|
||||
|
||||
function ListDel({ disabled, name, readOnly, ...props }: ListDelFieldProps) {
|
||||
const nameParts = joinName(null, name);
|
||||
const nameIndex = +nameParts[nameParts.length - 1];
|
||||
const parentName = joinName(nameParts.slice(0, -1));
|
||||
const parent = useField<{ minCount?: number }, unknown[]>(
|
||||
parentName,
|
||||
{},
|
||||
{ absoluteName: true },
|
||||
)[0];
|
||||
|
||||
const limitNotReached =
|
||||
!disabled && !(parent.minCount! >= parent.value!.length);
|
||||
|
||||
function onAction(
|
||||
event:
|
||||
| React.KeyboardEvent<HTMLSpanElement>
|
||||
| React.MouseEvent<HTMLSpanElement, MouseEvent>,
|
||||
) {
|
||||
if (
|
||||
limitNotReached &&
|
||||
!readOnly &&
|
||||
(!('key' in event) || event.key === 'Enter')
|
||||
) {
|
||||
const value = parent.value!.slice();
|
||||
value.splice(nameIndex, 1);
|
||||
parent.onChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className='autoform__list_del_field'
|
||||
{...filterDOMProps(props)}
|
||||
onClick={onAction}
|
||||
onKeyDown={onAction}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='line'></div>
|
||||
<TrashIcon />
|
||||
</span>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<ListDelFieldProps>(ListDel, {
|
||||
initialValue: false,
|
||||
kind: 'leaf',
|
||||
});
|
||||
8
src/components/uniforms-frontmatter/ListField.css
Normal file
8
src/components/uniforms-frontmatter/ListField.css
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
.autoform__list_field {
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 1rem;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--frontmatter-list-border, rgba(255, 255, 255, 0.2));
|
||||
}
|
||||
46
src/components/uniforms-frontmatter/ListField.tsx
Normal file
46
src/components/uniforms-frontmatter/ListField.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as React from 'react';
|
||||
import { Children, cloneElement, isValidElement } from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
|
||||
import ListAddField from './ListAddField';
|
||||
import ListItemField from './ListItemField';
|
||||
|
||||
import './ListField.css';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
export type ListFieldProps = HTMLFieldProps<
|
||||
unknown[],
|
||||
HTMLDivElement,
|
||||
{ initialCount?: number; itemProps?: object }
|
||||
>;
|
||||
|
||||
function List({
|
||||
children = <ListItemField name="$" />,
|
||||
initialCount,
|
||||
itemProps,
|
||||
label,
|
||||
value,
|
||||
...props
|
||||
}: ListFieldProps) {
|
||||
return (
|
||||
<div className="autoform__list_field" {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={props.id} required={props.required} />
|
||||
|
||||
{value?.map((item, itemIndex) =>
|
||||
Children.map(children, (child, childIndex) =>
|
||||
isValidElement(child)
|
||||
? cloneElement(child, {
|
||||
key: `${itemIndex}-${childIndex}`,
|
||||
name: (child.props.name || "").replace('$', '' + itemIndex),
|
||||
...itemProps,
|
||||
})
|
||||
: child,
|
||||
),
|
||||
)}
|
||||
|
||||
<ListAddField initialCount={initialCount} name="$" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<ListFieldProps>(List);
|
||||
23
src/components/uniforms-frontmatter/ListItemField.tsx
Normal file
23
src/components/uniforms-frontmatter/ListItemField.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { connectField } from 'uniforms';
|
||||
|
||||
import AutoField from './AutoField';
|
||||
import ListDelField from './ListDelField';
|
||||
|
||||
export type ListItemFieldProps = { children?: ReactNode; value?: unknown };
|
||||
|
||||
function ListItem({
|
||||
children = <AutoField label={null} name="" />,
|
||||
}: ListItemFieldProps) {
|
||||
return (
|
||||
<div>
|
||||
<ListDelField name="" />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<ListItemFieldProps>(ListItem, {
|
||||
initialValue: false,
|
||||
});
|
||||
41
src/components/uniforms-frontmatter/LongTextField.tsx
Normal file
41
src/components/uniforms-frontmatter/LongTextField.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as React from 'react';
|
||||
import { Ref } from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
|
||||
export type LongTextFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLTextAreaElement> }
|
||||
>;
|
||||
|
||||
function LongText({
|
||||
disabled,
|
||||
id,
|
||||
inputRef,
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
readOnly,
|
||||
value,
|
||||
...props
|
||||
}: LongTextFieldProps) {
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
{label && <label htmlFor={id}>{label}</label>}
|
||||
|
||||
<textarea
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
name={name}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
ref={inputRef}
|
||||
value={value ?? ''}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<LongTextFieldProps>(LongText, { kind: 'leaf' });
|
||||
32
src/components/uniforms-frontmatter/NestField.tsx
Normal file
32
src/components/uniforms-frontmatter/NestField.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
|
||||
import AutoField from './AutoField';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
export type NestFieldProps = HTMLFieldProps<
|
||||
object,
|
||||
HTMLDivElement,
|
||||
{ itemProps?: object }
|
||||
>;
|
||||
|
||||
function Nest({
|
||||
children,
|
||||
fields,
|
||||
itemProps,
|
||||
label,
|
||||
...props
|
||||
}: NestFieldProps) {
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={props.id} required={props.required} />
|
||||
|
||||
{children ||
|
||||
fields.map(field => (
|
||||
<AutoField key={field} name={field} {...itemProps} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<NestFieldProps>(Nest);
|
||||
54
src/components/uniforms-frontmatter/NumField.tsx
Normal file
54
src/components/uniforms-frontmatter/NumField.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as React from 'react';
|
||||
import { Ref } from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
export type NumFieldProps = HTMLFieldProps<
|
||||
number,
|
||||
HTMLDivElement,
|
||||
{ decimal?: boolean; inputRef?: Ref<HTMLInputElement> }
|
||||
>;
|
||||
|
||||
function Num({
|
||||
decimal,
|
||||
disabled,
|
||||
id,
|
||||
inputRef,
|
||||
label,
|
||||
max,
|
||||
min,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
readOnly,
|
||||
step,
|
||||
value,
|
||||
...props
|
||||
}: NumFieldProps) {
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<input
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
max={max}
|
||||
min={min}
|
||||
name={name}
|
||||
onChange={event => {
|
||||
const parse = decimal ? parseFloat : parseInt;
|
||||
const value = parse(event.target.value);
|
||||
onChange(isNaN(value) ? undefined : value);
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
ref={inputRef}
|
||||
step={step || (decimal ? 0.01 : 1)}
|
||||
type="number"
|
||||
value={value ?? ''}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<NumFieldProps>(Num, { kind: 'leaf' });
|
||||
28
src/components/uniforms-frontmatter/QuickForm.tsx
Normal file
28
src/components/uniforms-frontmatter/QuickForm.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { QuickForm } from 'uniforms';
|
||||
|
||||
import AutoField from './AutoField';
|
||||
import BaseForm from './BaseForm';
|
||||
import ErrorsField from './ErrorsField';
|
||||
import SubmitField from './SubmitField';
|
||||
|
||||
function Quick(parent: any) {
|
||||
class _ extends QuickForm.Quick(parent) {
|
||||
static Quick = Quick;
|
||||
|
||||
getAutoField() {
|
||||
return AutoField;
|
||||
}
|
||||
|
||||
getErrorsField() {
|
||||
return ErrorsField;
|
||||
}
|
||||
|
||||
getSubmitField() {
|
||||
return SubmitField;
|
||||
}
|
||||
}
|
||||
|
||||
return _ as unknown as QuickForm;
|
||||
}
|
||||
|
||||
export default Quick(BaseForm);
|
||||
62
src/components/uniforms-frontmatter/RadioField.tsx
Normal file
62
src/components/uniforms-frontmatter/RadioField.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import omit = require('lodash.omit');
|
||||
import * as React from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
const base64: typeof btoa =
|
||||
typeof btoa === 'undefined'
|
||||
? /* istanbul ignore next */ x => Buffer.from(x).toString('base64')
|
||||
: btoa;
|
||||
const escape = (x: string) => base64(encodeURIComponent(x)).replace(/=+$/, '');
|
||||
|
||||
export type RadioFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
HTMLDivElement,
|
||||
{
|
||||
allowedValues?: string[];
|
||||
checkboxes?: boolean;
|
||||
transform?: (value: string) => string;
|
||||
}
|
||||
>;
|
||||
|
||||
function Radio({
|
||||
allowedValues,
|
||||
disabled,
|
||||
id,
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
readOnly,
|
||||
transform,
|
||||
value,
|
||||
...props
|
||||
}: RadioFieldProps) {
|
||||
return (
|
||||
<div {...omit(filterDOMProps(props), ['checkboxes'])}>
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
{allowedValues?.map(item => (
|
||||
<div key={item}>
|
||||
<input
|
||||
checked={item === value}
|
||||
disabled={disabled}
|
||||
id={`${id}-${escape(item)}`}
|
||||
name={name}
|
||||
onChange={() => {
|
||||
if (!readOnly) {
|
||||
onChange(item);
|
||||
}
|
||||
}}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
<label htmlFor={`${id}-${escape(item)}`}>
|
||||
{transform ? transform(item) : item}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<RadioFieldProps>(Radio, { kind: 'leaf' });
|
||||
5
src/components/uniforms-frontmatter/SelectField.css
Normal file
5
src/components/uniforms-frontmatter/SelectField.css
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
.autoform__select_field {
|
||||
color: var(--frontmatter-select-foreground, var(--vscode-editor-foreground));
|
||||
}
|
||||
110
src/components/uniforms-frontmatter/SelectField.tsx
Normal file
110
src/components/uniforms-frontmatter/SelectField.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import xor = require('lodash.xor');
|
||||
import * as React from 'react';
|
||||
import { Ref } from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
import { LabelField } from './LabelField';
|
||||
import './SelectField.css';
|
||||
|
||||
const base64: typeof btoa =
|
||||
typeof btoa === 'undefined'
|
||||
? /* istanbul ignore next */ x => Buffer.from(x).toString('base64')
|
||||
: btoa;
|
||||
const escape = (x: string) => base64(encodeURIComponent(x)).replace(/=+$/, '');
|
||||
|
||||
export type SelectFieldProps = HTMLFieldProps<
|
||||
string | string[],
|
||||
HTMLDivElement,
|
||||
{
|
||||
allowedValues?: string[];
|
||||
checkboxes?: boolean;
|
||||
disableItem?: (value: string) => boolean;
|
||||
inputRef?: Ref<HTMLSelectElement>;
|
||||
transform?: (value: string) => string;
|
||||
}
|
||||
>;
|
||||
|
||||
function Select({
|
||||
allowedValues,
|
||||
checkboxes,
|
||||
disabled,
|
||||
fieldType,
|
||||
id,
|
||||
inputRef,
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
readOnly,
|
||||
required,
|
||||
disableItem,
|
||||
transform,
|
||||
value,
|
||||
...props
|
||||
}: SelectFieldProps) {
|
||||
const multiple = fieldType === Array;
|
||||
return (
|
||||
<div className='autoform__select_field' {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={id} required={required} />
|
||||
|
||||
{checkboxes ? (
|
||||
allowedValues!.map(item => (
|
||||
<div key={item}>
|
||||
<input
|
||||
checked={
|
||||
fieldType === Array ? value!.includes(item) : value === item
|
||||
}
|
||||
disabled={disableItem?.(item) ?? disabled}
|
||||
id={`${id}-${escape(item)}`}
|
||||
name={name}
|
||||
onChange={() => {
|
||||
if (!readOnly) {
|
||||
onChange(fieldType === Array ? xor([item], value) : item);
|
||||
}
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
<label htmlFor={`${id}-${escape(item)}`}>
|
||||
{transform ? transform(item) : item}
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<select
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
multiple={multiple}
|
||||
name={name}
|
||||
onChange={event => {
|
||||
if (!readOnly) {
|
||||
const item = event.target.value;
|
||||
if (multiple) {
|
||||
const clear = event.target.selectedIndex === -1;
|
||||
onChange(clear ? [] : xor([item], value));
|
||||
} else {
|
||||
onChange(item !== '' ? item : undefined);
|
||||
}
|
||||
}
|
||||
}}
|
||||
ref={inputRef}
|
||||
value={value ?? ''}
|
||||
style={{ width: "100%", padding: "0.5rem" }}
|
||||
>
|
||||
{(!!placeholder || !required || value === undefined) && !multiple && (
|
||||
<option value="" disabled={required} hidden={required}>
|
||||
{placeholder || label}
|
||||
</option>
|
||||
)}
|
||||
|
||||
{allowedValues?.map(value => (
|
||||
<option disabled={disableItem?.(value)} key={value} value={value}>
|
||||
{transform ? transform(value) : value}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectField<SelectFieldProps>(Select, { kind: 'leaf' });
|
||||
29
src/components/uniforms-frontmatter/SubmitField.tsx
Normal file
29
src/components/uniforms-frontmatter/SubmitField.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { HTMLProps, Ref } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Override, filterDOMProps, useForm } from 'uniforms';
|
||||
|
||||
export type SubmitFieldProps = Override<
|
||||
HTMLProps<HTMLInputElement>,
|
||||
{ inputRef?: Ref<HTMLInputElement>; value?: string }
|
||||
>;
|
||||
|
||||
export default function SubmitField({
|
||||
disabled,
|
||||
inputRef,
|
||||
readOnly,
|
||||
value,
|
||||
...props
|
||||
}: SubmitFieldProps) {
|
||||
const { error, state } = useForm();
|
||||
|
||||
return (
|
||||
<input
|
||||
disabled={disabled === undefined ? !!(error || state.disabled) : disabled}
|
||||
readOnly={readOnly}
|
||||
ref={inputRef}
|
||||
type="submit"
|
||||
{...(value ? { value } : {})}
|
||||
{...filterDOMProps(props)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
49
src/components/uniforms-frontmatter/TextField.tsx
Normal file
49
src/components/uniforms-frontmatter/TextField.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Ref } from 'react';
|
||||
import * as React from 'react';
|
||||
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
|
||||
import { LabelField } from './LabelField';
|
||||
|
||||
export type TextFieldProps = HTMLFieldProps<
|
||||
string,
|
||||
HTMLDivElement,
|
||||
{ inputRef?: Ref<HTMLInputElement> }
|
||||
>;
|
||||
|
||||
function Text({
|
||||
autoComplete,
|
||||
disabled,
|
||||
id,
|
||||
inputRef,
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
placeholder,
|
||||
readOnly,
|
||||
type,
|
||||
value,
|
||||
...props
|
||||
}: TextFieldProps) {
|
||||
|
||||
return (
|
||||
<div {...filterDOMProps(props)}>
|
||||
<LabelField label={label} id={id} required={props.required} />
|
||||
|
||||
<input
|
||||
autoComplete={autoComplete}
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
name={name}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
ref={inputRef}
|
||||
type={type}
|
||||
value={value ?? ''}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Text.defaultProps = { type: 'text' };
|
||||
|
||||
export default connectField<TextFieldProps>(Text, { kind: 'leaf' });
|
||||
13
src/components/uniforms-frontmatter/ValidatedForm.tsx
Normal file
13
src/components/uniforms-frontmatter/ValidatedForm.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ValidatedForm } from 'uniforms';
|
||||
|
||||
import BaseForm from './BaseForm';
|
||||
|
||||
function Validated(parent: any) {
|
||||
class _ extends ValidatedForm.Validated(parent) {
|
||||
static Validated = Validated;
|
||||
}
|
||||
|
||||
return _ as unknown as ValidatedForm;
|
||||
}
|
||||
|
||||
export default Validated(BaseForm);
|
||||
@@ -0,0 +1,5 @@
|
||||
import BaseForm from './BaseForm';
|
||||
import QuickForm from './QuickForm';
|
||||
import ValidatedForm from './ValidatedForm';
|
||||
|
||||
export default ValidatedForm.Validated(QuickForm.Quick(BaseForm));
|
||||
23
src/components/uniforms-frontmatter/index.ts
Normal file
23
src/components/uniforms-frontmatter/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export { default as AutoField, AutoFieldProps } from './AutoField';
|
||||
export { default as AutoFields, AutoFieldsProps } from './AutoFields';
|
||||
export { default as AutoForm } from './AutoForm';
|
||||
export { default as BaseForm } from './BaseForm';
|
||||
export { default as BoolField, BoolFieldProps } from './BoolField';
|
||||
export { default as DateField, DateFieldProps } from './DateField';
|
||||
export { default as ErrorField, ErrorFieldProps } from './ErrorField';
|
||||
export { default as ErrorsField, ErrorsFieldProps } from './ErrorsField';
|
||||
export { default as HiddenField, HiddenFieldProps } from './HiddenField';
|
||||
export { default as ListAddField, ListAddFieldProps } from './ListAddField';
|
||||
export { default as ListDelField, ListDelFieldProps } from './ListDelField';
|
||||
export { default as ListField, ListFieldProps } from './ListField';
|
||||
export { default as ListItemField, ListItemFieldProps } from './ListItemField';
|
||||
export { default as LongTextField, LongTextFieldProps } from './LongTextField';
|
||||
export { default as NestField, NestFieldProps } from './NestField';
|
||||
export { default as NumField, NumFieldProps } from './NumField';
|
||||
export { default as QuickForm } from './QuickForm';
|
||||
export { default as RadioField, RadioFieldProps } from './RadioField';
|
||||
export { default as SelectField, SelectFieldProps } from './SelectField';
|
||||
export { default as SubmitField, SubmitFieldProps } from './SubmitField';
|
||||
export { default as TextField, TextFieldProps } from './TextField';
|
||||
export { default as ValidatedForm } from './ValidatedForm';
|
||||
export { default as ValidatedQuickForm } from './ValidatedQuickForm';
|
||||
28
src/constants/TelemetryEvent.ts
Normal file
28
src/constants/TelemetryEvent.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export const TelemetryEvent = {
|
||||
activate: 'activate',
|
||||
initialization: 'initialization',
|
||||
openContentDashboard: 'openContentDashboard',
|
||||
openMediaDashboard: 'openMediaDashboard',
|
||||
openDataDashboard: 'openDataDashboard',
|
||||
closeDashboard: 'closeDashboard',
|
||||
generateSlug: 'generateSlug',
|
||||
createContentFromTemplate: 'createContentFromTemplate',
|
||||
createContentFromContentType: 'createContentFromContentType',
|
||||
registerFolder: 'registerFolder',
|
||||
unregisterFolder: 'unregisterFolder',
|
||||
addMediaFolder: 'addMediaFolder',
|
||||
promoteSettings: 'promoteSettings',
|
||||
openPreview: 'openPreview',
|
||||
uploadMedia: 'uploadMedia',
|
||||
refreshMedia: 'refreshMedia',
|
||||
deleteMedia: 'deleteMedia',
|
||||
insertMediaToContent: 'insertMediaToContent',
|
||||
updateMediaMetadata: 'updateMediaMetadata',
|
||||
openExplorerView: 'openExplorerView',
|
||||
|
||||
// Webviews
|
||||
webviewWelcomeScreen: 'webviewWelcomeScreen',
|
||||
webviewMediaView: 'webviewMediaView',
|
||||
webviewDataView: 'webviewDataView',
|
||||
webviewContentsView: 'webviewContentsView',
|
||||
};
|
||||
@@ -1,10 +1,13 @@
|
||||
export * from './ContentType';
|
||||
export * from './DefaultFields';
|
||||
export * from './DefaultFileTypes';
|
||||
export * from './Extension';
|
||||
export * from './ExtensionState';
|
||||
export * from './FrameworkDetectors';
|
||||
export * from './Links';
|
||||
export * from './LocalStore';
|
||||
export * from './Navigation';
|
||||
export * from './TelemetryEvent';
|
||||
export * from './charMap';
|
||||
export * from './context';
|
||||
export * from './settings';
|
||||
|
||||
@@ -7,6 +7,7 @@ export const SETTING_GLOBAL_NOTIFICATIONS = "global.notifications";
|
||||
export const SETTING_TAXONOMY_TAGS = "taxonomy.tags";
|
||||
export const SETTING_TAXONOMY_CATEGORIES = "taxonomy.categories";
|
||||
export const SETTING_TAXONOMY_CUSTOM = "taxonomy.customTaxonomy";
|
||||
export const SETTING_TAXONOMY_FIELD_GROUPS = "taxonomy.fieldGroups";
|
||||
|
||||
export const SETTING_DATE_FORMAT = "taxonomy.dateFormat";
|
||||
export const SETTING_COMMA_SEPARATED_FIELDS = "taxonomy.commaSeparatedFields";
|
||||
@@ -32,6 +33,8 @@ export const SETTING_SEO_DESCRIPTION_FIELD = "taxonomy.seoDescriptionField";
|
||||
export const SETTING_TEMPLATES_FOLDER = "templates.folder";
|
||||
export const SETTING_TEMPLATES_PREFIX = "templates.prefix";
|
||||
|
||||
export const SETTING_TELEMETRY_DISABLE = "telemetry.disable";
|
||||
|
||||
export const SETTING_PANEL_FREEFORM = "panel.freeform";
|
||||
|
||||
export const SETTING_PREVIEW_HOST = "preview.host";
|
||||
@@ -61,6 +64,8 @@ export const SETTINGS_DATA_FILES = "data.files";
|
||||
export const SETTINGS_DATA_FOLDERS = "data.folders";
|
||||
export const SETTINGS_DATA_TYPES = "data.types";
|
||||
|
||||
export const SETTINGS_FILE_PRESERVE_CASING = "file.preserveCasing";
|
||||
|
||||
export const SETTINGS_FRAMEWORK_ID = "framework.id";
|
||||
export const SETTINGS_FRAMEWORK_START = "framework.startCommand";
|
||||
|
||||
|
||||
@@ -24,4 +24,5 @@ export enum DashboardMessage {
|
||||
runCustomScript = 'runCustomScript',
|
||||
getDataEntries = 'getDataEntries',
|
||||
putDataEntries = 'putDataEntries',
|
||||
sendTelemetry = 'sendTelemetry',
|
||||
}
|
||||
@@ -7,6 +7,10 @@ import { Overview } from './Overview';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { SponsorMsg } from '../SponsorMsg';
|
||||
import usePages from '../../hooks/usePages';
|
||||
import { useEffect } from 'react';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
|
||||
export interface IContentsProps {
|
||||
pages: Page[];
|
||||
@@ -19,6 +23,12 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loadin
|
||||
|
||||
const pageFolders = [...new Set(pageItems.map(page => page.fmFolder))];
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewContentsView
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-auto">
|
||||
<Header
|
||||
|
||||
@@ -3,21 +3,18 @@ import { useRecoilValue } from 'recoil';
|
||||
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { Page } from '../../models/Page';
|
||||
import { SettingsSelector, ViewSelector } from '../../state';
|
||||
import { ViewSelector } from '../../state';
|
||||
import { DateField } from '../DateField';
|
||||
import { Status } from '../Status';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import useContentType from '../../../hooks/useContentType';
|
||||
import { DashboardViewType } from '../../models';
|
||||
|
||||
export interface IItemProps extends Page {}
|
||||
|
||||
const PREVIEW_IMAGE_FIELD = 'fmPreviewImage';
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, draft, description, type, ...pageData }: React.PropsWithChildren<IItemProps>) => {
|
||||
const view = useRecoilValue(ViewSelector);
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const contentType = useContentType(settings, { type });
|
||||
|
||||
const previewField = contentType.fields.find(field => field.isPreviewImage && field.type === "image")?.name || "preview";
|
||||
|
||||
const openFile = () => {
|
||||
Messenger.send(DashboardMessage.openFile, fmFilePath);
|
||||
@@ -30,8 +27,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
|
||||
onClick={openFile}>
|
||||
<div className="relative h-36 w-full overflow-hidden border-b border-gray-100 dark:border-vulcan-100 dark:group-hover:border-vulcan-200">
|
||||
{
|
||||
previewField && pageData[previewField] ? (
|
||||
<img src={`${pageData[previewField]}`} alt={title} className="absolute inset-0 h-full w-full object-cover" loading="lazy" />
|
||||
pageData[PREVIEW_IMAGE_FIELD] ? (
|
||||
<img src={`${pageData[PREVIEW_IMAGE_FIELD]}`} alt={title} className="absolute inset-0 h-full w-full object-cover" loading="lazy" />
|
||||
) : (
|
||||
<div className={`flex items-center justify-center bg-whisper-500 dark:bg-vulcan-200 dark:group-hover:bg-vulcan-100`}>
|
||||
<MarkdownIcon className={`h-32 text-vulcan-100 dark:text-whisper-100`} />
|
||||
|
||||
@@ -2,7 +2,8 @@ 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 } from 'uniforms-antd';
|
||||
import { AutoFields, AutoForm, ErrorsField } from '../../../components/uniforms-frontmatter';
|
||||
// import { AutoFields, AutoForm, ErrorsField } from 'uniforms-antd';
|
||||
import { ErrorBoundary } from '@sentry/react';
|
||||
import { DataFormControls } from './DataFormControls';
|
||||
|
||||
@@ -52,7 +53,7 @@ export const DataForm: React.FunctionComponent<IDataFormProps> = ({ schema, mode
|
||||
schema={bridge}
|
||||
model={model || {}}
|
||||
onSubmit={onSubmit}
|
||||
ref={form => form?.reset()}>
|
||||
ref={(form: any) => form?.reset()}>
|
||||
<div className={`fields`}>
|
||||
<AutoFields />
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ChevronRightIcon } from '@heroicons/react/outline';
|
||||
import { ToastContainer, toast, Slide } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { DataType } from '../../../models/DataType';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
|
||||
export interface IDataViewProps {}
|
||||
|
||||
@@ -102,6 +103,10 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (props: React.P
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.listen(messageListener);
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewDataView
|
||||
});
|
||||
|
||||
return () => {
|
||||
Messenger.unlisten(messageListener);
|
||||
|
||||
@@ -3,4 +3,4 @@ import { SortableContainer } from 'react-sortable-hoc';
|
||||
|
||||
export interface ISortableContainerProps {}
|
||||
|
||||
export const Container = SortableContainer(({ children }: React.PropsWithChildren<ISortableContainerProps>) => ( <ul className={`-mx-4 divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>{children}</ul> ));
|
||||
export const Container = SortableContainer(({ children }: React.PropsWithChildren<ISortableContainerProps>) => ( <ul className={`-mx-4 divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>{children}</ul>));
|
||||
@@ -23,8 +23,10 @@ export const SortableItem = SortableElement(({ value, selectedIndex, crntIndex,
|
||||
|
||||
return (
|
||||
<>
|
||||
<li data-test={`${selectedIndex}-${crntIndex}`} className={`py-2 px-2 w-full flex justify-between content-center hover:bg-gray-200 dark:hover:bg-vulcan-400 ${selectedIndex === crntIndex ? `bg-gray-300 dark:bg-vulcan-300` : ``}`}>
|
||||
<div className='flex items-center'>
|
||||
<li data-test={`${selectedIndex}-${crntIndex}`} className={`sortable_item py-2 px-2 w-full flex justify-between content-center hover:bg-gray-200 dark:hover:bg-vulcan-400 ${selectedIndex === crntIndex ? `bg-gray-300 dark:bg-vulcan-300` : ``}`}>
|
||||
<div
|
||||
className='flex items-center w-full'
|
||||
onClick={() => onSelectedIndexChange(crntIndex)}>
|
||||
<DragHandle />
|
||||
<span>{value}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { ClipboardIcon, CodeIcon, EyeIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { ClipboardIcon, CodeIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { basename, dirname } from 'path';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
@@ -81,6 +81,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
multiple: viewData?.data?.multiple,
|
||||
value: viewData?.data?.value,
|
||||
position: viewData?.data?.position || null,
|
||||
blockData: typeof viewData?.data?.blockData !== "undefined" ? viewData?.data?.blockData : undefined,
|
||||
alt: alt || "",
|
||||
caption: caption || ""
|
||||
});
|
||||
@@ -94,6 +95,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
|
||||
snippet = snippet?.replace("{alt}", alt || "");
|
||||
snippet = snippet?.replace("{caption}", caption || "");
|
||||
snippet = snippet?.replace("{filename}", basename(relPath || ""));
|
||||
snippet = snippet?.replace("{mediaWidth}", media?.dimensions?.width?.toString() || "");
|
||||
snippet = snippet?.replace("{mediaHeight}", media?.dimensions?.height?.toString() || "");
|
||||
|
||||
Messenger.send(DashboardMessage.insertPreviewImage, {
|
||||
image: parseWinPath(relPath) || "",
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { EventData } from '@estruyf/vscode/dist/models';
|
||||
import {UploadIcon} from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { MediaInfo, MediaPaths } from '../../../models/MediaPaths';
|
||||
import { DashboardCommand } from '../../DashboardCommand';
|
||||
import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { LoadingAtom, MediaFoldersAtom, SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { Header } from '../Header';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { SponsorMsg } from '../SponsorMsg';
|
||||
@@ -13,11 +10,12 @@ import { Item } from './Item';
|
||||
import { Lightbox } from './Lightbox';
|
||||
import { List } from './List';
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMatterIcon';
|
||||
import { FolderItem } from './FolderItem';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import { TelemetryEvent } from '../../../constants';
|
||||
|
||||
export interface IMediaProps {}
|
||||
|
||||
@@ -46,6 +44,12 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
|
||||
});
|
||||
}, [selectedFolder]);
|
||||
|
||||
useEffect(() => {
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewMediaView
|
||||
});
|
||||
}, []);
|
||||
|
||||
const {getRootProps, isDragActive} = useDropzone({
|
||||
onDrop,
|
||||
accept: 'image/*'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {HeartIcon, StarIcon} from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { GITHUB_LINK, REVIEW_LINK, SPONSOR_LINK } from '../../constants';
|
||||
import { GITHUB_LINK, REVIEW_LINK, SPONSOR_LINK, TelemetryEvent } from '../../constants';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { FrontMatterIcon } from '../../panelWebView/components/Icons/FrontMatterIcon';
|
||||
import { GitHubIcon } from '../../panelWebView/components/Icons/GitHubIcon';
|
||||
@@ -15,10 +15,15 @@ export interface IWelcomeScreenProps {
|
||||
export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({settings}: React.PropsWithChildren<IWelcomeScreenProps>) => {
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
Messenger.send(DashboardMessage.sendTelemetry, {
|
||||
event: TelemetryEvent.webviewWelcomeScreen
|
||||
});
|
||||
|
||||
return () => {
|
||||
Messenger.send(DashboardMessage.reload)
|
||||
};
|
||||
}, ['']);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`h-full overflow-auto py-24`}>
|
||||
|
||||
@@ -100,12 +100,12 @@ export default function usePages(pages: Page[]) {
|
||||
|
||||
// Filter by tag
|
||||
if (tag) {
|
||||
pagesSorted = pagesSorted.filter(page => page.tags && page.tags.includes(tag));
|
||||
pagesSorted = pagesSorted.filter(page => page.fmTags && page.fmTags.includes(tag));
|
||||
}
|
||||
|
||||
// Filter by category
|
||||
if (category) {
|
||||
pagesSorted = pagesSorted.filter(page => page.categories && page.categories.includes(category));
|
||||
pagesSorted = pagesSorted.filter(page => page.fmCategories && page.fmCategories.includes(category));
|
||||
}
|
||||
|
||||
setPageItems(pagesSorted);
|
||||
|
||||
@@ -7,6 +7,9 @@ export interface Page {
|
||||
fmModified: number;
|
||||
fmDraft: "Draft" | "Published",
|
||||
fmYear: number | null | undefined;
|
||||
fmPreviewImage: string;
|
||||
fmTags: string[];
|
||||
fmCategories: string[];
|
||||
|
||||
title: string;
|
||||
slug: string;
|
||||
|
||||
@@ -2,6 +2,40 @@
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
:root {
|
||||
/* Bool field */
|
||||
--frontmatter-toggle-background: #15c2cb;
|
||||
--frontmatter-toggle-secondaryBackground: #ADADAD;
|
||||
|
||||
/* Errors field */
|
||||
--frontmatter-error-background: rgba(255, 85, 0, 0.2);
|
||||
--frontmatter-error-border: #f50;
|
||||
--frontmatter-error-foreground: #0e131f;
|
||||
|
||||
.vscode-dark {
|
||||
--frontmatter-error-foreground: #fff;
|
||||
}
|
||||
|
||||
/* List add field */
|
||||
--frontmatter-field-border: #222733;
|
||||
--frontmatter-field-borderActive: #15c2cb;
|
||||
|
||||
.vscode-dark {
|
||||
--frontmatter-field-border: rgba(255, 255, 255, 0.5);
|
||||
--frontmatter-field-borderActive: #009aa3;
|
||||
}
|
||||
|
||||
/* List field */
|
||||
--frontmatter-list-border: #ADADAD;
|
||||
|
||||
.vscode-dark {
|
||||
--frontmatter-list-border: #404551;
|
||||
}
|
||||
|
||||
/* Select field */
|
||||
--frontmatter-select-foreground: #0e131f;
|
||||
}
|
||||
|
||||
|
||||
.loader {
|
||||
border-top-color: #15c2cb;
|
||||
@@ -80,15 +114,11 @@
|
||||
|
||||
.errors {
|
||||
> div {
|
||||
@apply border border-red-400 !important;
|
||||
@apply border;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply list-disc pl-6 pr-4 py-4 bg-opacity-50 text-vulcan-500;
|
||||
}
|
||||
|
||||
li {
|
||||
@apply capitalize text-gray-900;
|
||||
@apply list-disc pl-6 pr-4 py-4 bg-opacity-50;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,12 +306,6 @@
|
||||
input[type="submit"] {
|
||||
@apply text-vulcan-500
|
||||
}
|
||||
|
||||
.errors {
|
||||
li {
|
||||
@apply text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-list.ant-list-bordered {
|
||||
|
||||
@@ -1,30 +1,12 @@
|
||||
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, 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, ThemeIcon } from "vscode";
|
||||
import { ArticleHelper, Settings } from "../helpers";
|
||||
import { ArticleListener, ExtensionListener, MediaListener, ScriptListener, TaxonomyListener, DataListener, SettingsListener } from './../listeners/panel';
|
||||
import { TelemetryEvent } from '../constants';
|
||||
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window } from "vscode";
|
||||
import { Logger, Settings } from "../helpers";
|
||||
import { Command } from "../panelWebView/Command";
|
||||
import { CommandToCode } from '../panelWebView/CommandToCode';
|
||||
import { Article } from '../commands';
|
||||
import { TagType } from '../panelWebView/TagType';
|
||||
import { CustomTaxonomyData, DraftField, Field, ScriptType, TaxonomyType } from '../models';
|
||||
import { exec } from 'child_process';
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown';
|
||||
import { Content } from 'mdast';
|
||||
import { COMMAND_NAME, EXTENSION_BETA_ID, EXTENSION_ID } from '../constants/Extension';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { Preview } from '../commands/Preview';
|
||||
import { openFileInEditor } from '../helpers/openFileInEditor';
|
||||
import { WebviewHelper } from '@estruyf/vscode';
|
||||
import { Extension } from '../helpers/Extension';
|
||||
import { Dashboard } from '../commands/Dashboard';
|
||||
import { ImageHelper } from '../helpers/ImageHelper';
|
||||
import { CustomScript } from '../helpers/CustomScript';
|
||||
import { Link, Parent } from 'mdast-util-from-markdown/lib';
|
||||
|
||||
const FILE_LIMIT = 10;
|
||||
import { Telemetry } from '../helpers/Telemetry';
|
||||
|
||||
export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
public static readonly viewType = "frontMatter.explorer";
|
||||
@@ -63,6 +45,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public getWebview() {
|
||||
return this.panel?.webview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default resolve webview panel
|
||||
* @param webviewView
|
||||
@@ -85,192 +71,43 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
);
|
||||
|
||||
webviewView.webview.onDidReceiveMessage(async (msg) => {
|
||||
switch(msg.command) {
|
||||
case CommandToCode.getData:
|
||||
this.getSettings();
|
||||
this.getFoldersAndFiles();
|
||||
this.getFileData();
|
||||
break;
|
||||
case CommandToCode.updateSlug:
|
||||
Article.generateSlug();
|
||||
break;
|
||||
case CommandToCode.updateLastMod:
|
||||
Article.setLastModifiedDate();
|
||||
break;
|
||||
case CommandToCode.publish:
|
||||
Article.toggleDraft();
|
||||
break;
|
||||
case CommandToCode.updateTags:
|
||||
this.updateTags(TagType.tags, msg.data?.values || [], msg.data?.parents || []);
|
||||
break;
|
||||
case CommandToCode.updateCategories:
|
||||
this.updateTags(TagType.categories, msg.data?.values || [], msg.data?.parents || []);
|
||||
break;
|
||||
case CommandToCode.updateKeywords:
|
||||
this.updateTags(TagType.keywords, msg.data?.values || [], msg.data?.parents || []);
|
||||
break;
|
||||
case CommandToCode.updateCustomTaxonomy:
|
||||
this.updateCustomTaxonomy(msg.data);
|
||||
break;
|
||||
case CommandToCode.addTagToSettings:
|
||||
this.addTags(TagType.tags, msg.data);
|
||||
break;
|
||||
case CommandToCode.addCategoryToSettings:
|
||||
this.addTags(TagType.categories, msg.data);
|
||||
break;
|
||||
case CommandToCode.addToCustomTaxonomy:
|
||||
this.addCustomTaxonomy(msg.data);
|
||||
break;
|
||||
case CommandToCode.openSettings:
|
||||
const isBeta = Extension.getInstance().isBetaVersion();
|
||||
commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`);
|
||||
break;
|
||||
case CommandToCode.openFile:
|
||||
if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
|
||||
commands.executeCommand('remote-wsl.revealInExplorer');
|
||||
} else {
|
||||
commands.executeCommand('revealFileInOS');
|
||||
}
|
||||
break;
|
||||
case CommandToCode.runCustomScript:
|
||||
this.runCustomScript(msg);
|
||||
break;
|
||||
case CommandToCode.openProject:
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
const wsPath = wsFolder.fsPath;
|
||||
if (os.type() === "Darwin") {
|
||||
exec(`open ${wsPath}`);
|
||||
} else if (os.type() === "Windows_NT") {
|
||||
exec(`explorer ${wsPath}`);
|
||||
} else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
|
||||
exec('explorer.exe `wslpath -w "$PWD"`');
|
||||
} else {
|
||||
exec(`xdg-open ${wsPath}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CommandToCode.initProject:
|
||||
await commands.executeCommand(COMMAND_NAME.init);
|
||||
this.getSettings();
|
||||
break;
|
||||
case CommandToCode.createContent:
|
||||
await commands.executeCommand(COMMAND_NAME.createContent);
|
||||
break;
|
||||
case CommandToCode.createTemplate:
|
||||
await commands.executeCommand(COMMAND_NAME.createTemplate);
|
||||
break;
|
||||
case CommandToCode.updateModifiedUpdating:
|
||||
this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false);
|
||||
break;
|
||||
case CommandToCode.toggleWritingSettings:
|
||||
this.toggleWritingSettings();
|
||||
break;
|
||||
case CommandToCode.updateFmHighlight:
|
||||
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`);
|
||||
break;
|
||||
case CommandToCode.openPreview:
|
||||
await commands.executeCommand(COMMAND_NAME.preview);
|
||||
break;
|
||||
case CommandToCode.openDashboard:
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard);
|
||||
break;
|
||||
case CommandToCode.updatePreviewUrl:
|
||||
this.updateSetting(SETTING_PREVIEW_HOST, msg.data || "");
|
||||
break;
|
||||
case CommandToCode.openInEditor:
|
||||
openFileInEditor(msg.data);
|
||||
break;
|
||||
case CommandToCode.updateMetadata:
|
||||
this.updateMetadata(msg.data);
|
||||
break;
|
||||
case CommandToCode.selectImage:
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: "media",
|
||||
data: msg.data
|
||||
} 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;
|
||||
}
|
||||
Logger.info(`Receiving message from webview to panel: ${msg.command}`);
|
||||
|
||||
ArticleListener.process(msg);
|
||||
DataListener.process(msg);
|
||||
ExtensionListener.process(msg);
|
||||
MediaListener.process(msg);
|
||||
ScriptListener.process(msg);
|
||||
SettingsListener.process(msg);
|
||||
TaxonomyListener.process(msg);
|
||||
});
|
||||
|
||||
webviewView.onDidChangeVisibility(() => {
|
||||
if (this.visible) {
|
||||
// this.getFileData();
|
||||
Telemetry.send(TelemetryEvent.openExplorerView);
|
||||
DataListener.getFileData();
|
||||
}
|
||||
});
|
||||
|
||||
window.onDidChangeActiveTextEditor(() => {
|
||||
this.postWebviewMessage({ command: Command.loading, data: true });
|
||||
this.sendMessage({ command: Command.loading, data: true });
|
||||
|
||||
if (this.visible) {
|
||||
this.getFileData();
|
||||
DataListener.getFileData();
|
||||
}
|
||||
}, this);
|
||||
|
||||
Settings.onConfigChange((global?: any) => {
|
||||
this.getSettings();
|
||||
SettingsListener.getSettings();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a metadata change in the panel
|
||||
* @param metadata
|
||||
* Post data to the panel
|
||||
* @param msg
|
||||
*/
|
||||
public pushMetadata(metadata: any) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const filePath = window.activeTextEditor?.document.uri.fsPath;
|
||||
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
|
||||
const contentTypes = Settings.get<string>(SETTING_TAXONOMY_CONTENT_TYPES);
|
||||
|
||||
const articleDetails = this.getArticleDetails();
|
||||
|
||||
if (articleDetails) {
|
||||
metadata.articleDetails = articleDetails;
|
||||
}
|
||||
|
||||
let updatedMetadata = Object.assign({}, metadata);
|
||||
if (commaSeparated) {
|
||||
for (const key of commaSeparated) {
|
||||
if (updatedMetadata[key] && typeof updatedMetadata[key] === "string") {
|
||||
updatedMetadata[key] = updatedMetadata[key].split(",").map((s: string) => s.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const keys = Object.keys(updatedMetadata);
|
||||
if (keys.length > 0) {
|
||||
updatedMetadata.filePath = filePath;
|
||||
}
|
||||
|
||||
if (keys.length > 0 && contentTypes && wsFolder) {
|
||||
// Get the current content type
|
||||
const contentType = ArticleHelper.getContentType(updatedMetadata);
|
||||
if (contentType) {
|
||||
this.processImageFields(updatedMetadata, contentType.fields)
|
||||
}
|
||||
}
|
||||
|
||||
// Check slug
|
||||
if (!updatedMetadata[DefaultFields.Slug]) {
|
||||
const slug = Article.getSlug();
|
||||
|
||||
if (slug) {
|
||||
updatedMetadata[DefaultFields.Slug] = slug;
|
||||
}
|
||||
}
|
||||
|
||||
this.postWebviewMessage({ command: Command.metadata, data: {
|
||||
...updatedMetadata
|
||||
}});
|
||||
public sendMessage(msg: { command: Command, data?: any }) {
|
||||
this.panel?.webview?.postMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,9 +116,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
*/
|
||||
public triggerInputFocus(tagType: TagType) {
|
||||
if (tagType === TagType.tags) {
|
||||
this.postWebviewMessage({ command: Command.focusOnTags });
|
||||
this.sendMessage({ command: Command.focusOnTags });
|
||||
} else {
|
||||
this.postWebviewMessage({ command: Command.focusOnCategories });
|
||||
this.sendMessage({ command: Command.focusOnCategories });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,395 +126,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
* Trigger all sections to close
|
||||
*/
|
||||
public collapseAll() {
|
||||
this.postWebviewMessage({ command: Command.closeSections });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metadata of the article
|
||||
*/
|
||||
public async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
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) {
|
||||
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
|
||||
parentObj[field] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const imageField of imageFields) {
|
||||
if (field === imageField.name) {
|
||||
// If value is an array, it means it comes from the explorer view itself (deletion)
|
||||
if (Array.isArray(value)) {
|
||||
parentObj[field] = value || [];
|
||||
} else { // Otherwise it is coming from the media dashboard (addition)
|
||||
let fieldValue = parentObj[field];
|
||||
if (fieldValue && !Array.isArray(fieldValue)) {
|
||||
fieldValue = [fieldValue];
|
||||
}
|
||||
const crntData = Object.assign([], fieldValue);
|
||||
const allRelPaths = [...(crntData || []), value];
|
||||
parentObj[field] = [...new Set(allRelPaths)].filter(f => f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArticleHelper.update(editor, article);
|
||||
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`,
|
||||
iconPath: new ThemeIcon('server-environment'),
|
||||
message: `Starting local server`,
|
||||
});
|
||||
}
|
||||
|
||||
if (terminal) {
|
||||
terminal.sendText(command);
|
||||
terminal.show(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a custom script
|
||||
* @param msg
|
||||
*/
|
||||
private runCustomScript(msg: { command: string, data: any}) {
|
||||
const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS);
|
||||
|
||||
if (msg?.data?.title && msg?.data?.script && scripts) {
|
||||
const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title);
|
||||
if (customScript?.script && customScript?.title) {
|
||||
CustomScript.run(customScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the media selection
|
||||
*/
|
||||
public async getMediaSelection() {
|
||||
this.postWebviewMessage({
|
||||
command: Command.mediaSelectionData,
|
||||
data: Dashboard.viewData
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the extension settings
|
||||
*/
|
||||
public async getSettings() {
|
||||
this.postWebviewMessage({
|
||||
command: Command.settings,
|
||||
data: {
|
||||
seo: {
|
||||
title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1,
|
||||
slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1,
|
||||
description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1,
|
||||
content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1,
|
||||
descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description
|
||||
},
|
||||
slug: {
|
||||
prefix: Settings.get(SETTING_SLUG_PREFIX) || "",
|
||||
suffix: Settings.get(SETTING_SLUG_SUFFIX) || "",
|
||||
updateFileName: !!Settings.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME),
|
||||
},
|
||||
date: {
|
||||
format: Settings.get(SETTING_DATE_FORMAT)
|
||||
},
|
||||
tags: Settings.get(SETTING_TAXONOMY_TAGS, true) || [],
|
||||
categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [],
|
||||
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
|
||||
freeform: Settings.get(SETTING_PANEL_FREEFORM),
|
||||
scripts: (Settings.get<ICustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type),
|
||||
isInitialized: await Template.isInitialized(),
|
||||
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
|
||||
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
|
||||
fmHighlighting: Settings.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
|
||||
preview: Preview.getSettings(),
|
||||
commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [],
|
||||
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
|
||||
dashboardViewData: Dashboard.viewData,
|
||||
draftField: Settings.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
|
||||
isBacker: await Extension.getInstance().getState<boolean | undefined>(CONTEXT.backer, 'global'),
|
||||
framework: Settings.get<string>(SETTINGS_FRAMEWORK_ID),
|
||||
commands: {
|
||||
start: Settings.get<string>(SETTINGS_FRAMEWORK_START)
|
||||
}
|
||||
} as PanelSettings
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the information about the registered folders and its files
|
||||
*/
|
||||
public async getFoldersAndFiles() {
|
||||
this.postWebviewMessage({
|
||||
command: Command.folderInfo,
|
||||
data: await Folders.getInfo(FILE_LIMIT) || null
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
if (parentObj) {
|
||||
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
|
||||
*/
|
||||
private getFileData() {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data) {
|
||||
this.pushMetadata(article!.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tags in the current document
|
||||
* @param tagType
|
||||
* @param values
|
||||
*/
|
||||
private updateTags(tagType: TagType, values: string[], parents: string[]) {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article && article.data) {
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tags in the current document
|
||||
* @param data
|
||||
*/
|
||||
private updateCustomTaxonomy(data: CustomTaxonomyData) {
|
||||
if (!data?.id || !data?.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article && article.data) {
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tag to the settings
|
||||
* @param data
|
||||
*/
|
||||
private async addCustomTaxonomy(data: CustomTaxonomyData) {
|
||||
if (!data?.id || !data?.option) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Settings.updateCustomTaxonomy(data.id, data.option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tag to the settings
|
||||
* @param tagType
|
||||
* @param value
|
||||
*/
|
||||
private async addTags(tagType: TagType, value: string) {
|
||||
if (value) {
|
||||
let options = tagType === TagType.tags ? Settings.get<string[]>(SETTING_TAXONOMY_TAGS, true) : Settings.get<string[]>(SETTING_TAXONOMY_CATEGORIES, true);
|
||||
|
||||
if (!options) {
|
||||
options = [];
|
||||
}
|
||||
|
||||
options.push(value);
|
||||
const taxType = tagType === TagType.tags ? TaxonomyType.Tag : TaxonomyType.Category;
|
||||
await Settings.updateTaxonomy(taxType, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get article details
|
||||
*/
|
||||
private getArticleDetails() {
|
||||
const baseUrl = Settings.get<string>(SETTING_SITE_BASEURL);
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ArticleHelper.isMarkdownFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
|
||||
if (article && article.content) {
|
||||
let content = article.content;
|
||||
content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes
|
||||
|
||||
const mdTree = fromMarkdown(content);
|
||||
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
|
||||
|
||||
const headings = elms.filter(node => node.type === 'heading');
|
||||
const paragraphs = elms.filter(node => node.type === 'paragraph').length;
|
||||
const images = elms.filter(node => node.type === 'image').length;
|
||||
const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url);
|
||||
|
||||
const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length;
|
||||
let externalLinks = links.filter(link => link.startsWith('http'));
|
||||
if (baseUrl) {
|
||||
externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase()));
|
||||
}
|
||||
|
||||
const headers = [];
|
||||
for (const header of headings) {
|
||||
const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" ");
|
||||
if (text) {
|
||||
headers.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
const wordCount = this.wordCount(0, mdTree);
|
||||
|
||||
return {
|
||||
headings: headings.length,
|
||||
headingsText: headers,
|
||||
paragraphs,
|
||||
images,
|
||||
internalLinks,
|
||||
externalLinks: externalLinks.length,
|
||||
wordCount,
|
||||
content: article.content
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private getAllElms(node: Content | any, allElms?: any[]): any[] {
|
||||
if (!allElms) {
|
||||
allElms = [];
|
||||
}
|
||||
|
||||
if (node.children?.length > 0) {
|
||||
for (const child of node.children) {
|
||||
allElms.push(Object.assign({}, child));
|
||||
this.getAllElms(child, allElms);
|
||||
}
|
||||
}
|
||||
|
||||
return allElms;
|
||||
this.sendMessage({ command: Command.closeSections });
|
||||
}
|
||||
|
||||
private counts(acc: any, node: any) {
|
||||
@@ -691,69 +140,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the word count for the current document
|
||||
*/
|
||||
private wordCount(count: number, node: Content | any) {
|
||||
if (node.type === "text") {
|
||||
return count + node.value.split(" ").length;
|
||||
} else {
|
||||
return (node.children || []).reduce((childCount: number, childNode: any) => this.wordCount(childCount, childNode), count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the writing settings
|
||||
*/
|
||||
private async toggleWritingSettings() {
|
||||
const config = workspace.getConfiguration("", { languageId: "markdown" });
|
||||
const enabled = this.isWritingSettingsEnabled();
|
||||
|
||||
await config.update("editor.fontSize", enabled ? undefined : 14, false, true);
|
||||
await config.update("editor.lineHeight", enabled ? undefined : 26, false, true);
|
||||
await config.update("editor.wordWrap", enabled ? undefined : "wordWrapColumn", false, true);
|
||||
await config.update("editor.wordWrapColumn", enabled ? undefined : 64, false, true);
|
||||
await config.update("editor.lineNumbers", enabled ? undefined : "off", false, true);
|
||||
await config.update("editor.quickSuggestions", enabled ? undefined : false, false, true);
|
||||
await config.update("editor.minimap.enabled", enabled ? undefined : false, false, true);
|
||||
|
||||
this.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the writing settings are enabled
|
||||
*/
|
||||
private isWritingSettingsEnabled() {
|
||||
const config = workspace.getConfiguration("", { languageId: "markdown" });
|
||||
|
||||
const fontSize = config.get("editor.fontSize");
|
||||
const lineHeight = config.get("editor.lineHeight");
|
||||
const wordWrap = config.get("editor.wordWrap");
|
||||
const wordWrapColumn = config.get("editor.wordWrapColumn");
|
||||
const lineNumbers = config.get("editor.lineNumbers");
|
||||
const quickSuggestions = config.get<boolean>("editor.quickSuggestions");
|
||||
|
||||
return fontSize && lineHeight && wordWrap && wordWrapColumn && lineNumbers && quickSuggestions !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a setting and refreshes the retrieved settings
|
||||
* @param setting
|
||||
* @param value
|
||||
*/
|
||||
private async updateSetting(setting: string, value: any) {
|
||||
await Settings.update(setting, value);
|
||||
this.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post data to the panel
|
||||
* @param msg
|
||||
*/
|
||||
private postWebviewMessage(msg: { command: Command, data?: any }) {
|
||||
this.panel?.webview?.postMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the webview HTML contents
|
||||
* @param webView
|
||||
@@ -785,7 +171,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
|
||||
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 'unsafe-eval' ${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}`}`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Telemetry } from './helpers/Telemetry';
|
||||
import { ContentType } from './helpers/ContentType';
|
||||
import { Dashboard } from './commands/Dashboard';
|
||||
import * as vscode from 'vscode';
|
||||
@@ -6,7 +7,7 @@ import { Folders } from './commands/Folders';
|
||||
import { Preview } from './commands/Preview';
|
||||
import { Project } from './commands/Project';
|
||||
import { Template } from './commands/Template';
|
||||
import { COMMAND_NAME } from './constants';
|
||||
import { COMMAND_NAME, TelemetryEvent } from './constants';
|
||||
import { TaxonomyType } from './models';
|
||||
import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider';
|
||||
import { TagType } from './panelWebView/TagType';
|
||||
@@ -18,16 +19,15 @@ import { Content } from './commands/Content';
|
||||
import ContentProvider from './providers/ContentProvider';
|
||||
import { Wysiwyg } from './commands/Wysiwyg';
|
||||
import { Diagnostics } from './commands/Diagnostics';
|
||||
import { PagesListener } from './listeners';
|
||||
import { PagesListener } from './listeners/dashboard';
|
||||
import { Backers } from './commands/Backers';
|
||||
import { DataListener, SettingsListener } from './listeners/panel';
|
||||
|
||||
let frontMatterStatusBar: vscode.StatusBarItem;
|
||||
let statusDebouncer: { (fnc: any, time: number): void; };
|
||||
let editDebounce: { (fnc: any, time: number): void; };
|
||||
let collection: vscode.DiagnosticCollection;
|
||||
|
||||
const mdSelector: vscode.DocumentSelector = { language: 'markdown', scheme: 'file' };
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
const { subscriptions, extensionUri, extensionPath } = context;
|
||||
|
||||
@@ -43,6 +43,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
SettingsHelper.checkToPromote();
|
||||
|
||||
// Sends the activation event
|
||||
Telemetry.send(TelemetryEvent.activate);
|
||||
|
||||
// Start listening to the folders for content changes.
|
||||
// This will make sure the dashboard is up to date
|
||||
PagesListener.startWatchers();
|
||||
@@ -52,6 +55,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// Pages dashboard
|
||||
Dashboard.init();
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openContentDashboard);
|
||||
if (!data) {
|
||||
Dashboard.open({ type: "contents" });
|
||||
} else {
|
||||
@@ -60,14 +64,17 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}));
|
||||
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardMedia, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openMediaDashboard);
|
||||
Dashboard.open({ type: "media" });
|
||||
}));
|
||||
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardData, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.openDataDashboard);
|
||||
Dashboard.open({ type: "data" });
|
||||
}));
|
||||
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardClose, (data?: DashboardData) => {
|
||||
Telemetry.send(TelemetryEvent.closeDashboard);
|
||||
Dashboard.close();
|
||||
}));
|
||||
|
||||
@@ -84,7 +91,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
});
|
||||
|
||||
// Folding the front matter of markdown files
|
||||
vscode.languages.registerFoldingRangeProvider(mdSelector, new MarkdownFoldingProvider());
|
||||
MarkdownFoldingProvider.register();
|
||||
|
||||
const insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => {
|
||||
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
|
||||
@@ -151,7 +158,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
});
|
||||
|
||||
// Settings promotion command
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, SettingsHelper.promote ));
|
||||
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.promote, SettingsHelper.promote));
|
||||
|
||||
// Collapse all sections in the webview
|
||||
const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => {
|
||||
@@ -163,9 +170,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
Template.init();
|
||||
Preview.init();
|
||||
|
||||
const exView = ExplorerView.getInstance();
|
||||
exView.getSettings();
|
||||
exView.getFoldersAndFiles();
|
||||
SettingsListener.getSettings();
|
||||
DataListener.getFoldersAndFiles();
|
||||
MarkdownFoldingProvider.triggerHighlighting();
|
||||
});
|
||||
|
||||
@@ -189,7 +195,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
subscriptions.push(vscode.workspace.onDidSaveTextDocument((doc: vscode.TextDocument) => {
|
||||
if (doc.languageId === 'markdown') {
|
||||
// Optimize the list of recently changed files
|
||||
ExplorerView.getInstance().getFoldersAndFiles();
|
||||
DataListener.getFoldersAndFiles();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -234,7 +240,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
export function deactivate() {
|
||||
Telemetry.dispose();
|
||||
}
|
||||
|
||||
const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => {
|
||||
Article.autoUpdate(e);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { MarkdownFoldingProvider } from './../providers/MarkdownFoldingProvider';
|
||||
import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType';
|
||||
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, 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 { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTINGS_FILE_PRESERVE_CASING, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants';
|
||||
import { DumpOptions } from 'js-yaml';
|
||||
import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine';
|
||||
import { FrontMatterParser, ParsedFrontMatter } from '../parsers';
|
||||
import { Extension, Logger, Settings, SlugHelper } from '.';
|
||||
import { format, parse } from 'date-fns';
|
||||
import { Notifications } from './Notifications';
|
||||
@@ -18,6 +17,9 @@ import { ContentType } from '../models';
|
||||
import { DateHelper } from './DateHelper';
|
||||
import { DiagnosticSeverity, Position, window, Range } from 'vscode';
|
||||
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown';
|
||||
import { Link, Parent } from 'mdast-util-from-markdown/lib';
|
||||
import { Content } from 'mdast';
|
||||
|
||||
export class ArticleHelper {
|
||||
private static notifiedFiles: string[] = [];
|
||||
@@ -56,7 +58,7 @@ export class ArticleHelper {
|
||||
* @param editor
|
||||
* @param article
|
||||
*/
|
||||
public static async update(editor: vscode.TextEditor, article: matter.GrayMatterFile<string>) {
|
||||
public static async update(editor: vscode.TextEditor, article: ParsedFrontMatter) {
|
||||
const update = this.generateUpdate(editor.document, article);
|
||||
|
||||
await editor.edit(builder => builder.replace(update.range, update.newText));
|
||||
@@ -66,7 +68,7 @@ export class ArticleHelper {
|
||||
* Generate the update to be applied to the article.
|
||||
* @param article
|
||||
*/
|
||||
public static generateUpdate(document: vscode.TextDocument, article: matter.GrayMatterFile<string>): vscode.TextEdit {
|
||||
public static generateUpdate(document: vscode.TextDocument, article: ParsedFrontMatter): vscode.TextEdit {
|
||||
const nrOfLines = document.lineCount as number;
|
||||
const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(nrOfLines, 0));
|
||||
const removeQuotes = Settings.get(SETTING_REMOVE_QUOTES) as string[];
|
||||
@@ -117,9 +119,6 @@ export class ArticleHelper {
|
||||
const indentArray = Settings.get(SETTING_INDENT_ARRAY) as boolean;
|
||||
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
|
||||
|
||||
const language = getFmLanguage();
|
||||
const langOpts = getFormatOpts(language);
|
||||
|
||||
const spaces = vscode.window.activeTextEditor?.options?.tabSize;
|
||||
|
||||
if (commaSeparated) {
|
||||
@@ -130,9 +129,7 @@ export class ArticleHelper {
|
||||
}
|
||||
}
|
||||
|
||||
return matter.stringify(content, data, ({
|
||||
...TomlEngine,
|
||||
...langOpts,
|
||||
return FrontMatterParser.toFile(content, data, ({
|
||||
noArrayIndent: !indentArray,
|
||||
skipInvalid: true,
|
||||
noCompatMode: true,
|
||||
@@ -169,7 +166,7 @@ export class ArticleHelper {
|
||||
/**
|
||||
* Get date from front matter
|
||||
*/
|
||||
public static getDate(article: matter.GrayMatterFile<string> | null) {
|
||||
public static getDate(article: ParsedFrontMatter | null) {
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
@@ -230,7 +227,8 @@ export class ArticleHelper {
|
||||
* @returns
|
||||
*/
|
||||
public static sanitize(value: string): string {
|
||||
return sanitize(value.toLowerCase().replace(/ /g, "-"));
|
||||
const preserveCasing = Settings.get(SETTINGS_FILE_PRESERVE_CASING) as boolean;
|
||||
return sanitize((preserveCasing ? value : value.toLowerCase()).replace(/ /g, "-"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,22 +351,109 @@ export class ArticleHelper {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the details of the current article
|
||||
* @returns
|
||||
*/
|
||||
public static getDetails() {
|
||||
const baseUrl = Settings.get<string>(SETTING_SITE_BASEURL);
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ArticleHelper.isMarkdownFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
|
||||
if (article && article.content) {
|
||||
let content = article.content;
|
||||
content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes
|
||||
|
||||
const mdTree = fromMarkdown(content);
|
||||
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
|
||||
|
||||
const headings = elms.filter(node => node.type === 'heading');
|
||||
const paragraphs = elms.filter(node => node.type === 'paragraph').length;
|
||||
const images = elms.filter(node => node.type === 'image').length;
|
||||
const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url);
|
||||
|
||||
const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length;
|
||||
let externalLinks = links.filter(link => link.startsWith('http'));
|
||||
if (baseUrl) {
|
||||
externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase()));
|
||||
}
|
||||
|
||||
const headers = [];
|
||||
for (const header of headings) {
|
||||
const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" ");
|
||||
if (text) {
|
||||
headers.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
const wordCount = this.wordCount(0, mdTree);
|
||||
|
||||
return {
|
||||
headings: headings.length,
|
||||
headingsText: headers,
|
||||
paragraphs,
|
||||
images,
|
||||
internalLinks,
|
||||
externalLinks: externalLinks.length,
|
||||
wordCount,
|
||||
content: article.content
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the elements from the markdown content
|
||||
* @param node
|
||||
* @param allElms
|
||||
* @returns
|
||||
*/
|
||||
private static getAllElms(node: Content | any, allElms?: any[]): any[] {
|
||||
if (!allElms) {
|
||||
allElms = [];
|
||||
}
|
||||
|
||||
if (node.children?.length > 0) {
|
||||
for (const child of node.children) {
|
||||
allElms.push(Object.assign({}, child));
|
||||
this.getAllElms(child, allElms);
|
||||
}
|
||||
}
|
||||
|
||||
return allElms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the word count for the current document
|
||||
*/
|
||||
private static wordCount(count: number, node: Content | any) {
|
||||
if (node.type === "text") {
|
||||
return count + node.value.split(" ").length;
|
||||
} else {
|
||||
return (node.children || []).reduce((childCount: number, childNode: any) => this.wordCount(childCount, childNode), count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a markdown file and its front matter
|
||||
* @param fileContents
|
||||
* @returns
|
||||
*/
|
||||
private static parseFile(fileContents: string, fileName: string): matter.GrayMatterFile<string> | null {
|
||||
private static parseFile(fileContents: string, fileName: string): ParsedFrontMatter | null {
|
||||
try {
|
||||
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
|
||||
|
||||
if (fileContents) {
|
||||
const language: string = getFmLanguage();
|
||||
const langOpts = getFormatOpts(language);
|
||||
let article: matter.GrayMatterFile<string> | null = matter(fileContents, {
|
||||
...TomlEngine,
|
||||
...langOpts
|
||||
});
|
||||
let article = FrontMatterParser.fromFile(fileContents);
|
||||
|
||||
if (article?.data) {
|
||||
if (commaSeparated) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { PagesListener } from './../listeners/PagesListener';
|
||||
import { PagesListener } from './../listeners/dashboard';
|
||||
import { ArticleHelper, Settings } from ".";
|
||||
import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants";
|
||||
import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
|
||||
import { ContentType as IContentType, DraftField, Field } from '../models';
|
||||
import { Uri, workspace, window, commands } from 'vscode';
|
||||
import { Uri, commands } from 'vscode';
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { Questions } from "./Questions";
|
||||
import { writeFileSync } from "fs";
|
||||
import { Notifications } from "./Notifications";
|
||||
import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType";
|
||||
import { Telemetry } from './Telemetry';
|
||||
|
||||
|
||||
export class ContentType {
|
||||
@@ -125,6 +126,8 @@ export class ContentType {
|
||||
|
||||
Notifications.info(`Your new content has been created.`);
|
||||
|
||||
Telemetry.send(TelemetryEvent.createContentFromContentType);
|
||||
|
||||
// Trigger a refresh for the dashboard
|
||||
PagesListener.refresh();
|
||||
}
|
||||
@@ -138,7 +141,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);
|
||||
|
||||
@@ -3,13 +3,13 @@ import { window, env as vscodeEnv, ProgressLocation } from 'vscode';
|
||||
import { ArticleHelper } from '.';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { exec } from 'child_process';
|
||||
import matter = require('gray-matter');
|
||||
import * as os from 'os';
|
||||
import { join } from 'path';
|
||||
import { Notifications } from './Notifications';
|
||||
import ContentProvider from '../providers/ContentProvider';
|
||||
import { Dashboard } from '../commands/Dashboard';
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
|
||||
export class CustomScript {
|
||||
|
||||
@@ -117,7 +117,7 @@ export class CustomScript {
|
||||
});
|
||||
}
|
||||
|
||||
private static async runScript(wsPath: string, article: matter.GrayMatterFile<string> | null, contentPath: string, script: ICustomScript): Promise<string | null> {
|
||||
private static async runScript(wsPath: string, article: ParsedFrontMatter | null, contentPath: string, script: ICustomScript): Promise<string | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let articleData = "";
|
||||
if (os.type() === "Windows_NT") {
|
||||
|
||||
@@ -84,6 +84,13 @@ export class Extension {
|
||||
return this.ctx.extension.packageJSON.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension's version
|
||||
*/
|
||||
public get version(): string {
|
||||
return this.ctx.extension.packageJSON.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the extension is in production/development mode
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ExplorerView } from './../explorerView/ExplorerView';
|
||||
import { Uri, window } from 'vscode';
|
||||
import { dirname, join } from "path";
|
||||
import { Field } from '../models';
|
||||
@@ -54,11 +55,14 @@ export class ImageHelper {
|
||||
|
||||
const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || "", value);
|
||||
const contentFolderPath = filePath ? join(dirname(filePath), value) : null;
|
||||
const workspaceFolderPath = wsFolder ? join(wsFolder.fsPath, value) : null;
|
||||
|
||||
if (existsSync(staticPath)) {
|
||||
return Uri.file(staticPath);
|
||||
} else if (contentFolderPath && existsSync(contentFolderPath)) {
|
||||
return Uri.file(contentFolderPath);
|
||||
} else if (workspaceFolderPath && existsSync(workspaceFolderPath)) {
|
||||
return Uri.file(workspaceFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,4 +82,57 @@ export class ImageHelper {
|
||||
}
|
||||
return relPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the image fields in the content type
|
||||
* @param updatedMetadata
|
||||
* @param fields
|
||||
* @param parents
|
||||
*/
|
||||
public static processImageFields(updatedMetadata: any, fields: Field[], parents: string[] = []) {
|
||||
const imageFields = fields.filter((field) => field.type === "image");
|
||||
const panel = ExplorerView.getInstance();
|
||||
|
||||
// Support multi-level fields
|
||||
let parentObj = updatedMetadata;
|
||||
for (const parent of parents || []) {
|
||||
parentObj = parentObj[parent];
|
||||
}
|
||||
|
||||
// Process image fields
|
||||
if (parentObj) {
|
||||
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: panel.getWebview()?.asWebviewUri(preview.absPath).toString()
|
||||
}) : null);
|
||||
|
||||
parentObj[field.name] = preview || [];
|
||||
} else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) {
|
||||
const preview = panel.getWebview()?.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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import imageSize from "image-size";
|
||||
import { EditorHelper } from "@estruyf/vscode";
|
||||
import { ExplorerView } from "../explorerView/ExplorerView";
|
||||
import { SortOption } from "../dashboardWebView/constants/SortOption";
|
||||
import { DataListener, MediaListener } from "../listeners/panel";
|
||||
|
||||
|
||||
export class MediaHelpers {
|
||||
@@ -301,10 +302,15 @@ export class MediaHelpers {
|
||||
}
|
||||
});
|
||||
}
|
||||
panel.getMediaSelection();
|
||||
MediaListener.getMediaSelection();
|
||||
} else {
|
||||
panel.getMediaSelection();
|
||||
panel.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents });
|
||||
MediaListener.getMediaSelection();
|
||||
DataListener.updateMetadata({
|
||||
field: data.fieldName,
|
||||
value: data.image,
|
||||
parents: data.parents,
|
||||
blockData: data.blockData
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,30 @@ export class MessageHelper {
|
||||
private static vscode: ClientVsCode<any>;
|
||||
|
||||
public static getVsCodeAPI() {
|
||||
MessageHelper.vscode = acquireVsCodeApi();
|
||||
if (!MessageHelper.vscode) {
|
||||
MessageHelper.vscode = acquireVsCodeApi();
|
||||
}
|
||||
return MessageHelper.vscode;
|
||||
}
|
||||
|
||||
public static sendMessage = (command: CommandToCode | DashboardMessage, data?: any) => {
|
||||
const vscode = MessageHelper.getVsCodeAPI();
|
||||
if (data) {
|
||||
MessageHelper.vscode.postMessage({ command, data });
|
||||
vscode.postMessage({ command, data });
|
||||
} else {
|
||||
MessageHelper.vscode.postMessage({ command });
|
||||
vscode.postMessage({ command });
|
||||
}
|
||||
}
|
||||
|
||||
public static getState = () => {
|
||||
const vscode = MessageHelper.getVsCodeAPI();
|
||||
return vscode.getState();
|
||||
}
|
||||
|
||||
public static setState = (data: any) => {
|
||||
const vscode = MessageHelper.getVsCodeAPI();
|
||||
vscode.setState({
|
||||
...data
|
||||
});
|
||||
}
|
||||
}
|
||||
67
src/helpers/PanelSettings.ts
Normal file
67
src/helpers/PanelSettings.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { workspace } from "vscode"
|
||||
import { Extension, Settings } from "."
|
||||
import { Dashboard } from "../commands/Dashboard"
|
||||
import { Preview } from "../commands/Preview"
|
||||
import { Template } from "../commands/Template"
|
||||
import { CONTEXT, DefaultFields, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_CUSTOM_SCRIPTS, SETTING_DATE_FORMAT, SETTING_PANEL_FREEFORM, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_SLUG_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TAXONOMY_FIELD_GROUPS, SETTING_TAXONOMY_TAGS } from "../constants"
|
||||
import { CustomScript, DataType, DraftField, FieldGroup, PanelSettings as IPanelSettings, ScriptType } from "../models"
|
||||
|
||||
export class PanelSettings {
|
||||
|
||||
public static async get(): Promise<IPanelSettings> {
|
||||
return {
|
||||
seo: {
|
||||
title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1,
|
||||
slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1,
|
||||
description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1,
|
||||
content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1,
|
||||
descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description
|
||||
},
|
||||
slug: {
|
||||
prefix: Settings.get(SETTING_SLUG_PREFIX) || "",
|
||||
suffix: Settings.get(SETTING_SLUG_SUFFIX) || "",
|
||||
updateFileName: !!Settings.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME),
|
||||
},
|
||||
date: {
|
||||
format: Settings.get<string>(SETTING_DATE_FORMAT) || ""
|
||||
},
|
||||
tags: Settings.get(SETTING_TAXONOMY_TAGS, true) || [],
|
||||
categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [],
|
||||
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
|
||||
freeform: Settings.get(SETTING_PANEL_FREEFORM),
|
||||
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type),
|
||||
isInitialized: await Template.isInitialized(),
|
||||
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
|
||||
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
|
||||
fmHighlighting: Settings.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
|
||||
preview: Preview.getSettings(),
|
||||
commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [],
|
||||
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
|
||||
dashboardViewData: Dashboard.viewData,
|
||||
draftField: Settings.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
|
||||
isBacker: await Extension.getInstance().getState<boolean | undefined>(CONTEXT.backer, 'global'),
|
||||
framework: Settings.get<string>(SETTINGS_FRAMEWORK_ID),
|
||||
commands: {
|
||||
start: Settings.get<string>(SETTINGS_FRAMEWORK_START)
|
||||
},
|
||||
dataTypes: Settings.get<DataType[]>(SETTINGS_DATA_TYPES),
|
||||
fieldGroups: Settings.get<FieldGroup[]>(SETTING_TAXONOMY_FIELD_GROUPS),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the writing settings are enabled
|
||||
*/
|
||||
public static isWritingSettingsEnabled(): boolean {
|
||||
const config = workspace.getConfiguration("", { languageId: "markdown" });
|
||||
|
||||
const fontSize = config.get("editor.fontSize");
|
||||
const lineHeight = config.get("editor.lineHeight");
|
||||
const wordWrap = config.get("editor.wordWrap");
|
||||
const wordWrapColumn = config.get("editor.wordWrapColumn");
|
||||
const lineNumbers = config.get("editor.lineNumbers");
|
||||
const quickSuggestions = config.get<boolean>("editor.quickSuggestions");
|
||||
|
||||
return( fontSize && lineHeight && wordWrap && wordWrapColumn && lineNumbers && quickSuggestions !== undefined) as boolean;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { ArticleHelper } from '.';
|
||||
import matter = require('gray-matter');
|
||||
import { ParsedFrontMatter } from '../parsers';
|
||||
|
||||
export class SeoHelper {
|
||||
|
||||
public static checkLength(editor: vscode.TextEditor, collection: vscode.DiagnosticCollection, article: matter.GrayMatterFile<string>, fieldName: string, length: number) {
|
||||
public static checkLength(editor: vscode.TextEditor, collection: vscode.DiagnosticCollection, article: ParsedFrontMatter, fieldName: string, length: number) {
|
||||
const value = article.data[fieldName];
|
||||
if (value.length > length) {
|
||||
const text = editor.document.getText();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Telemetry } from './Telemetry';
|
||||
import { Notifications } from './Notifications';
|
||||
import { commands, Uri, workspace, window } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import { ContentType, CustomTaxonomy, TaxonomyType } from '../models';
|
||||
import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM } from '../constants';
|
||||
import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM, TelemetryEvent } from '../constants';
|
||||
import { Folders } from '../commands/Folders';
|
||||
import { join, basename } from 'path';
|
||||
import { existsSync, readFileSync, watch, writeFileSync } from 'fs';
|
||||
@@ -249,6 +250,8 @@ export class Settings {
|
||||
}
|
||||
|
||||
Notifications.info(`All settings promoted to team level.`);
|
||||
|
||||
Telemetry.send(TelemetryEvent.promoteSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
41
src/helpers/Telemetry.ts
Normal file
41
src/helpers/Telemetry.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import TelemetryReporter, { TelemetryEventMeasurements, TelemetryEventProperties } from '@vscode/extension-telemetry';
|
||||
import { Extension, Settings } from '.';
|
||||
import { EXTENSION_BETA_ID, EXTENSION_ID, SETTING_TELEMETRY_DISABLE } from '../constants';
|
||||
|
||||
export class Telemetry {
|
||||
private static instance: Telemetry;
|
||||
private static reporter: TelemetryReporter | null = null;
|
||||
|
||||
private constructor() {
|
||||
const extension = Extension.getInstance();
|
||||
const extTitle = extension.isBetaVersion() ? EXTENSION_BETA_ID : EXTENSION_ID;
|
||||
const extVersion = extension.version;
|
||||
const appKey = `525037e5-70ff-4620-8e52-30e1aef8deee`;
|
||||
|
||||
Telemetry.reporter = new TelemetryReporter(extTitle, extVersion, appKey);
|
||||
}
|
||||
|
||||
public static getInstance(): Telemetry {
|
||||
if (!Telemetry.instance) {
|
||||
Telemetry.instance = new Telemetry();
|
||||
}
|
||||
return Telemetry.instance;
|
||||
}
|
||||
|
||||
public static send(eventName: string, properties?: TelemetryEventProperties, measurements?: TelemetryEventMeasurements) {
|
||||
if (!Telemetry.reporter) {
|
||||
Telemetry.getInstance();
|
||||
}
|
||||
|
||||
const isDisabled = Settings.get<boolean>(SETTING_TELEMETRY_DISABLE);
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry.reporter?.sendTelemetryEvent(eventName, properties, measurements);
|
||||
}
|
||||
|
||||
public static dispose() {
|
||||
Telemetry.reporter?.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import * as toml from '@iarna/toml';
|
||||
import { SETTING_FRONTMATTER_TYPE } from '../constants';
|
||||
import { Settings } from './SettingsHelper';
|
||||
|
||||
export const getFmLanguage = (): string => {
|
||||
const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML";
|
||||
return language.toLowerCase();
|
||||
};
|
||||
|
||||
export const getFormatOpts = (format: string): { language: string, delimiters: string | [string, string] | undefined } => {
|
||||
const formats: { [prop: string]: { language: string, delimiters: string | [string, string] | undefined }} = {
|
||||
yaml: { language: 'yaml', delimiters: '---' },
|
||||
toml: { language: 'toml', delimiters: '+++' },
|
||||
json: { language: 'json', delimiters: '---' },
|
||||
};
|
||||
|
||||
return formats[format];
|
||||
};
|
||||
|
||||
export const TomlEngine = {
|
||||
engines: {
|
||||
toml: {
|
||||
parse: (value: string) => {
|
||||
return toml.parse(value);
|
||||
},
|
||||
stringify: (value: any) => {
|
||||
return toml.stringify(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -9,6 +9,7 @@ export * from './FrameworkDetector';
|
||||
export * from './GroupBy';
|
||||
export * from './ImageHelper';
|
||||
export * from './Logger';
|
||||
export * from './MediaHelpers';
|
||||
export * from './MediaLibrary';
|
||||
export * from './MessageHelper';
|
||||
export * from './Notifications';
|
||||
@@ -19,7 +20,7 @@ export * from './SettingsHelper';
|
||||
export * from './SlugHelper';
|
||||
export * from './Sorting';
|
||||
export * from './StringHelpers';
|
||||
export * from './TomlEngine';
|
||||
export * from './Telemetry';
|
||||
export * from './decodeBase64Image';
|
||||
export * from './getNonce';
|
||||
export * from './isValidFile';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const parseWinPath = (path: string | undefined): string => {
|
||||
return path?.split(`\\`).join(`/`) || '';
|
||||
}
|
||||
return path?.split(`\\`).join(`/`) || '';
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from '../constants/ContentType';
|
||||
import { Settings } from '../dashboardWebView/models';
|
||||
import { ContentType, PanelSettings } from '../models';
|
||||
import { ContentType, Field, PanelSettings } from '../models';
|
||||
|
||||
export default function useContentType(settings: PanelSettings | Settings | undefined | null, metadata: any) {
|
||||
const [contentType, setContentType] = useState<ContentType>(DEFAULT_CONTENT_TYPE);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { Logger } from "../helpers/Logger";
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Logger } from "../../helpers/Logger";
|
||||
|
||||
|
||||
export abstract class BaseListener {
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { ExtensionState } from "../constants";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { Extension } from "../helpers";
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { ExtensionState } from "../../constants";
|
||||
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Extension } from "../../helpers";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { DataFile } from './../models/DataFile';
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
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 { 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 { Logger, Notifications } from '../../helpers';
|
||||
import { commands } from 'vscode';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { commands, env } from "vscode";
|
||||
import { SettingsListener } from ".";
|
||||
import { COMMAND_NAME } from "../constants";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { CustomScript, Extension } from "../helpers";
|
||||
import { openFileInEditor } from "../helpers/openFileInEditor";
|
||||
import { COMMAND_NAME } from "../../constants";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { CustomScript, Extension } from "../../helpers";
|
||||
import { openFileInEditor } from "../../helpers/openFileInEditor";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { MediaHelpers } from './../helpers/MediaHelpers';
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { Telemetry } from '../../helpers/Telemetry';
|
||||
import { MediaHelpers } from '../../helpers/MediaHelpers';
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
|
||||
import { SortingOption } from '../dashboardWebView/models';
|
||||
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
|
||||
import { SortingOption } from '../../dashboardWebView/models';
|
||||
import { commands, env, Uri } from 'vscode';
|
||||
import { COMMAND_NAME } from '../constants';
|
||||
import { COMMAND_NAME, TelemetryEvent } from '../../constants';
|
||||
import * as os from 'os';
|
||||
|
||||
|
||||
@@ -20,22 +21,27 @@ export class MediaListener extends BaseListener {
|
||||
this.sendMediaFiles(page, folder, sorting);
|
||||
break;
|
||||
case DashboardMessage.refreshMedia:
|
||||
Telemetry.send(TelemetryEvent.refreshMedia);
|
||||
MediaHelpers.resetMedia();
|
||||
this.sendMediaFiles(0, msg?.data?.folder);
|
||||
break;
|
||||
case DashboardMessage.uploadMedia:
|
||||
Telemetry.send(TelemetryEvent.uploadMedia);
|
||||
this.store(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.deleteMedia:
|
||||
Telemetry.send(TelemetryEvent.deleteMedia);
|
||||
this.delete(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.revealMedia:
|
||||
this.openFileInFinder(msg?.data?.file);
|
||||
break;
|
||||
case DashboardMessage.insertPreviewImage:
|
||||
Telemetry.send(TelemetryEvent.insertMediaToContent);
|
||||
MediaHelpers.insertMediaToMarkdown(msg?.data);
|
||||
break;
|
||||
case DashboardMessage.updateMediaMetadata:
|
||||
Telemetry.send(TelemetryEvent.updateMediaMetadata);
|
||||
this.update(msg.data);
|
||||
break;
|
||||
case DashboardMessage.createMediaFolder:
|
||||
@@ -1,18 +1,19 @@
|
||||
import { isValidFile } from './../helpers/isValidFile';
|
||||
import { isValidFile } from '../../helpers/isValidFile';
|
||||
import { existsSync } from "fs";
|
||||
import { basename, dirname, join } from "path";
|
||||
import { commands, FileSystemWatcher, RelativePattern, Uri, workspace } from "vscode";
|
||||
import { Dashboard } from "../commands/Dashboard";
|
||||
import { Folders } from "../commands/Folders";
|
||||
import { COMMAND_NAME, DefaultFields, SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../constants";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { Page } from "../dashboardWebView/models";
|
||||
import { ArticleHelper, Logger, Settings } from "../helpers";
|
||||
import { ContentType } from "../helpers/ContentType";
|
||||
import { DateHelper } from "../helpers/DateHelper";
|
||||
import { Notifications } from "../helpers/Notifications";
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { Folders } from "../../commands/Folders";
|
||||
import { COMMAND_NAME, DefaultFields, SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants";
|
||||
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Page } from "../../dashboardWebView/models";
|
||||
import { ArticleHelper, Logger, Settings } from "../../helpers";
|
||||
import { ContentType } from "../../helpers/ContentType";
|
||||
import { DateHelper } from "../../helpers/DateHelper";
|
||||
import { Notifications } from "../../helpers/Notifications";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { Field, FieldType } from '../../models';
|
||||
|
||||
|
||||
export class PagesListener extends BaseListener {
|
||||
@@ -150,6 +151,9 @@ export class PagesListener extends BaseListener {
|
||||
fmFileName: fileName,
|
||||
fmDraft: ContentType.getDraftStatus(article?.data),
|
||||
fmYear: article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField])?.getFullYear() : null,
|
||||
fmPreviewImage: "",
|
||||
fmTags: [],
|
||||
fmCategories: [],
|
||||
// Make sure these are always set
|
||||
title: article?.data.title,
|
||||
slug: article?.data.slug,
|
||||
@@ -159,35 +163,69 @@ export class PagesListener extends BaseListener {
|
||||
};
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const previewField = contentType.fields.find(field => field.isPreviewImage && field.type === "image")?.name || "preview";
|
||||
|
||||
if (article?.data[previewField] && wsFolder) {
|
||||
let fieldValue = article?.data[previewField];
|
||||
if (fieldValue && Array.isArray(fieldValue)) {
|
||||
if (fieldValue.length > 0) {
|
||||
fieldValue = fieldValue[0];
|
||||
let previewFieldParents = this.findPreviewField(contentType.fields);
|
||||
if (previewFieldParents.length === 0) {
|
||||
const previewField = contentType.fields.find(field => field.type === "image" && field.name === "preview");
|
||||
if (previewField) {
|
||||
previewFieldParents = ["preview"];
|
||||
}
|
||||
}
|
||||
|
||||
let tagParents = this.findFieldByType(contentType.fields, "tags");
|
||||
if (tagParents.length !== 0) {
|
||||
page.fmTags = this.getFieldValue(article.data, tagParents);
|
||||
}
|
||||
|
||||
let categoryParents = this.findFieldByType(contentType.fields, "categories");
|
||||
if (categoryParents.length !== 0) {
|
||||
page.fmCategories = this.getFieldValue(article.data, categoryParents);
|
||||
}
|
||||
|
||||
// Check if parent fields were retrieved, if not there was no image present
|
||||
if (previewFieldParents.length > 0) {
|
||||
let fieldValue = null;
|
||||
let crntPageData = article?.data;
|
||||
|
||||
for (let i = 0; i < previewFieldParents.length; i++) {
|
||||
const previewField = previewFieldParents[i];
|
||||
|
||||
if (i === previewFieldParents.length - 1) {
|
||||
fieldValue = crntPageData[previewField];
|
||||
} else {
|
||||
fieldValue = undefined;
|
||||
if (!crntPageData[previewField]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
crntPageData = crntPageData[previewField];
|
||||
}
|
||||
}
|
||||
|
||||
// Revalidate as the array could have been empty
|
||||
if (fieldValue) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
|
||||
const contentFolderPath = join(dirname(filePath), fieldValue);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
if (fieldValue && wsFolder) {
|
||||
if (fieldValue && Array.isArray(fieldValue)) {
|
||||
if (fieldValue.length > 0) {
|
||||
fieldValue = fieldValue[0];
|
||||
} else {
|
||||
fieldValue = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = Dashboard.getWebview()?.asWebviewUri(previewUri);
|
||||
page[previewField] = preview?.toString() || "";
|
||||
} else {
|
||||
page[previewField] = "";
|
||||
|
||||
// Revalidate as the array could have been empty
|
||||
if (fieldValue) {
|
||||
const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue);
|
||||
const contentFolderPath = join(dirname(filePath), fieldValue);
|
||||
|
||||
let previewUri = null;
|
||||
if (existsSync(staticPath)) {
|
||||
previewUri = Uri.file(staticPath);
|
||||
} else if (existsSync(contentFolderPath)) {
|
||||
previewUri = Uri.file(contentFolderPath);
|
||||
}
|
||||
|
||||
if (previewUri) {
|
||||
const preview = Dashboard.getWebview()?.asWebviewUri(previewUri);
|
||||
page["fmPreviewImage"] = preview?.toString() || "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,4 +235,76 @@ export class PagesListener extends BaseListener {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the field value
|
||||
* @param data
|
||||
* @param parents
|
||||
* @returns
|
||||
*/
|
||||
private static getFieldValue(data: any, parents: string[]): string[] {
|
||||
let fieldValue = [];
|
||||
let crntPageData = data;
|
||||
|
||||
for (let i = 0; i < parents.length; i++) {
|
||||
const crntField = parents[i];
|
||||
|
||||
if (i === parents.length - 1) {
|
||||
fieldValue = crntPageData[crntField];
|
||||
} else {
|
||||
if (!crntPageData[crntField]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
crntPageData = crntPageData[crntField];
|
||||
}
|
||||
}
|
||||
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the field by its type
|
||||
* @param fields
|
||||
* @param type
|
||||
* @param parents
|
||||
* @returns
|
||||
*/
|
||||
private static findFieldByType(fields: Field[], type: FieldType, parents: string[] = []) {
|
||||
for (const field of fields) {
|
||||
if (field.type === type) {
|
||||
parents = [...parents, field.name];
|
||||
return parents;
|
||||
} else if (field.type === "fields" && field.fields) {
|
||||
const subFields = this.findPreviewField(field.fields);
|
||||
if (subFields.length > 0) {
|
||||
return [...parents, field.name, ...subFields];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the preview field in the fields
|
||||
* @param ctFields
|
||||
* @param parents
|
||||
* @returns
|
||||
*/
|
||||
private static findPreviewField(ctFields: Field[], parents: string[] = []): string[] {
|
||||
for (const field of ctFields) {
|
||||
if (field.isPreviewImage && field.type === "image") {
|
||||
parents = [...parents, field.name];
|
||||
return parents;
|
||||
} else if (field.type === "fields" && field.fields) {
|
||||
const subFields = this.findPreviewField(field.fields);
|
||||
if (subFields.length > 0) {
|
||||
return [...parents, field.name, ...subFields];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../constants";
|
||||
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
|
||||
import { DashboardSettings, Settings } from "../helpers";
|
||||
import { FrameworkDetector } from "../helpers/FrameworkDetector";
|
||||
import { Framework } from "../models";
|
||||
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../../constants";
|
||||
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { DashboardSettings, Settings } from "../../helpers";
|
||||
import { FrameworkDetector } from "../../helpers/FrameworkDetector";
|
||||
import { Framework } from "../../models";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
21
src/listeners/dashboard/TelemetryListener.ts
Normal file
21
src/listeners/dashboard/TelemetryListener.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
|
||||
import { Telemetry } from "../../helpers/Telemetry";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class TelemetryListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: DashboardMessage, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case DashboardMessage.sendTelemetry:
|
||||
Telemetry.send(msg.data.event, msg.data.properties, msg.data.metrics);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './DashboardListener';
|
||||
export * from './DataListener';
|
||||
export * from './ExtensionListener';
|
||||
export * from './MediaListener';
|
||||
export * from './PagesListener';
|
||||
export * from './SettingsListener';
|
||||
export * from './TelemetryListener';
|
||||
27
src/listeners/panel/ArticleListener.ts
Normal file
27
src/listeners/panel/ArticleListener.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Article } from "../../commands";
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class ArticleListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case CommandToCode.updateSlug:
|
||||
Article.generateSlug();
|
||||
break;
|
||||
case CommandToCode.updateLastMod:
|
||||
Article.setLastModifiedDate();
|
||||
break;
|
||||
case CommandToCode.publish:
|
||||
Article.toggleDraft();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/listeners/panel/BaseListener.ts
Normal file
28
src/listeners/panel/BaseListener.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Extension } from './../../helpers/Extension';
|
||||
import { ExplorerView } from './../../explorerView/ExplorerView';
|
||||
import { Logger } from "../../helpers";
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
|
||||
|
||||
export abstract class BaseListener {
|
||||
|
||||
public static process(msg: { command: CommandToCode, data: any }) {}
|
||||
|
||||
/**
|
||||
* Send a message to the webview
|
||||
* @param command
|
||||
* @param data
|
||||
*/
|
||||
public static sendMsg(command: Command, data: any) {
|
||||
Logger.info(`Sending message to panel: ${command}`);
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = ExplorerView.getInstance(extPath);
|
||||
|
||||
panel.sendMessage({
|
||||
command,
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
||||
292
src/listeners/panel/DataListener.ts
Normal file
292
src/listeners/panel/DataListener.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import { BlockFieldData } from './../../models/BlockFieldData';
|
||||
import { ImageHelper } from './../../helpers/ImageHelper';
|
||||
import { Folders } from "../../commands/Folders";
|
||||
import { Command } from "../../panelWebView/Command";
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { commands, ThemeIcon, window } from 'vscode';
|
||||
import { ArticleHelper, Logger, Settings } from "../../helpers";
|
||||
import { COMMAND_NAME, DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES } from "../../constants";
|
||||
import { Article } from '../../commands';
|
||||
import { ParsedFrontMatter } from '../../parsers';
|
||||
|
||||
const FILE_LIMIT = 10;
|
||||
|
||||
export class DataListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: CommandToCode, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case CommandToCode.getData:
|
||||
this.getFoldersAndFiles();
|
||||
this.getFileData();
|
||||
break;
|
||||
case CommandToCode.createContent:
|
||||
commands.executeCommand(COMMAND_NAME.createContent);
|
||||
break;
|
||||
case CommandToCode.createTemplate:
|
||||
commands.executeCommand(COMMAND_NAME.createTemplate);
|
||||
break;
|
||||
case CommandToCode.updateMetadata:
|
||||
this.updateMetadata(msg.data);
|
||||
break;
|
||||
case CommandToCode.frameworkCommand:
|
||||
this.openTerminalWithCommand(msg.data.command);
|
||||
break;
|
||||
case CommandToCode.updatePlaceholder:
|
||||
this.updatePlaceholder(msg?.data?.field, msg?.data?.value, msg?.data?.title);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the information about the registered folders and its files
|
||||
*/
|
||||
public static async getFoldersAndFiles() {
|
||||
const folders = await Folders.getInfo(FILE_LIMIT) || null;
|
||||
|
||||
this.sendMsg(Command.folderInfo, folders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a metadata change in the panel
|
||||
* @param metadata
|
||||
*/
|
||||
public static pushMetadata(metadata: any) {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
const filePath = window.activeTextEditor?.document.uri.fsPath;
|
||||
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
|
||||
const contentTypes = Settings.get<string>(SETTING_TAXONOMY_CONTENT_TYPES);
|
||||
|
||||
let articleDetails = null;
|
||||
|
||||
try {
|
||||
articleDetails = ArticleHelper.getDetails();
|
||||
} catch (e) {
|
||||
Logger.error(`DataListener::pushMetadata: ${(e as Error).message}`);
|
||||
}
|
||||
|
||||
if (articleDetails) {
|
||||
metadata.articleDetails = articleDetails;
|
||||
}
|
||||
|
||||
let updatedMetadata = Object.assign({}, metadata);
|
||||
if (commaSeparated) {
|
||||
for (const key of commaSeparated) {
|
||||
if (updatedMetadata[key] && typeof updatedMetadata[key] === "string") {
|
||||
updatedMetadata[key] = updatedMetadata[key].split(",").map((s: string) => s.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const keys = Object.keys(updatedMetadata);
|
||||
if (keys.length > 0) {
|
||||
updatedMetadata.filePath = filePath;
|
||||
}
|
||||
|
||||
if (keys.length > 0 && contentTypes && wsFolder) {
|
||||
// Get the current content type
|
||||
const contentType = ArticleHelper.getContentType(updatedMetadata);
|
||||
if (contentType) {
|
||||
ImageHelper.processImageFields(updatedMetadata, contentType.fields)
|
||||
}
|
||||
}
|
||||
|
||||
// Check slug
|
||||
if (!updatedMetadata[DefaultFields.Slug]) {
|
||||
const slug = Article.getSlug();
|
||||
|
||||
if (slug) {
|
||||
updatedMetadata[DefaultFields.Slug] = slug;
|
||||
}
|
||||
}
|
||||
|
||||
this.sendMsg(Command.metadata, updatedMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metadata of the article
|
||||
*/
|
||||
public static async updateMetadata({
|
||||
field,
|
||||
parents,
|
||||
value,
|
||||
blockData
|
||||
}: {
|
||||
field: string,
|
||||
value: any,
|
||||
parents?: string[],
|
||||
blockData?: BlockFieldData,
|
||||
fieldData?: { multiple: boolean, value: string[] }
|
||||
}) {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
const dateFields = contentType.fields.filter((f) => f.type === "datetime");
|
||||
const imageFields = contentType.fields.filter((f) => f.type === "image" && f.multiple);
|
||||
|
||||
// Support multi-level fields
|
||||
const parentObj = DataListener.getParentObject(article.data, article, parents, blockData);
|
||||
|
||||
for (const dateField of dateFields) {
|
||||
if ((field === dateField.name) && 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
|
||||
parentObj[field] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const imageField of imageFields) {
|
||||
if (field === imageField.name) {
|
||||
// If value is an array, it means it comes from the explorer view itself (deletion)
|
||||
if (Array.isArray(value)) {
|
||||
parentObj[field] = value || [];
|
||||
} else { // Otherwise it is coming from the media dashboard (addition)
|
||||
let fieldValue = parentObj[field];
|
||||
if (fieldValue && !Array.isArray(fieldValue)) {
|
||||
fieldValue = [fieldValue];
|
||||
}
|
||||
const crntData = Object.assign([], fieldValue);
|
||||
const allRelPaths = [...(crntData || []), value];
|
||||
parentObj[field] = [...new Set(allRelPaths)].filter(f => f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArticleHelper.update(editor, article);
|
||||
this.pushMetadata(article.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the parent object to update
|
||||
* @param data
|
||||
* @param article
|
||||
* @param parents
|
||||
* @param blockData
|
||||
* @returns
|
||||
*/
|
||||
public static getParentObject(data: any, article: ParsedFrontMatter, parents: string[] | undefined, blockData?: BlockFieldData) {
|
||||
let parentObj = data;
|
||||
let allParents = Object.assign([], parents);
|
||||
const contentType = ArticleHelper.getContentType(article.data);
|
||||
|
||||
// Add support for block fields
|
||||
if (blockData?.parentFields) {
|
||||
let crntField = null;
|
||||
parentObj = article.data;
|
||||
|
||||
// Loop through the parents of the block field
|
||||
for (const parent of blockData?.parentFields) {
|
||||
if (!parentObj[parent]) {
|
||||
parentObj[parent] = {};
|
||||
}
|
||||
|
||||
if (allParents[0] && allParents[0] === parent) {
|
||||
allParents.shift();
|
||||
}
|
||||
|
||||
parentObj = parentObj[parent];
|
||||
crntField = contentType.fields.find(f => f.name === parent);
|
||||
}
|
||||
|
||||
// Fetches the current block
|
||||
if (blockData && crntField && crntField.type === 'block') {
|
||||
if (typeof blockData.selectedIndex !== 'undefined') {
|
||||
parentObj = parentObj[blockData.selectedIndex];
|
||||
} else {
|
||||
parentObj.push({
|
||||
fieldGroup: blockData.blockType
|
||||
});
|
||||
parentObj = parentObj[parentObj.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are parents left
|
||||
if (allParents.length > 0) {
|
||||
for (const parent of allParents) {
|
||||
if (!parentObj[parent]) {
|
||||
parentObj[parent] = {};
|
||||
}
|
||||
|
||||
parentObj = parentObj[parent];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const parent of parents || []) {
|
||||
// If parent doesn't yet exists, it needs to be created
|
||||
if (!parentObj[parent]) {
|
||||
parentObj[parent] = {};
|
||||
}
|
||||
|
||||
parentObj = parentObj[parent];
|
||||
}
|
||||
}
|
||||
|
||||
return parentObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the file its front matter
|
||||
*/
|
||||
public static async getFileData() {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article?.data) {
|
||||
this.pushMetadata(article!.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a terminal and run the passed command
|
||||
* @param command
|
||||
*/
|
||||
private static openTerminalWithCommand(command: string) {
|
||||
if (command) {
|
||||
let terminal = window.activeTerminal;
|
||||
|
||||
if (!terminal || (terminal && terminal.state.isInteractedWith === true)) {
|
||||
terminal = window.createTerminal({
|
||||
name: `Starting local server`,
|
||||
iconPath: new ThemeIcon('server-environment'),
|
||||
message: `Starting local server`,
|
||||
});
|
||||
}
|
||||
|
||||
if (terminal) {
|
||||
terminal.sendText(command);
|
||||
terminal.show(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static updatePlaceholder(field: string, value: string, title: string) {
|
||||
if (field && value) {
|
||||
value = ArticleHelper.processKnownPlaceholders(value, title || "");
|
||||
value = ArticleHelper.processCustomPlaceholders(value, title || "");
|
||||
}
|
||||
|
||||
this.sendMsg(Command.updatePlaceholder, { field, value });
|
||||
}
|
||||
}
|
||||
83
src/listeners/panel/ExtensionListener.ts
Normal file
83
src/listeners/panel/ExtensionListener.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { commands, env as vscodeEnv } from 'vscode';
|
||||
import * as os from 'os';
|
||||
import { exec } from 'child_process';
|
||||
import { Folders } from "../../commands/Folders";
|
||||
import { COMMAND_NAME } from "../../constants";
|
||||
import { SettingsListener } from ".";
|
||||
import { openFileInEditor } from "../../helpers";
|
||||
|
||||
|
||||
export class ExtensionListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case CommandToCode.openFile:
|
||||
this.openFile();
|
||||
break;
|
||||
case CommandToCode.openProject:
|
||||
this.openFolder();
|
||||
break;
|
||||
case CommandToCode.openInEditor:
|
||||
openFileInEditor(msg.data);
|
||||
break;
|
||||
case CommandToCode.initProject:
|
||||
this.initialize();
|
||||
break;
|
||||
case CommandToCode.toggleCenterMode:
|
||||
commands.executeCommand(`workbench.action.toggleCenteredLayout`);
|
||||
break;
|
||||
case CommandToCode.openPreview:
|
||||
commands.executeCommand(COMMAND_NAME.preview);
|
||||
break;
|
||||
case CommandToCode.openDashboard:
|
||||
commands.executeCommand(COMMAND_NAME.dashboard);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize project
|
||||
*/
|
||||
private static async initialize() {
|
||||
await commands.executeCommand(COMMAND_NAME.init);
|
||||
SettingsListener.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the file in your explorer
|
||||
*/
|
||||
private static openFile() {
|
||||
if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
|
||||
commands.executeCommand('remote-wsl.revealInExplorer');
|
||||
} else {
|
||||
commands.executeCommand('revealFileInOS');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the project folder
|
||||
*/
|
||||
private static openFolder() {
|
||||
const wsFolder = Folders.getWorkspaceFolder();
|
||||
if (wsFolder) {
|
||||
const wsPath = wsFolder.fsPath;
|
||||
if (os.type() === "Darwin") {
|
||||
exec(`open ${wsPath}`);
|
||||
} else if (os.type() === "Windows_NT") {
|
||||
exec(`explorer ${wsPath}`);
|
||||
} else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
|
||||
exec('explorer.exe `wslpath -w "$PWD"`');
|
||||
} else {
|
||||
exec(`xdg-open ${wsPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/listeners/panel/MediaListener.ts
Normal file
63
src/listeners/panel/MediaListener.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { ExplorerView } from './../../explorerView/ExplorerView';
|
||||
import { commands, window } from "vscode";
|
||||
import { Dashboard } from "../../commands/Dashboard";
|
||||
import { COMMAND_NAME } from "../../constants";
|
||||
import { ImageHelper } from "../../helpers";
|
||||
import { DashboardData } from "../../models";
|
||||
import { Command } from "../../panelWebView/Command";
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class MediaListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case CommandToCode.selectImage:
|
||||
this.selectMedia(msg);
|
||||
break;
|
||||
case CommandToCode.getImageUrl:
|
||||
this.generateUrl(msg.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static generateUrl(data: string) {
|
||||
const filePath = window.activeTextEditor?.document.uri.fsPath;
|
||||
|
||||
const imgUrl = ImageHelper.relToAbs(filePath || "", data);
|
||||
if (imgUrl) {
|
||||
const viewUrl = ExplorerView.getInstance().getWebview()?.asWebviewUri(imgUrl);
|
||||
if (viewUrl) {
|
||||
this.sendMsg(Command.sendMediaUrl, {
|
||||
original: data,
|
||||
url: viewUrl.toString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a media file
|
||||
*/
|
||||
private static async selectMedia(msg: { data: any }) {
|
||||
await commands.executeCommand(COMMAND_NAME.dashboard, {
|
||||
type: "media",
|
||||
data: msg.data
|
||||
} as DashboardData);
|
||||
this.getMediaSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the media selection
|
||||
*/
|
||||
public static async getMediaSelection() {
|
||||
this.sendMsg(Command.mediaSelectionData, Dashboard.viewData);
|
||||
}
|
||||
}
|
||||
38
src/listeners/panel/ScriptListener.ts
Normal file
38
src/listeners/panel/ScriptListener.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { SETTING_CUSTOM_SCRIPTS } from "../../constants";
|
||||
import { CustomScript, Settings } from "../../helpers";
|
||||
import { CustomScript as ICustomScript } from "../../models";
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class ScriptListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case CommandToCode.runCustomScript:
|
||||
this.runCustomScript(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a custom script
|
||||
* @param msg
|
||||
*/
|
||||
private static runCustomScript(msg: { command: string, data: any}) {
|
||||
const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS);
|
||||
|
||||
if (msg?.data?.title && msg?.data?.script && scripts) {
|
||||
const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title);
|
||||
if (customScript?.script && customScript?.title) {
|
||||
CustomScript.run(customScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/listeners/panel/SettingsListener.ts
Normal file
87
src/listeners/panel/SettingsListener.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { commands, workspace } from "vscode";
|
||||
import { EXTENSION_BETA_ID, EXTENSION_ID, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_PREVIEW_HOST } from "../../constants";
|
||||
import { Extension, Settings } from "../../helpers";
|
||||
import { PanelSettings } from "../../helpers/PanelSettings";
|
||||
import { Command } from "../../panelWebView/Command";
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
|
||||
|
||||
export class SettingsListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: CommandToCode, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case CommandToCode.getData:
|
||||
this.getSettings();
|
||||
break;
|
||||
case CommandToCode.openSettings:
|
||||
this.openVSCodeSettings();
|
||||
break;
|
||||
case CommandToCode.toggleWritingSettings:
|
||||
this.toggleWritingSettings();
|
||||
break;
|
||||
case CommandToCode.updateModifiedUpdating:
|
||||
this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false);
|
||||
break;
|
||||
case CommandToCode.updateFmHighlight:
|
||||
this.updateSetting(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false);
|
||||
break;
|
||||
case CommandToCode.updatePreviewUrl:
|
||||
this.updateSetting(SETTING_PREVIEW_HOST, msg.data || "");
|
||||
break;
|
||||
case CommandToCode.updateStartCommand:
|
||||
this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the extension settings required to render the panel
|
||||
*/
|
||||
public static async getSettings() {
|
||||
const panelSettings = await PanelSettings.get();
|
||||
this.sendMsg(Command.settings, panelSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the settings view of VS Code
|
||||
*/
|
||||
public static openVSCodeSettings() {
|
||||
const isBeta = Extension.getInstance().isBetaVersion();
|
||||
commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a setting and refreshes the retrieved settings
|
||||
* @param setting
|
||||
* @param value
|
||||
*/
|
||||
private static async updateSetting(setting: string, value: any) {
|
||||
await Settings.update(setting, value);
|
||||
this.getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the writing settings
|
||||
*/
|
||||
private static async toggleWritingSettings() {
|
||||
const config = workspace.getConfiguration("", { languageId: "markdown" });
|
||||
const enabled = PanelSettings.isWritingSettingsEnabled();
|
||||
|
||||
await config.update("editor.fontSize", enabled ? undefined : 14, false, true);
|
||||
await config.update("editor.lineHeight", enabled ? undefined : 26, false, true);
|
||||
await config.update("editor.wordWrap", enabled ? undefined : "wordWrapColumn", false, true);
|
||||
await config.update("editor.wordWrapColumn", enabled ? undefined : 64, false, true);
|
||||
await config.update("editor.lineNumbers", enabled ? undefined : "off", false, true);
|
||||
await config.update("editor.quickSuggestions", enabled ? undefined : false, false, true);
|
||||
await config.update("editor.minimap.enabled", enabled ? undefined : false, false, true);
|
||||
|
||||
this.getSettings();
|
||||
}
|
||||
}
|
||||
122
src/listeners/panel/TaxonomyListener.ts
Normal file
122
src/listeners/panel/TaxonomyListener.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { CommandToCode } from "../../panelWebView/CommandToCode";
|
||||
import { TagType } from "../../panelWebView/TagType";
|
||||
import { BaseListener } from "./BaseListener";
|
||||
import { window } from "vscode";
|
||||
import { ArticleHelper, Settings } from "../../helpers";
|
||||
import { BlockFieldData, CustomTaxonomyData, TaxonomyType } from "../../models";
|
||||
import { DataListener } from ".";
|
||||
import { SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../../constants";
|
||||
|
||||
|
||||
export class TaxonomyListener extends BaseListener {
|
||||
|
||||
/**
|
||||
* Process the messages for the dashboard views
|
||||
* @param msg
|
||||
*/
|
||||
public static process(msg: { command: any, data: any }) {
|
||||
super.process(msg);
|
||||
|
||||
switch(msg.command) {
|
||||
case CommandToCode.updateTags:
|
||||
this.updateTags(msg.data?.fieldName, msg.data?.values || [], msg.data?.parents || [], msg.data?.blockData);
|
||||
break;
|
||||
case CommandToCode.updateCategories:
|
||||
this.updateTags(msg.data?.fieldName, msg.data?.values || [], msg.data?.parents || [], msg.data?.blockData);
|
||||
break;
|
||||
case CommandToCode.updateKeywords:
|
||||
this.updateTags(TagType.keywords.toLowerCase(), msg.data?.values || [], msg.data?.parents || [], msg.data?.blockData);
|
||||
break;
|
||||
case CommandToCode.updateCustomTaxonomy:
|
||||
this.updateCustomTaxonomy(msg.data);
|
||||
break;
|
||||
case CommandToCode.addTagToSettings:
|
||||
this.addTags(TagType.tags, msg.data);
|
||||
break;
|
||||
case CommandToCode.addCategoryToSettings:
|
||||
this.addTags(TagType.categories, msg.data);
|
||||
break;
|
||||
case CommandToCode.addToCustomTaxonomy:
|
||||
this.addCustomTaxonomy(msg.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tags in the current document
|
||||
* @param tagType
|
||||
* @param values
|
||||
*/
|
||||
private static updateTags(fieldName: string, values: string[], parents: string[], blockData?: BlockFieldData) {
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article && article.data) {
|
||||
|
||||
const parentObj = DataListener.getParentObject(article.data, article, parents, blockData);
|
||||
|
||||
parentObj[fieldName] = values || [];
|
||||
ArticleHelper.update(editor, article);
|
||||
DataListener.pushMetadata(article!.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tags in the current document
|
||||
* @param data
|
||||
*/
|
||||
private static updateCustomTaxonomy(data: CustomTaxonomyData) {
|
||||
if (!data?.id || !data?.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getFrontMatter(editor);
|
||||
if (article && article.data) {
|
||||
|
||||
const parentObj = DataListener.getParentObject(article.data, article, data.parents, data.blockData);
|
||||
|
||||
parentObj[data.name] = data.options || [];
|
||||
ArticleHelper.update(editor, article);
|
||||
DataListener.pushMetadata(article!.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tag to the settings
|
||||
* @param data
|
||||
*/
|
||||
private static async addCustomTaxonomy(data: CustomTaxonomyData) {
|
||||
if (!data?.id || !data?.option) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Settings.updateCustomTaxonomy(data.id, data.option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tag to the settings
|
||||
* @param tagType
|
||||
* @param value
|
||||
*/
|
||||
private static async addTags(tagType: TagType, value: string) {
|
||||
if (value) {
|
||||
let options = tagType === TagType.tags ? Settings.get<string[]>(SETTING_TAXONOMY_TAGS, true) : Settings.get<string[]>(SETTING_TAXONOMY_CATEGORIES, true);
|
||||
|
||||
if (!options) {
|
||||
options = [];
|
||||
}
|
||||
|
||||
options.push(value);
|
||||
const taxType = tagType === TagType.tags ? TaxonomyType.Tag : TaxonomyType.Category;
|
||||
await Settings.updateTaxonomy(taxType, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/listeners/panel/index.ts
Normal file
8
src/listeners/panel/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './ArticleListener';
|
||||
export * from './BaseListener';
|
||||
export * from './DataListener';
|
||||
export * from './ExtensionListener';
|
||||
export * from './MediaListener';
|
||||
export * from './ScriptListener';
|
||||
export * from './SettingsListener';
|
||||
export * from './TaxonomyListener';
|
||||
7
src/models/BlockFieldData.ts
Normal file
7
src/models/BlockFieldData.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export interface BlockFieldData {
|
||||
parentFields: string[] | undefined;
|
||||
blockType: string | undefined;
|
||||
selectedIndex: number | undefined;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BlockFieldData } from './BlockFieldData';
|
||||
|
||||
export interface CustomTaxonomyData {
|
||||
id: string | undefined;
|
||||
@@ -5,4 +6,5 @@ export interface CustomTaxonomyData {
|
||||
options?: string[] | undefined;
|
||||
option?: string | undefined;
|
||||
parents?: string[];
|
||||
blockData?: BlockFieldData;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { FileStat } from "vscode";
|
||||
import { DraftField } from ".";
|
||||
import { Choice } from "./Choice";
|
||||
import { DashboardData } from "./DashboardData";
|
||||
import { DataType } from "./DataType";
|
||||
|
||||
export interface PanelSettings {
|
||||
seo: SEO;
|
||||
@@ -10,19 +11,28 @@ export interface PanelSettings {
|
||||
date: DateInfo;
|
||||
categories: string[];
|
||||
customTaxonomy: CustomTaxonomy[];
|
||||
freeform: boolean;
|
||||
freeform: boolean | undefined;
|
||||
scripts: CustomScript[];
|
||||
isInitialized: boolean;
|
||||
modifiedDateUpdate: boolean;
|
||||
writingSettingsEnabled: boolean;
|
||||
fmHighlighting: boolean;
|
||||
fmHighlighting: boolean | undefined;
|
||||
preview: PreviewSettings;
|
||||
contentTypes: ContentType[];
|
||||
dashboardViewData: DashboardData | undefined;
|
||||
draftField: DraftField;
|
||||
draftField: DraftField | undefined;
|
||||
isBacker: boolean | undefined;
|
||||
framework: string | undefined;
|
||||
commands: FrameworkCommands;
|
||||
dataTypes: DataType[] | undefined;
|
||||
fieldGroups: FieldGroup[] | undefined;
|
||||
commaSeparatedFields: string[];
|
||||
}
|
||||
|
||||
export interface FieldGroup {
|
||||
id: string;
|
||||
labelField?: string;
|
||||
fields: Field[];
|
||||
}
|
||||
|
||||
export interface FrameworkCommands {
|
||||
@@ -38,10 +48,12 @@ export interface ContentType {
|
||||
pageBundle?: boolean;
|
||||
}
|
||||
|
||||
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block";
|
||||
|
||||
export interface Field {
|
||||
title?: string;
|
||||
name: string;
|
||||
type: "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields";
|
||||
type: FieldType;
|
||||
choices?: string[] | Choice[];
|
||||
single?: boolean;
|
||||
multiple?: boolean;
|
||||
@@ -50,6 +62,9 @@ export interface Field {
|
||||
taxonomyId?: string;
|
||||
default?: string;
|
||||
fields?: Field[];
|
||||
fieldGroup?: string | string[];
|
||||
dataType?: string | string[];
|
||||
taxonomyLimit?: number;
|
||||
}
|
||||
|
||||
export interface DateInfo {
|
||||
@@ -65,8 +80,9 @@ export interface SEO {
|
||||
}
|
||||
|
||||
export interface Slug {
|
||||
prefix: number;
|
||||
suffix: number;
|
||||
prefix: number | string;
|
||||
suffix: number | string;
|
||||
updateFileName?: boolean;
|
||||
}
|
||||
|
||||
export interface FolderInfo {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
export * from './BlockFieldData';
|
||||
export * from './Choice';
|
||||
export * from './ContentFolder';
|
||||
export * from './CustomTaxonomyData';
|
||||
export * from './DashboardData';
|
||||
export * from './DataFile';
|
||||
export * from './DataFolder';
|
||||
export * from './DataType';
|
||||
export * from './DraftField';
|
||||
export * from './Framework';
|
||||
export * from './MediaPaths';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user