Compare commits

...

34 Commits

Author SHA1 Message Date
Elio Struyf a6fdfe0dfa Merge pull request #342 from estruyf/dev 2022-05-25 13:13:50 +02:00
Elio Struyf 6154164b1c Updated changelog 2022-05-25 13:09:16 +02:00
Elio Struyf 1cdb6c56a5 #336 - Fix for updating status field 2022-05-25 13:08:39 +02:00
Elio Struyf c63310a2db Initialize template folder 2022-05-25 12:51:39 +02:00
Elio Struyf 9f681a7459 Remove template creation 2022-05-25 11:46:18 +02:00
Elio Struyf 2610032a38 Update for the dataFile field 2022-05-25 10:12:05 +02:00
Elio Struyf d11e8112e0 Fix for data view 2022-05-25 10:09:21 +02:00
Elio Struyf df5e346cf1 Changelog update 2022-05-24 12:03:30 +02:00
Elio Struyf 9892d14a62 default labels for issues 2022-05-20 16:55:51 +02:00
Elio Struyf 8c61f79885 #340 - Show notification for not existing content folders 2022-05-20 16:46:39 +02:00
Elio Struyf ffa6638d3d #339 - Fix for content folders without a title 2022-05-20 16:25:08 +02:00
Elio Struyf f9ef12bd3a #338 - Disable templates setting 2022-05-20 16:22:46 +02:00
Elio Struyf b79216f2b5 Added project label flow 2022-05-18 16:52:43 +02:00
Elio Struyf 2597a63718 Updated workflow 2022-05-18 14:13:07 +02:00
Elio Struyf 126a21a6b5 Added github action info 2022-05-18 13:58:50 +02:00
Elio Struyf 3abfbd5302 Message handler updates 2022-05-18 13:20:06 +02:00
Elio Struyf efdbce2d08 #332 - Adding the new fileData field 2022-05-18 13:19:55 +02:00
Elio Struyf 09eea16d60 Remove logging 2022-05-18 10:29:23 +02:00
Elio Struyf 71697a09b6 Update dependency version 2022-05-17 16:23:23 +02:00
Elio Struyf 3583a2b962 #337 - Add support for other fm types 2022-05-17 16:19:40 +02:00
Elio Struyf 2825d5ddd8 #336 - support for draft field invert 2022-05-13 09:01:30 +02:00
Elio Struyf 2e66174c4a Setting focus for questions 2022-05-13 08:26:30 +02:00
Elio Struyf e78069ad17 #333 - collection and published field support 2022-05-13 08:18:42 +02:00
Elio Struyf 4c97993c5f #333 - better support for Jekyll 2022-05-12 20:53:47 +02:00
Elio Struyf a452173d9a Added icons to snippets 2022-05-12 17:59:17 +02:00
Elio Struyf 60a38be923 #335 - Merge media snippets to content snippets 2022-05-12 15:51:50 +02:00
Elio Struyf 6c3d286282 #331 - Added functionality to run other type of scripts 2022-05-10 16:26:59 +02:00
Elio Struyf 32c7bbd3f9 #334 - Fix for locked content folders retrieval 2022-05-09 09:00:36 +02:00
Elio Struyf 426dbc2e46 Update beta script 2022-05-08 20:02:35 +02:00
Elio Struyf 9882dea960 Update beta release script 2022-05-08 19:52:31 +02:00
Elio Struyf eb9a05e90c update beta release script 2022-05-08 18:18:14 +02:00
Elio Struyf 4e59e736ed Parse windows path 2022-05-08 18:09:19 +02:00
Elio Struyf 9f91ebf289 7.3.0 2022-05-05 17:41:32 +02:00
Elio Struyf b80de402bd #330 - Update front matter from script 2022-05-05 17:41:25 +02:00
79 changed files with 1428 additions and 8907 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: 'Issue: '
labels: ''
labels: 'bug'
assignees: ''
---
+1 -1
View File
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: 'Enhancement: '
labels: ''
labels: 'enhancement'
assignees: ''
---
+27
View File
@@ -0,0 +1,27 @@
name: Project labelling
on:
project_card:
types: [created, moved, deleted]
jobs:
automate-issues-labels:
runs-on: ubuntu-latest
steps:
- name: Fetch project data
run: |
echo 'PROJECT_DATA<<EOF' >> $GITHUB_ENV
curl --request GET --url '${{ github.event.project_card.project_url }}' --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Add the project label
uses: andymckay/labeler@master
if: ${{ contains(github.event.action, 'created') || contains(github.event.action, 'moved') }}
with:
add-labels: "Project: ${{ fromJSON(env.PROJECT_DATA).name }}"
- name: Remove the project label
uses: andymckay/labeler@master
if: ${{ contains(github.event.action, 'deleted') }}
with:
remove-labels: "Project: ${{ fromJSON(env.PROJECT_DATA).name }}"
+2 -1
View File
@@ -2,6 +2,7 @@
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"ms-vscode.vscode-typescript-tslint-plugin"
"ms-vscode.vscode-typescript-tslint-plugin",
"eliostruyf.vscode-typescript-exportallmodules"
]
}
+21
View File
@@ -1,5 +1,26 @@
# Change Log
## [7.3.0] - 2022-05-25 - [Release notes](https://beta.frontmatter.codes/updates/v7.3.0)
### 🎨 Enhancements
- JSON schema enhancements for working with data files
- [#330](https://github.com/estruyf/vscode-front-matter/issues/330): Allow custom scripts to easily update front matter
- [#331](https://github.com/estruyf/vscode-front-matter/issues/331): Added functionality to run other type of scripts
- [#332](https://github.com/estruyf/vscode-front-matter/issues/332): New `dataFile` field which allows you to create data file references
- [#333](https://github.com/estruyf/vscode-front-matter/issues/333): Automatically mark Jekyll posts in `_drafts` folder as draft
- [#335](https://github.com/estruyf/vscode-front-matter/issues/335): Merge media snippets with content snippets to allow you to define multiple media snippets and use these in your content
- [#336](https://github.com/estruyf/vscode-front-matter/issues/336): Support added for inverting the draft field so that SSGs/authors can use a published field instead
- [#337](https://github.com/estruyf/vscode-front-matter/issues/337): Allow multiple front matter types to be used
- [#338](https://github.com/estruyf/vscode-front-matter/issues/338): Ability to disable the templates functionality (default is disabled)
- [#340](https://github.com/estruyf/vscode-front-matter/issues/340): Show an error message when there is a content folder registered that does not exist in the project
### 🐞 Fixes
- [#334](https://github.com/estruyf/vscode-front-matter/issues/334): Fix for locked content folders retrieval
- [#339](https://github.com/estruyf/vscode-front-matter/issues/339): Fix for content folders without a title
## [7.2.0] - 2022-05-02 - [Release notes](https://beta.frontmatter.codes/updates/v7.2.0)
### 🎨 Enhancements
+22
View File
@@ -50,6 +50,28 @@
],
"openingTags": "{{",
"closingTags": "}}"
},
"Issue link": {
"description": "Link to a GitHub issue",
"body": "- [#{{id}}](https://github.com/estruyf/vscode-front-matter/issues/{{id}}): {{title}}",
"fields": [
{
"name": "id",
"title": "Issue ID",
"type": "string",
"single": true,
"default": ""
},
{
"name": "title",
"title": "Title",
"type": "string",
"single": true,
"default": ""
}
],
"openingTags": "{{",
"closingTags": "}}"
}
}
}
+53 -8557
View File
File diff suppressed because it is too large Load Diff
+114 -19
View File
@@ -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": "7.2.0",
"version": "7.3.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -137,6 +137,10 @@
"type": "string",
"description": "Name of the field to use"
},
"invert": {
"type": "boolean",
"description": "By default the draft field is set to true when the content is a draft. Set this to true to set it to false."
},
"choices": {
"type": "array",
"description": "List of choices for the field",
@@ -225,8 +229,7 @@
"additionalProperties": {
"type": "object",
"required": [
"body",
"fields"
"body"
],
"properties": {
"body": {
@@ -255,6 +258,11 @@
"description": "The snippet closing tags.",
"type": "string",
"default": "]]"
},
"isMediaSnippet": {
"description": "Specify if the snippet is to be used for media files.",
"type": "boolean",
"default": false
}
},
"additionalProperties": false
@@ -342,7 +350,7 @@
},
"nodeBin": {
"type": "string",
"description": "Path to the node executable. This is required when using NVM, so that there is no confusion of which node version to use."
"description": "Path to the node executable. This is required when using NVM, so that there is no confusion of which node version to use. (deprecated: use the command property instead)"
},
"bulk": {
"type": "boolean",
@@ -369,6 +377,25 @@
"mediaFile"
],
"description": "The type for which the script will be used."
},
"command": {
"type": "string",
"oneOf": [
{
"enum": [
"node",
"bash",
"powershell",
"python",
"python3"
]
},
{
"type": "string"
}
],
"description": "The type of script you want to execute.",
"default": "node"
}
},
"additionalProperties": false,
@@ -389,6 +416,7 @@
"type": "array",
"default": [],
"markdownDescription": "Specify the a snippet for your custom media insert markup. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.mediasnippet)",
"deprecationMessage": "This setting is deprecated and will be removed in the next major version. Please define your media snippet in the `frontMatter.content.snippet` setting.",
"items": {
"type": "string",
"description": "Use the `{mediaUrl}`, `{caption}`, `{alt}`, `{filename}`, `{mediaHeight}`, and `{mediaWidth}` placeholders in your snippet to automatically insert the media information."
@@ -426,7 +454,8 @@
},
"file": {
"type": "string",
"description": "Path to the file to load. Only JSON or YAML files are supported."
"description": "Path to the file to load. Only JSON or YAML files are supported.",
"default": "[[workspace]]/"
},
"fileType": {
"type": "string",
@@ -438,10 +467,38 @@
"description": "Defines how you want to parse the file. JSON is the default."
},
"schema": {
"$id": "#dataFileSchema",
"type": "object",
"default": {},
"description": "The JSON schema for your data which will be used to render the data form.",
"additionalProperties": true
"additionalProperties": true,
"required": [
"type",
"properties"
],
"properties": {
"title": {
"type": "string",
"description": "Title of the form."
},
"type": {
"type": "string",
"description": "Defines the type of the form. Default is 'object'.",
"default": "object"
},
"required": {
"type": "array",
"description": "Defines the required fields for the form.",
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"description": "Defines the fields of the form.",
"additionalProperties": true
}
}
},
"type": {
"type": "string",
@@ -488,13 +545,11 @@
},
"path": {
"type": "string",
"description": "Path to the folder to load files."
"description": "Path to the folder to load files.",
"default": "[[workspace]]/"
},
"schema": {
"type": "object",
"default": {},
"description": "The JSON schema for your data which will be used to render the data form.",
"additionalProperties": true
"$ref": "#dataFileSchema"
},
"type": {
"type": "string",
@@ -535,10 +590,7 @@
"description": "Your unique ID you want to use for your data type."
},
"schema": {
"type": "object",
"default": {},
"description": "The JSON schema for your data which will be used to render the data form.",
"additionalProperties": true
"$ref": "#dataFileSchema"
}
},
"required": [
@@ -753,7 +805,8 @@
"draft",
"fields",
"json",
"block"
"block",
"dataFile"
],
"description": "Define the type of field"
},
@@ -875,6 +928,21 @@
"type": "boolean",
"default": false,
"description": "Specify if the field is the modified date field"
},
"dataFileId": {
"type": "string",
"default": "",
"description": "Specify the ID of the data file to use for this field"
},
"dataFileKey": {
"type": "string",
"default": "",
"description": "Specify the key of the data file to use for this field"
},
"dataFileValue": {
"type": "string",
"default": "",
"description": "Specify the property name that will be used to show the value for the field"
}
},
"additionalProperties": false,
@@ -883,6 +951,21 @@
"name"
],
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "dataFile"
}
}
},
"then": {
"required": [
"dataFileId",
"dataFileKey"
]
}
},
{
"if": {
"properties": {
@@ -1198,6 +1281,12 @@
"default": "yyyy-MM-dd",
"markdownDescription": "Specify the prefix you want to add for your new article filenames. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.templates.prefix)",
"scope": "Templates"
},
"frontMatter.templates.enabled": {
"type": "boolean",
"default": false,
"markdownDescription": "Specify if you want to use templates. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.templates.enabled)",
"scope": "Templates"
}
}
},
@@ -1276,9 +1365,14 @@
"dark": "assets/icons/close-dark.svg"
}
},
{
"command": "frontMatter.initTemplate",
"title": "Initialize the template folder",
"category": "Front matter"
},
{
"command": "frontMatter.createTemplate",
"title": "Create a template from current file",
"title": "Create template from current file",
"category": "Front matter"
},
{
@@ -1799,9 +1893,10 @@
"start:site": "cd ./docs && npm run dev"
},
"devDependencies": {
"@actions/core": "^1.8.2",
"@bendera/vscode-webview-elements": "0.6.2",
"@estruyf/vscode": "0.0.3",
"@headlessui/react": "^1.5.0",
"@headlessui/react": "1.5.0",
"@heroicons/react": "1.0.4",
"@iarna/toml": "2.2.3",
"@octokit/rest": "^18.12.0",
@@ -1835,7 +1930,7 @@
"downshift": "6.0.6",
"fuse.js": "6.5.3",
"glob": "7.1.6",
"gray-matter": "4.0.2",
"gray-matter": "4.0.3",
"html-loader": "1.3.2",
"html-webpack-plugin": "4.5.0",
"image-size": "^1.0.0",
+19 -1
View File
@@ -1,5 +1,6 @@
const fs = require('fs');
const path = require('path');
const core = require('@actions/core');
const packageJson = require('../package.json');
const version = packageJson.version.split('.');
@@ -14,7 +15,24 @@ packageJson.homepage = "https://beta.frontmatter.codes";
console.log(packageJson.version);
core.summary.addHeading(`Version info`).addDetails(`${packageJson.version}`);
const scripts = packageJson.scripts;
for (const key in scripts) {
if (key.startsWith(`prod:`)) {
scripts[key] = scripts[key].replace("production", "development");
}
}
console.log(JSON.stringify(packageJson.scripts, null, 2));
fs.writeFileSync(path.join(path.resolve('.'), 'package.json'), JSON.stringify(packageJson, null, 2));
let readme = fs.readFileSync(path.join(__dirname, '../README.beta.md'), 'utf8');
fs.writeFileSync(path.join(__dirname, '../README.md'), readme);
fs.writeFileSync(path.join(__dirname, '../README.md'), readme);
// Update the .vscodeignore file
const ignoreFilePath = path.join(path.resolve('.'), '.vscodeignore');
let vscodeignore = fs.readFileSync(ignoreFilePath, 'utf8');
vscodeignore = vscodeignore.replace(`**/*.map`, '');
fs.writeFileSync(ignoreFilePath, vscodeignore);
+2 -1
View File
@@ -69,7 +69,8 @@ export class Article {
const selectedOptions = await vscode.window.showQuickPick(options, {
placeHolder: `Select your ${type === TaxonomyType.Tag ? "tags" : "categories"} to insert`,
canPickMany: true
canPickMany: true,
ignoreFocusOut: true
});
if (selectedOptions) {
+8 -2
View File
@@ -1,9 +1,14 @@
import { commands, QuickPickItem, window } from 'vscode';
import { COMMAND_NAME } from '../constants';
import { COMMAND_NAME, SETTING_TEMPLATES_ENABLED } from '../constants';
import { Settings } from '../helpers';
export class Content {
public static async create() {
const templatesEnabled = await Settings.get(SETTING_TEMPLATES_ENABLED);
if (!templatesEnabled) {
commands.executeCommand(COMMAND_NAME.createByContentType);
}
const options: QuickPickItem[] = [{
label: "Create content by content type",
@@ -15,7 +20,8 @@ export class Content {
const selectedOption = await window.showQuickPick(options, {
placeHolder: `Select how you want to create your new content`,
canPickMany: false
canPickMany: false,
ignoreFocusOut: true
});
if (selectedOption) {
+42 -13
View File
@@ -6,7 +6,7 @@ import { ContentFolder, FileInfo, FolderInfo } from "../models";
import uniqBy = require("lodash.uniqby");
import { Template } from "./Template";
import { Notifications } from "../helpers/Notifications";
import { Settings } from "../helpers";
import { Logger, Settings } from "../helpers";
import { existsSync, mkdirSync } from 'fs';
import { format } from 'date-fns';
import { Dashboard } from './Dashboard';
@@ -98,7 +98,10 @@ export class Folders {
* Register the new folder path
* @param folder
*/
public static async register(folder: Uri) {
public static async register(folderInfo: { title: string, path: Uri } | Uri) {
let folderName = folderInfo instanceof Uri ? undefined : folderInfo.title;
const folder = folderInfo instanceof Uri ? folderInfo : folderInfo.path;
if (folder && folder.fsPath) {
const wslPath = folder.fsPath.replace(/\//g, '\\');
@@ -111,11 +114,14 @@ export class Folders {
return;
}
const folderName = await window.showInputBox({
prompt: `Which name would you like to specify for this folder?`,
placeHolder: `Folder name`,
value: basename(folder.fsPath)
});
if (!folderName) {
folderName = await window.showInputBox({
prompt: `Which name would you like to specify for this folder?`,
placeHolder: `Folder name`,
value: basename(folder.fsPath),
ignoreFocusOut: true
});
}
folders.push({
title: folderName,
@@ -287,11 +293,30 @@ export class Folders {
public static get(): ContentFolder[] {
const wsFolder = Folders.getWorkspaceFolder();
const folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
const contentFolders = folders.map(folder => {
if (!folder.title) {
folder.title = basename(folder.path);
}
let folderPath = Folders.absWsFolder(folder, wsFolder);
if (!existsSync(folderPath)) {
Notifications.errorShowOnce(`Folder "${folder.title} (${folder.path})" does not exist. Please remove it from the settings.`, "Remove folder").then(answer => {
if (answer === "Remove folder") {
let folders = Folders.get();
Folders.update(folders.filter(f => f.path !== folder.path));
}
});
return null;
}
return {
...folder,
path: folderPath
}
})
return folders.map(folder => ({
...folder,
path: Folders.absWsFolder(folder, wsFolder)
}));
return contentFolders.filter(folder => folder !== null) as ContentFolder[];
}
/**
@@ -358,11 +383,15 @@ export class Folders {
// Find folders that contain files
const wsFolder = Folders.getWorkspaceFolder();
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
const patterns = supportedFiles.map(fileType => `${join(wsFolder?.fsPath || "", "**", `*${fileType.startsWith('.') ? '' : '.'}${fileType}`)}`);
const patterns = supportedFiles.map(fileType => `${join(parseWinPath(wsFolder?.fsPath || ""), "**", `*${fileType.startsWith('.') ? '' : '.'}${fileType}`)}`);
let folders: string[] = [];
for (const pattern of patterns) {
folders = [...folders, ...(await this.findFolders(pattern))];
try {
folders = [...folders, ...(await this.findFolders(pattern))];
} catch (e) {
Logger.error(`Something went wrong while searching for folders with pattern "${pattern}": ${(e as Error).message}`);
}
}
// Filter out the workspace folder
+36 -19
View File
@@ -24,35 +24,25 @@ categories: []
---
`;
public static isInitialized() {
return Settings.hasProjectFile();
}
/**
* Initialize a new "Project" instance.
*/
public static async init(sampleTemplate: boolean = true) {
public static async init(sampleTemplate?: boolean) {
try {
Settings.createTeamSettings();
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
const folder = Template.getSettings();
const templatePath = Project.templatePath();
if (!folder || !templatePath) {
return;
}
const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`));
if (!fs.existsSync(templatePath.fsPath)) {
await workspace.fs.createDirectory(templatePath);
}
if (sampleTemplate) {
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
if (sampleTemplate !== undefined) {
await Project.createSampleTemplate();
} else {
Notifications.info("Project initialized successfully.");
}
Telemetry.send(TelemetryEvent.initialization);
// Check if you can find the framework
const wsFolder = Folders.getWorkspaceFolder();
const framework = FrameworkDetector.get(wsFolder?.fsPath || "");
@@ -68,6 +58,33 @@ categories: []
}
}
/**
* Creates the templates folder + sample if needed
* @param sampleTemplate
* @returns
*/
public static async createSampleTemplate(sampleTemplate?: boolean) {
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
const folder = Template.getSettings();
const templatePath = Project.templatePath();
if (!folder || !templatePath) {
return;
}
const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`));
if (!fs.existsSync(templatePath.fsPath)) {
await workspace.fs.createDirectory(templatePath);
}
if (sampleTemplate) {
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
Notifications.info("Sample template created.");
}
}
/**
* Get the template path for the current project
*/
+19 -7
View File
@@ -17,7 +17,8 @@ export class Settings {
public static async create(type: TaxonomyType) {
const newOption = await vscode.window.showInputBox({
prompt: `Insert the value of the ${type === TaxonomyType.Tag ? "tag" : "category"} that you want to add to your configuration.`,
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`,
ignoreFocusOut: true
});
if (newOption) {
@@ -36,7 +37,11 @@ export class Settings {
await SettingsHelper.updateTaxonomy(type, options);
// Ask if the new term needs to be added to the page
const addToPage = await vscode.window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: `Do you want to add the new ${type === TaxonomyType.Tag ? "tag" : "category"} to the page?` });
const addToPage = await vscode.window.showQuickPick(["yes", "no"], {
canPickMany: false,
placeHolder: `Do you want to add the new ${type === TaxonomyType.Tag ? "tag" : "category"} to the page?`,
ignoreFocusOut: true
});
if (addToPage && addToPage === "yes") {
const editor = vscode.window.activeTextEditor;
@@ -149,7 +154,8 @@ export class Settings {
"Category"
], {
placeHolder: `What do you want to remap?`,
canPickMany: false
canPickMany: false,
ignoreFocusOut: true
});
if (!taxType) {
return;
@@ -165,7 +171,8 @@ export class Settings {
const selectedOption = await vscode.window.showQuickPick(options, {
placeHolder: `Select your ${type === TaxonomyType.Tag ? "tags" : "categories"} to insert`,
canPickMany: false
canPickMany: false,
ignoreFocusOut: true
});
if (!selectedOption) {
@@ -174,11 +181,16 @@ export class Settings {
const newOptionValue = await vscode.window.showInputBox({
prompt: `Specify the value of the ${type === TaxonomyType.Tag ? "tag" : "category"} with which you want to remap "${selectedOption}". Leave the input <blank> if you want to remove the ${type === TaxonomyType.Tag ? "tag" : "category"} from all articles.`,
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`
placeHolder: `Name of the ${type === TaxonomyType.Tag ? "tag" : "category"}`,
ignoreFocusOut: true
});
if (!newOptionValue) {
const deleteAnswer = await vscode.window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: `Delete ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"}?` });
const deleteAnswer = await vscode.window.showQuickPick(["yes", "no"], {
canPickMany: false,
placeHolder: `Delete ${selectedOption} ${type === TaxonomyType.Tag ? "tag" : "category"}?`,
ignoreFocusOut: true
});
if (deleteAnswer === "no") {
return;
}
@@ -226,7 +238,7 @@ export class Settings {
data[matterProp] = [...new Set(taxonomies)].sort();
const spaces = vscode.window.activeTextEditor?.options?.tabSize;
// Update the file
fs.writeFileSync(file.path, FrontMatterParser.toFile(article.content, article.data, {
fs.writeFileSync(file.path, FrontMatterParser.toFile(article.content, article.data, mdFile, {
indent: spaces || 2
} as DumpOptions as any), { encoding: "utf8" });
}
+20 -9
View File
@@ -64,7 +64,8 @@ export class Template {
const titleValue = await vscode.window.showInputBox({
prompt: `What name would you like to give your template?`,
placeHolder: `article`
placeHolder: `article`,
ignoreFocusOut: true
});
if (!titleValue) {
@@ -77,6 +78,7 @@ export class Template {
{
canPickMany: false,
placeHolder: `Do you want to keep the contents for the template?`,
ignoreFocusOut: true
}
);
@@ -98,11 +100,24 @@ export class Template {
}
}
/**
* Retrieve all templates
*/
public static async getTemplates() {
const folder = Settings.get<string>(SETTING_TEMPLATES_FOLDER);
if (!folder) {
Notifications.warning(`No templates found.`);
return;
}
return await vscode.workspace.findFiles(`${folder}/**/*`, "**/node_modules/**,**/archetypes/**");
}
/**
* Create from a template
*/
public static async create(folderPath: string) {
const folder = Settings.get<string>(SETTING_TEMPLATES_FOLDER);
const contentTypes = ContentType.getAll();
if (!folderPath) {
@@ -110,19 +125,15 @@ export class Template {
return;
}
if (!folder) {
Notifications.warning(`No templates found.`);
return;
}
const templates = await vscode.workspace.findFiles(`${folder}/**/*`, "**/node_modules/**,**/archetypes/**");
const templates = await Template.getTemplates();
if (!templates || templates.length === 0) {
Notifications.warning(`No templates found.`);
return;
}
const selectedTemplate = await vscode.window.showQuickPick(templates.map(t => path.basename(t.fsPath)), {
placeHolder: `Select the content template to use`
placeHolder: `Select the content template to use`,
ignoreFocusOut: true
});
if (!selectedTemplate) {
Notifications.warning(`No template selected.`);
+4 -4
View File
@@ -59,8 +59,8 @@ export class Wysiwyg {
const option = await window.showQuickPick([ ...qpItems ], {
placeHolder: "Which type of markup would you like to insert?",
canPickMany: false,
ignoreFocusOut: false,
canPickMany: false,
ignoreFocusOut: true
});
if (option) {
@@ -161,8 +161,8 @@ export class Wysiwyg {
"Heading 6"
], {
canPickMany: false,
placeHolder: "Which heading level do you want to insert?",
ignoreFocusOut: false
placeHolder: "Which heading level do you want to insert?",
ignoreFocusOut: true
});
if (headingLvl) {
+3
View File
@@ -25,6 +25,7 @@ export const COMMAND_NAME = {
createByContentType: getCommandName("createByContentType"),
createByTemplate: getCommandName("createByTemplate"),
createTemplate: getCommandName("createTemplate"),
initTemplate: getCommandName("initTemplate"),
collapseSections: getCommandName("collapseSections"),
preview: getCommandName("preview"),
dashboard: getCommandName("dashboard"),
@@ -37,6 +38,8 @@ export const COMMAND_NAME = {
diagnostics: getCommandName("diagnostics"),
modeSwitch: getCommandName("mode.switch"),
showOutputChannel: getCommandName("showOutputChannel"),
// Insert dashboards
insertMedia: getCommandName("insertMedia"),
insertSnippet: getCommandName("insertSnippet"),
+7 -2
View File
@@ -1,5 +1,10 @@
export enum GeneralCommands{
setMode = "setMode"
export const GeneralCommands = {
toWebview: {
setMode: "setMode",
},
toVSCode: {
openLink: "openLink",
}
};
+6 -1
View File
@@ -32,6 +32,7 @@ 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_TEMPLATES_ENABLED = "templates.enabled";
export const SETTING_TELEMETRY_DISABLE = "telemetry.disable";
@@ -61,7 +62,6 @@ export const SETTING_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes";
export const SETTING_MEDIA_SUPPORTED_MIMETYPES = "media.supportedMimeTypes";
export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
export const SETTING_DASHBOARD_CONTENT_TAGS = "dashboard.content.cardTags";
export const SETTING_DATA_FILES = "data.files";
@@ -89,3 +89,8 @@ export const SETTING_DATE_FIELD = "taxonomy.dateField";
* Use the `isModifiedDate` property on the content type datetime field instead
*/
export const SETTING_MODIFIED_FIELD = "taxonomy.modifiedField";
/**
* @deprecated
* Use the `frontMatter.content.snippets` setting instead
*/
export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
@@ -1,6 +1,6 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { Menu } from '@headlessui/react';
import { EyeIcon, TrashIcon } from '@heroicons/react/outline';
import { EyeIcon, TerminalIcon, TrashIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { CustomScript, ScriptType } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
@@ -43,7 +43,7 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
return (scripts || []).filter(script => (script.type === undefined || script.type === ScriptType.Content) && !script.bulk).map(script => (
<MenuItem
key={script.title}
title={script.title}
title={<div className='flex items-center'><TerminalIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{script.title}</span></div>}
onClick={(value, e) => runCustomScript(e, script)} />
))
}, [scripts]);
@@ -70,15 +70,15 @@ export const ContentActions: React.FunctionComponent<IContentActionsProps> = ({
<ActionMenuButton title={`Menu`} />
<MenuItems widthClass='w-40' marginTopClass='mt-6'>
<MenuItems widthClass='w-44' marginTopClass='mt-6'>
<MenuItem
title={`View`}
title={<div className='flex items-center'><EyeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>View</span></div>}
onClick={(value, e) => onView(e)} />
{ customScriptActions }
<MenuItem
title={`Delete`}
title={<div className='flex items-center'><TrashIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Delete</span></div>}
onClick={(value, e) => onDelete(e)} />
</MenuItems>
</Menu>
@@ -9,14 +9,16 @@ import { Status } from '../Status';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardViewType } from '../../models';
import { ContentActions } from './ContentActions';
import { useMemo } from 'react';
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>) => {
export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, title, description, type, ...pageData }: React.PropsWithChildren<IItemProps>) => {
const view = useRecoilValue(ViewSelector);
const settings = useRecoilValue(SettingsSelector);
const draftField = useMemo(() => settings?.draftField, [settings]);
const openFile = () => {
Messenger.send(DashboardMessage.openFile, fmFilePath);
@@ -51,7 +53,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
<div className="relative p-4 w-full">
<div className={`flex justify-between items-center`}>
<Status draft={draft} />
{ draftField && draftField.name && <Status draft={pageData[draftField.name]} /> }
<DateField className={`mr-4`} value={date} />
@@ -96,7 +98,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
<DateField value={date} />
</div>
<div className="col-span-2">
<Status draft={draft} />
{ draftField && draftField.name && <Status draft={pageData[draftField.name]} /> }
</div>
</button>
</li>
@@ -149,9 +149,9 @@ export const DataView: React.FunctionComponent<IDataViewProps> = (props: React.P
<div className={`divide-y divide-gray-200 dark:divide-vulcan-300 border-t border-b border-gray-200 dark:border-vulcan-300`}>
{
(dataFiles && dataFiles.length > 0) && (
dataFiles.map((dataFile) => (
dataFiles.map((dataFile, idx) => (
<button
key={dataFile.id}
key={`${dataFile.id}-${idx}`}
type='button'
className={`px-4 py-2 flex items-center text-sm font-medium w-full text-left hover:bg-gray-200 dark:hover:bg-vulcan-400 hover:text-vulcan-500 dark:hover:text-whisper-500 ${selectedData?.id === dataFile.id ? 'bg-gray-300 dark:bg-vulcan-300 text-vulcan-500 dark:text-whisper-500' : 'text-gray-500 dark:text-whisper-900'}`}
onClick={() => setSchema(dataFile)}>
+76 -23
View File
@@ -1,13 +1,14 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { Menu } from '@headlessui/react';
import { ClipboardIcon, CodeIcon, DocumentIcon, EyeIcon, MusicNoteIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon, VideoCameraIcon } from '@heroicons/react/outline';
import { ClipboardIcon, CodeIcon, DocumentIcon, EyeIcon, MusicNoteIcon, PencilIcon, PhotographIcon, PlusIcon, TerminalIcon, TrashIcon, VideoCameraIcon } from '@heroicons/react/outline';
import { basename, dirname } from 'path';
import * as React from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { CustomScript } from '../../../helpers/CustomScript';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { ScriptType } from '../../../models';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { ScriptType, Snippet } from '../../../models';
import { MediaInfo } from '../../../models/MediaPaths';
import { DashboardMessage } from '../../DashboardMessage';
import { LightboxAtom, SelectedMediaFolderSelector, SettingsSelector, ViewDataSelector } from '../../state';
@@ -15,6 +16,7 @@ import { MenuItem, MenuItems } from '../Menu';
import { ActionMenuButton } from '../Menu/ActionMenuButton';
import { QuickAction } from '../Menu/QuickAction';
import { Alert } from '../Modals/Alert';
import { InfoDialog } from '../Modals/InfoDialog';
import { DetailsSlideOver } from './DetailsSlideOver';
export interface IItemProps {
@@ -25,6 +27,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const [ , setLightbox ] = useRecoilState(LightboxAtom);
const [ showAlert, setShowAlert ] = React.useState(false);
const [ showForm, setShowForm ] = React.useState(false);
const [ showSnippetSelection, setShowSnippetSelection ] = React.useState(false);
const [ showDetails, setShowDetails ] = React.useState(false);
const [ caption, setCaption ] = React.useState(media.caption);
const [ alt, setAlt ] = React.useState(media.alt);
@@ -33,6 +36,15 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
const selectedFolder = useRecoilValue(SelectedMediaFolderSelector);
const viewData = useRecoilValue(ViewDataSelector);
const mediaSnippets = useMemo(() => {
if (!settings?.snippets) {
return [];
}
const keys = Object.keys(settings.snippets);
return keys.filter(key => (settings.snippets || {})[key].isMediaSnippet).map(key => ({ title: key, ...(settings.snippets || {})[key]}));
}, [settings]);
const getFolder = () => {
if (settings?.wsFolder && media.fsPath) {
let relPath = media.fsPath.split(settings.wsFolder).pop();
@@ -104,25 +116,39 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
};
const insertSnippet = useCallback(() => {
const relPath = getRelPath();
let snippet = settings?.mediaSnippet.join("\n");
if (mediaSnippets.length === 1) {
processSnippet(mediaSnippets[0]);
} else {
// Show dialog to select
setShowSnippetSelection(true);
}
}, [mediaSnippets]);
snippet = snippet?.replace("{mediaUrl}", parseWinPath(relPath) || "");
snippet = snippet?.replace("{alt}", alt || "");
snippet = snippet?.replace("{caption}", caption || "");
snippet = snippet?.replace("{title}", media.title || "");
snippet = snippet?.replace("{filename}", basename(relPath || ""));
snippet = snippet?.replace("{mediaWidth}", media?.dimensions?.width?.toString() || "");
snippet = snippet?.replace("{mediaHeight}", media?.dimensions?.height?.toString() || "");
const processSnippet = useCallback((snippet: Snippet) => {
setShowSnippetSelection(false);
const relPath = getRelPath();
const fieldData = {
mediaUrl: parseWinPath(relPath) || "",
alt: alt || "",
caption: caption || "",
title: media.title || "",
filename: basename(relPath || ""),
mediaWidth: media?.dimensions?.width?.toString() || "",
mediaHeight: media?.dimensions?.height?.toString() || "",
};
const output = SnippetParser.render(snippet.body, fieldData, snippet?.openingTags, snippet?.closingTags);
Messenger.send(DashboardMessage.insertMedia, {
relPath: parseWinPath(relPath) || "",
file: viewData?.data?.filePath,
fieldName: viewData?.data?.fieldName,
position: viewData?.data?.position || null,
snippet
snippet: output
});
}, [alt, caption, media, settings, viewData]);
}, [alt, caption, media, settings, viewData, mediaSnippets]);
const deleteMedia = () => {
setShowAlert(true);
@@ -198,7 +224,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
return (settings?.scripts || []).filter(script => script.type === ScriptType.MediaFile).map(script => (
<MenuItem
key={script.title}
title={script.title}
title={<div className='flex items-center'><TerminalIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{script.title}</span></div>}
onClick={() => runCustomScript(script)} />
))
}
@@ -323,7 +349,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
</QuickAction>
{
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
(viewData?.data?.position && mediaSnippets.length > 0) && (
<QuickAction
title='Insert snippet'
onClick={insertSnippet}>
@@ -354,7 +380,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
<MenuItems widthClass='w-40'>
<MenuItem
title={`Edit metadata`}
title={<div className='flex items-center'><PencilIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Edit metadata</span></div>}
onClick={updateMetadata}
/>
@@ -362,15 +388,16 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
viewData?.data?.filePath ? (
<>
<MenuItem
title={`Insert image markdown`}
title={<div className='flex items-center'><PlusIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Insert image markdown</span></div>}
onClick={insertToArticle} />
{
(viewData?.data?.position && settings?.mediaSnippet && settings?.mediaSnippet.length > 0) && (
(viewData?.data?.position && mediaSnippets.length > 0) && mediaSnippets.map((snippet, idx) => (
<MenuItem
title={`Insert snippet`}
onClick={insertSnippet} />
)
key={idx}
title={<div className='flex items-center'><CodeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>{snippet.title}</span></div>}
onClick={() => processSnippet(snippet)} />
))
}
{ customScriptActions() }
@@ -387,11 +414,11 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
}
<MenuItem
title={`Reveal media`}
title={<div className='flex items-center'><EyeIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Reveal media</span></div>}
onClick={revealMedia} />
<MenuItem
title={`Delete`}
title={<div className='flex items-center'><TrashIcon className="mr-2 h-5 w-5 flex-shrink-0" aria-hidden={true} /> <span>Delete</span></div>}
onClick={deleteMedia} />
</MenuItems>
</Menu>
@@ -436,6 +463,32 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
</div>
</li>
{
showSnippetSelection && (
<InfoDialog
icon={<CodeIcon className="h-6 w-6" aria-hidden="true" />}
title='Insert snippet'
description='Select the media snippet to use for the current media file.'
dismiss={() => setShowSnippetSelection(false)}>
<ul className='flex justify-center'>
{
mediaSnippets.map((snippet, idx) => (
<li key={idx} className="inline-flex items-center pb-2 mr-2">
<button
className="w-full inline-flex justify-center border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none sm:w-auto sm:text-sm disabled:opacity-30"
onClick={() => processSnippet(snippet)}>
{snippet.title}
</button>
</li>
))
}
</ul>
</InfoDialog>
)
}
{
showDetails && (
<DetailsSlideOver
@@ -18,7 +18,7 @@ export const MenuItems: React.FunctionComponent<IMenuItemsProps> = ({widthClass,
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className={`${widthClass || ""} ${marginTopClass || "mt-2"} origin-top-right absolute right-0 z-10 rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto`}>
<Menu.Items className={`${widthClass || ""} ${marginTopClass || "mt-2"} origin-top-right absolute right-0 z-20 rounded-md shadow-2xl bg-white dark:bg-vulcan-500 ring-1 ring-vulcan-400 dark:ring-white ring-opacity-5 focus:outline-none text-sm max-h-96 overflow-auto`}>
<div className="py-1">
{children}
</div>
@@ -0,0 +1,72 @@
import { Dialog, Transition } from '@headlessui/react';
import * as React from 'react';
import { Fragment } from 'react';
export interface IInfoDialogProps {
icon?: JSX.Element;
title: string;
description: string;
dismiss: () => void;
}
export const InfoDialog: React.FunctionComponent<IInfoDialogProps> = ({dismiss, icon, title, description, children}: React.PropsWithChildren<IInfoDialogProps>) => {
return (
<Transition.Root show={true} as={Fragment}>
<Dialog className="fixed z-10 inset-0 overflow-y-auto" onClose={dismiss}>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-vulcan-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white dark:bg-vulcan-500 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6 border-2 border-whisper-900">
<div className="sm:flex sm:items-start">
{
icon && (
<div className="mt-3 sm:mr-4 mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full sm:mx-0 sm:h-10 sm:w-10 bg-gray-50 dark:bg-vulcan-400">
{icon}
</div>
)
}
<div className="mt-3 text-center sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-vulcan-300 dark:text-whisper-900">
{title}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-vulcan-500 dark:text-whisper-500">
{description}
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-4">
{children}
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
};
@@ -1,12 +1,13 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { CodeIcon, DotsHorizontalIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
import { CodeIcon, DocumentTextIcon, DotsHorizontalIcon, PencilIcon, PhotographIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback, useRef, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { FeatureFlag } from '../../../components/features/FeatureFlag';
import { FEATURE_FLAG } from '../../../constants';
import { SnippetParser } from '../../../helpers/SnippetParser';
import { Snippet, Snippets } from '../../../models';
import { FileIcon } from '../../../panelWebView/components/Icons/FileIcon';
import { DashboardMessage } from '../../DashboardMessage';
import { ModeAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { QuickAction } from '../Menu';
@@ -31,9 +32,12 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
const [ snippetOriginalBody, setSnippetOriginalBody ] = useState<string>('');
const [ mediaSnippet, setMediaSnippet ] = useState<boolean>(false);
const formRef = useRef<SnippetFormHandle>(null);
const insertToContent = useMemo(() => viewData?.data?.filePath, [ viewData ]);
const insertToArticle = () => {
formRef.current?.onSave();
setShowInsertDialog(false);
@@ -44,6 +48,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
setSnippetTitle('');
setSnippetDescription('');
setSnippetOriginalBody('');
setMediaSnippet(false);
};
const onOpenEdit = useCallback(() => {
@@ -51,6 +56,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
setSnippetDescription(snippet.description);
setSnippetOriginalBody(typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`));
setShowEditDialog(true);
setMediaSnippet(!!snippet.isMediaSnippet);
}, [snippet]);
const onSnippetUpdate = useCallback(() => {
@@ -68,11 +74,16 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
const snippetContents: Snippet = {
...crntSnippet,
fields,
description: snippetDescription || '',
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines
};
if (!mediaSnippet) {
snippetContents.fields = fields;
} else {
snippetContents.isMediaSnippet = true;
}
// Check if new or update
if (title === snippetTitle) {
snippets[title] = snippetContents;
@@ -84,7 +95,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
Messenger.send(DashboardMessage.updateSnippet, { snippets });
reset();
}, [settings?.snippets, title, snippetTitle, snippetDescription, snippetOriginalBody]);
}, [settings?.snippets, title, snippetTitle, snippetDescription, snippetOriginalBody, mediaSnippet]);
const onDelete = useCallback(() => {
const snippets = Object.assign({}, settings?.snippets || {});
@@ -102,13 +113,17 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
<CodeIcon className='w-64 h-64 opacity-5 text-vulcan-200 dark:text-gray-400' />
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<h2 className="mt-2 mb-2 font-bold flex items-center" title={snippet.isMediaSnippet ? "Media snippet" : "Content snippet"}>
{ snippet.isMediaSnippet ? <PhotographIcon className='w-5 h-5 mr-1' aria-hidden={true} /> : <DocumentTextIcon className='w-5 h-5 mr-1' aria-hidden={true} /> }
{title}
</h2>
<FeatureFlag
features={mode?.features || []}
flag={FEATURE_FLAG.dashboard.snippets.manage}
alternative={(
viewData?.data?.filePath ? (
insertToContent ? (
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
<div className='group-hover:hidden'>
@@ -135,7 +150,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
<div className='hidden group-hover:flex'>
{
viewData?.data?.filePath && (
insertToContent && !snippet.isMediaSnippet && (
<>
<QuickAction
title={`Insert snippet`}
@@ -170,7 +185,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
<FormDialog
title={`Insert snippet: ${title}`}
description={`Insert the ${title.toLowerCase()} snippet into the current article`}
isSaveDisabled={!viewData?.data?.filePath}
isSaveDisabled={!insertToContent}
trigger={insertToArticle}
dismiss={() => setShowInsertDialog(false)}
okBtnText='Insert'
@@ -200,6 +215,8 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
title={snippetTitle}
description={snippetDescription}
body={snippetOriginalBody}
isMediaSnippet={mediaSnippet}
onMediaSnippetUpdate={(value: boolean) => setMediaSnippet(value)}
onTitleUpdate={(value: string) => setSnippetTitle(value)}
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetOriginalBody(value)} />
@@ -1,16 +1,24 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { GeneralCommands } from '../../../constants';
export interface INewFormProps {
title: string;
description: string;
body: string;
isMediaSnippet: boolean;
onMediaSnippetUpdate: (value: boolean) => void;
onTitleUpdate: (value: string) => void;
onDescriptionUpdate: (value: string) => void;
onBodyUpdate: (value: string) => void;
}
export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, description, body, onTitleUpdate, onDescriptionUpdate, onBodyUpdate }: React.PropsWithChildren<INewFormProps>) => {
export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, description, body, isMediaSnippet, onMediaSnippetUpdate, onTitleUpdate, onDescriptionUpdate, onBodyUpdate }: React.PropsWithChildren<INewFormProps>) => {
const openLink = () => {
Messenger.send(GeneralCommands.toVSCode.openLink, "https://frontmatter.codes/docs/markdown#placeholders");
}
return (
<div className='space-y-4'>
@@ -60,6 +68,36 @@ export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, descrip
/>
</div>
</div>
<div>
<label htmlFor={`snippet`} className="block text-sm font-medium">
Is a media snippet?
</label>
<div className="mt-1 relative flex items-start">
<div className="flex items-center h-5">
<input
id="isMediaSnippet"
aria-describedby="isMediaSnippet-description"
name="isMediaSnippet"
type="checkbox"
checked={isMediaSnippet}
onChange={(e) => onMediaSnippetUpdate(e.currentTarget.checked)}
className="focus:ring-teal-500 h-4 w-4 text-teal-600 border-gray-300 rounded"
/>
</div>
<div className="ml-3 text-sm">
<label htmlFor="isMediaSnippet" className="font-medium text-vulcan-100 dark:text-whisper-900">
Media snippet
</label>
<p id="isMediaSnippet-description" className="text-vulcan-300 dark:text-whisper-500">
Use the current snippet for inserting media files into your content.
</p>
<p>
Check our <button className='text-teal-700 hover:text-teal-500' onClick={openLink} title='media snippet placeholders'>media snippet placeholders</button> documentation to know which placeholders you can use.
</p>
</div>
</div>
</div>
</div>
);
};
@@ -25,6 +25,7 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
const [ snippetBody, setSnippetBody ] = useState<string>('');
const [ showCreateDialog, setShowCreateDialog ] = useState(false);
const [ mediaSnippet, setMediaSnippet ] = useState(false);
const snippets = settings?.snippets || {};
const snippetKeys = useMemo(() => Object.keys(snippets) || [], [settings?.snippets]);
@@ -41,11 +42,12 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
title: snippetTitle,
description: snippetDescription || '',
body: snippetBody,
fields
fields,
isMediaSnippet: mediaSnippet
});
reset();
}, [snippetTitle, snippetDescription, snippetBody]);
}, [snippetTitle, snippetDescription, snippetBody, mediaSnippet]);
const reset = () => {
setShowCreateDialog(false);
@@ -127,6 +129,8 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
title={snippetTitle}
description={snippetDescription}
body={snippetBody}
isMediaSnippet={mediaSnippet}
onMediaSnippetUpdate={(value: boolean) => setMediaSnippet(value)}
onTitleUpdate={(value: string) => setSnippetTitle(value)}
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetBody(value)} />
+18 -3
View File
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { SettingsAtom } from '../state';
@@ -9,15 +10,29 @@ export interface IStatusProps {
export const Status: React.FunctionComponent<IStatusProps> = ({draft}: React.PropsWithChildren<IStatusProps>) => {
const settings = useRecoilValue(SettingsAtom);
const draftField = useMemo(() => settings?.draftField, [settings]);
const draftValue = useMemo(() => {
if (draftField && draftField.type === 'choice') {
return draft;
} else if (draftField && typeof draftField.invert !== 'undefined' && draftField.invert) {
return !draft;
} else {
return draft;
}
}, [draftField, draft]);
if (settings?.draftField && settings.draftField.type === "choice") {
if (draft) {
return <span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 bg-teal-500`}>{draft}</span>;
if (draftValue) {
return <span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 bg-teal-500`}>{draftValue}</span>;
} else {
return null;
}
}
return (
<span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 ${draft ? "bg-red-500" : "bg-teal-500"}`}>{draft ? "Draft" : "Published"}</span>
<span className={`inline-block px-2 py-1 leading-none rounded-sm font-semibold uppercase tracking-wide text-xs text-whisper-200 dark:text-vulcan-500 ${draftValue ? "bg-red-500" : "bg-teal-500"}`}>
{draftValue ? "Draft" : "Published"}
</span>
);
};
+1 -1
View File
@@ -47,7 +47,7 @@ export default function useMessages() {
case DashboardCommand.searchReady:
setSearchReady(true);
break;
case GeneralCommands.setMode:
case GeneralCommands.toWebview.setMode:
setMode(message.data);
break;
}
+26 -4
View File
@@ -10,6 +10,7 @@ import { Sorting } from '../../helpers/Sorting';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../DashboardMessage';
import { EventData } from '@estruyf/vscode/dist/models';
import { parseWinPath } from '../../helpers/parseWinPath';
export default function usePages(pages: Page[]) {
const [ pageItems, setPageItems ] = useState<Page[]>([]);
@@ -24,10 +25,27 @@ export default function usePages(pages: Page[]) {
const processPages = useCallback((searchedPages: Page[]) => {
const draftField = settings?.draftField;
const framework = settings?.crntFramework;
// Filter the pages
let pagesToShow: Page[] = Object.assign([], searchedPages);
// Framework specific actions
if (framework?.toLowerCase() === "jekyll") {
pagesToShow = pagesToShow.map(page => {
// https://jekyllrb.com/docs/posts/#drafts
const filePath = parseWinPath(page.fmFilePath);
page.draft = filePath.indexOf(`/_drafts/`) > -1;
// Published field: https://jekyllrb.com/docs/front-matter/#predefined-global-variables
if (typeof page.published !== "undefined") {
page.draft = !page.published;
}
return page;
});
}
const draftTypes = Object.assign({}, tabInfo);
draftTypes[Tab.All] = pagesToShow.length;
@@ -46,15 +64,19 @@ export default function usePages(pages: Page[]) {
pagesToShow = searchedPages;
}
} else {
// Draft field is a boolean field
const draftFieldName = draftField?.name || "draft";
const drafts = pagesToShow.filter(page => page[draftFieldName] == true || page[draftFieldName] === "true");
const published = pagesToShow.filter(page => page[draftFieldName] == false || page[draftFieldName] === "false" || typeof page[draftFieldName] === "undefined");
draftTypes[Tab.Draft] = pagesToShow.filter(page => !!page[draftFieldName]).length;
draftTypes[Tab.Published] = pagesToShow.filter(page => !page[draftFieldName]).length;
draftTypes[Tab.Draft] = draftField?.invert ? published.length : drafts.length;
draftTypes[Tab.Published] = draftField?.invert ? drafts.length : published.length;
if (tab === Tab.Published) {
pagesToShow = searchedPages.filter(page => !page[draftFieldName]);
pagesToShow = draftField?.invert ? drafts : published;
} else if (tab === Tab.Draft) {
pagesToShow = searchedPages.filter(page => !!page[draftFieldName]);
pagesToShow = draftField?.invert ? published : drafts;
} else {
pagesToShow = searchedPages;
}
+1 -1
View File
@@ -15,7 +15,7 @@ export interface Page {
title: string;
slug: string;
date: string | Date;
draft: string;
draft: boolean | string;
description: string;
preview?: string;
-1
View File
@@ -16,7 +16,6 @@ export interface Settings {
openOnStart: boolean | null;
versionInfo: VersionInfo;
pageViewType: DashboardViewType | undefined;
mediaSnippet: string[];
contentTypes: ContentType[];
contentFolders: ContentFolder[];
crntFramework: string;
+5
View File
@@ -136,6 +136,10 @@ export async function activate(context: vscode.ExtensionContext) {
});
let createTemplate = vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate);
subscriptions.push(
vscode.commands.registerCommand(COMMAND_NAME.initTemplate, () => Project.createSampleTemplate(true))
);
const toggleDraftCommand = COMMAND_NAME.toggleDraft;
const toggleDraft = vscode.commands.registerCommand(toggleDraftCommand, async () => {
@@ -211,6 +215,7 @@ export async function activate(context: vscode.ExtensionContext) {
subscriptions.push(vscode.workspace.onDidChangeTextDocument((TextDocumentChangeEvent) => {
const filePath = TextDocumentChangeEvent.document.uri.fsPath;
if (filePath && !filePath.toLowerCase().startsWith(`extension-output`)) {
MarkdownFoldingProvider.triggerHighlighting();
statusDebouncer(() => triggerShowDraftStatus(`onDidChangeTextEditorSelection`), 200);
}
}));
+22 -3
View File
@@ -1,3 +1,4 @@
import { Uri, workspace } from 'vscode';
import { MarkdownFoldingProvider } from './../providers/MarkdownFoldingProvider';
import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType';
import * as vscode from 'vscode';
@@ -82,6 +83,23 @@ export class ArticleHelper {
await editor.edit(builder => builder.replace(update.range, update.newText));
}
/**
* Store the new information for the article path
*
* @param path
* @param article
*/
public static async updateByPath(path: string, article: ParsedFrontMatter) {
const file = await workspace.openTextDocument(Uri.parse(path));
const editor = await window.showTextDocument(file);
if (file && editor) {
const update = this.generateUpdate(file, article);
await editor.edit(builder => builder.replace(update.range, update.newText));
}
}
/**
* Generate the update to be applied to the article.
* @param article
@@ -97,7 +115,7 @@ export class ArticleHelper {
const lastLine = lines.pop();
const endsWithNewLine = lastLine !== undefined && lastLine.trim() === "";
let newMarkdown = this.stringifyFrontMatter(article.content, Object.assign({}, article.data));
let newMarkdown = this.stringifyFrontMatter(article.content, Object.assign({}, article.data), document?.getText());
// Logic to not include a new line at the end of the file
if (!endsWithNewLine) {
@@ -132,8 +150,9 @@ export class ArticleHelper {
*
* @param content
* @param data
* @param originalContent
*/
public static stringifyFrontMatter(content: string, data: any) {
public static stringifyFrontMatter(content: string, data: any, originalContent?: string) {
const indentArray = Settings.get(SETTING_INDENT_ARRAY) as boolean;
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
@@ -147,7 +166,7 @@ export class ArticleHelper {
}
}
return FrontMatterParser.toFile(content, data, ({
return FrontMatterParser.toFile(content, data, originalContent, ({
noArrayIndent: !indentArray,
skipInvalid: true,
noCompatMode: true,
+11 -1
View File
@@ -1,7 +1,7 @@
import { ModeListener } from './../listeners/general/ModeListener';
import { PagesListener } from './../listeners/dashboard';
import { ArticleHelper, Settings } from ".";
import { FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
import { FEATURE_FLAG, SETTING_CONTENT_DRAFT_FIELD, SETTING_DATE_FORMAT, SETTING_FRAMEWORK_ID, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
import { ContentType as IContentType, DraftField, Field } from '../models';
import { Uri, commands, window } from 'vscode';
import { Folders } from "../commands/Folders";
@@ -367,6 +367,16 @@ export class ContentType {
return;
}
if (contentType.name === "default") {
const crntFramework = Settings.get<string>(SETTING_FRAMEWORK_ID);
if (crntFramework?.toLowerCase() === "jekyll") {
const idx = contentType.fields.findIndex(f => f.name === "draft");
if (idx > -1) {
contentType.fields.splice(idx, 1);
}
}
}
let data: any = this.processFields(contentType, titleValue, {});
data = ArticleHelper.updateDates(Object.assign({}, data));
+127 -34
View File
@@ -1,6 +1,7 @@
import { CommandType } from './../models/PanelSettings';
import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings';
import { window, env as vscodeEnv, ProgressLocation } from 'vscode';
import { ArticleHelper, Telemetry } from '.';
import { ArticleHelper, Logger, Telemetry } from '.';
import { Folders } from '../commands/Folders';
import { exec } from 'child_process';
import * as os from 'os';
@@ -41,6 +42,13 @@ export class CustomScript {
}
}
/**
* Run the script on the current file
* @param wsPath
* @param script
* @param path
* @returns
*/
private static async singleRun(wsPath: string, script: ICustomScript, path: string | null = null): Promise<void> {
let articlePath: string | null = path;
let article: ParsedFrontMatter | null = null;
@@ -62,13 +70,19 @@ export class CustomScript {
cancellable: false
}, async () => {
const output = await CustomScript.runScript(wsPath, article, articlePath as string, script);
CustomScript.showOutput(output, script);
CustomScript.showOutput(output, script, articlePath);
});
} else {
Notifications.warning(`${script.title}: Article couldn't be retrieved.`);
}
}
/**
* Run the script on multiple files
* @param wsPath
* @param script
* @returns
*/
private static async bulkRun(wsPath: string, script: ICustomScript): Promise<void> {
const folders = await Folders.getInfo();
@@ -106,6 +120,13 @@ export class CustomScript {
});
}
/**
* Run a script for a media file
* @param wsPath
* @param path
* @param script
* @returns
*/
private static async runMediaScript(wsPath: string, path: string | null, script: ICustomScript): Promise<void> {
if (!path) {
Notifications.error(`${script.title}: There was no folder or media path specified.`);
@@ -118,28 +139,34 @@ export class CustomScript {
title: `Executing: ${script.title}`,
cancellable: false
}, async () => {
exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${path}"`, (error, stdout) => {
if (error) {
Notifications.error(`${script.title}: ${error.message}`);
resolve();
return;
}
CustomScript.showOutput(stdout, script);
try {
const output = await CustomScript.executeScript(script, wsPath, `"${wsPath}" "${path}"`);
CustomScript.showOutput(output, script);
Dashboard.postWebviewMessage({
command: DashboardCommand.mediaUpdate
});
resolve();
return;
});
} catch (e) {
Notifications.error(`${script.title}: ${(e as Error).message}`);
return;
}
});
});
}
/**
* Script runner
* @param wsPath
* @param article
* @param contentPath
* @param script
* @returns
*/
private static async runScript(wsPath: string, article: ParsedFrontMatter | null, contentPath: string, script: ICustomScript): Promise<string | null> {
return new Promise((resolve, reject) => {
try {
let articleData = "";
if (os.type() === "Windows_NT") {
articleData = `"${JSON.stringify(article?.data).replace(/"/g, `""`)}"`;
@@ -148,31 +175,97 @@ export class CustomScript {
articleData = `'${articleData}'`;
}
exec(`${script.nodeBin || "node"} ${join(wsPath, script.script)} "${wsPath}" "${contentPath}" ${articleData}`, (error, stdout) => {
const output = await CustomScript.executeScript(script, wsPath, `"${wsPath}" "${contentPath}" ${articleData}`);
return output;
} catch (e) {
Notifications.error(`${script.title}: ${(e as Error).message}`);
return null;
}
}
/**
* Show/process the output of the script
* @param output
* @param script
*/
private static showOutput(output: string | null, script: ICustomScript, articlePath?: string | null): void {
if (output) {
try {
const data = JSON.parse(output);
if (data.frontmatter) {
let article = null;
const editor = window.activeTextEditor;
if (!articlePath) {
if (!editor) return;
articlePath = editor.document.uri.fsPath;
article = ArticleHelper.getFrontMatter(editor);
} else {
article = ArticleHelper.getFrontMatterByPath(articlePath);
}
if (article && article.data) {
for (const key in data.frontmatter) {
article.data[key] = data.frontmatter[key];
}
if (articlePath) {
ArticleHelper.updateByPath(articlePath, article);
} else if (editor) {
ArticleHelper.update(editor, article);
} else {
throw new Error(`Couldn't update article.`);
}
Notifications.info(`${script.title}: front matter updated.`);
}
} else {
throw new Error(`No frontmatter found.`);
}
} catch (error) {
if (script.output === "editor") {
ContentProvider.show(output, script.title, script.outputType || "text");
} else {
window.showInformationMessage(`${script.title}: ${output}`, 'Copy output').then(value => {
if (value === 'Copy output') {
vscodeEnv.clipboard.writeText(output);
}
});
}
}
} else {
Notifications.info(`${script.title}: Executed your custom script.`);
}
}
/**
* Execute script
* @param script
* @param wsPath
* @param args
* @returns
*/
private static async executeScript(script: ICustomScript, wsPath: string, args: string): Promise<string> {
return new Promise((resolve, reject) => {
// Check the command to use
let command = script.nodeBin || "node";
if (script.command && script.command !== CommandType.Node) {
command = script.command;
}
const scriptPath = join(wsPath, script.script);
const fullScript = `${command} ${scriptPath} ${args}`;
Logger.info(`Executing: ${fullScript}`);
exec(fullScript, (error, stdout) => {
if (error) {
Notifications.error(`${script.title}: ${error.message}`);
resolve(null);
return;
reject(error.message);
}
resolve(stdout);
});
});
}
private static showOutput(output: string | null, script: ICustomScript): void {
if (output) {
if (script.output === "editor") {
ContentProvider.show(output, script.title, script.outputType || "text");
} else {
window.showInformationMessage(`${script.title}: ${output}`, 'Copy output').then(value => {
if (value === 'Copy output') {
vscodeEnv.clipboard.writeText(output);
}
});
}
} else {
Notifications.info(`${script.title}: Executed your custom script.`);
}
}
}
+5 -7
View File
@@ -1,10 +1,11 @@
import { basename, join } from "path";
import { workspace } from "vscode";
import { Folders } from "../commands/Folders";
import { Project } from "../commands/Project";
import { Template } from "../commands/Template";
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES } from "../constants";
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS, SETTING_DATE_FORMAT, SETTING_DASHBOARD_CONTENT_TAGS, SETTING_MEDIA_SUPPORTED_MIMETYPES } from "../constants";
import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models";
import { CustomScript, DraftField, ScriptType, Snippets, SortingSetting, TaxonomyType } from "../models";
import { CustomScript, DraftField, Snippets, SortingSetting, TaxonomyType } from "../models";
import { DataFile } from "../models/DataFile";
import { DataFolder } from "../models/DataFolder";
import { DataType } from "../models/DataType";
@@ -18,9 +19,7 @@ export class DashboardSettings {
public static async get() {
const ext = Extension.getInstance();
const wsFolder = Folders.getWorkspaceFolder();
const isInitialized = await Template.isInitialized();
const contentFolders = await Folders.getContentFolders();
const isInitialized = Project.isInitialized();
return {
beta: ext.isBetaVersion(),
@@ -32,7 +31,6 @@ export class DashboardSettings {
openOnStart: Settings.get(SETTING_DASHBOARD_OPENONSTART),
versionInfo: ext.getVersion(),
pageViewType: await ext.getState<DashboardViewType | undefined>(ExtensionState.PagesView, "workspace"),
mediaSnippet: Settings.get<string[]>(SETTING_DASHBOARD_MEDIA_SNIPPET) || [],
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
customSorting: Settings.get<SortingSetting[]>(SETTING_CONTENT_SORTING),
@@ -56,7 +54,7 @@ export class DashboardSettings {
mimeTypes: Settings.get<string[]>(SETTING_MEDIA_SUPPORTED_MIMETYPES)
},
welcome: {
contentFolders
contentFolders: await Folders.getContentFolders()
}
},
dataFiles: await this.getDataFiles(),
+74
View File
@@ -0,0 +1,74 @@
import { existsSync, readFileSync } from "fs";
import { Folders } from "../commands/Folders";
import { DataFile } from "../models";
import * as yaml from 'js-yaml';
import { Logger } from "./Logger";
import { Notifications } from "./Notifications";
import { commands } from "vscode";
import { COMMAND_NAME, SETTING_DATA_FILES } from "../constants";
import { Settings } from "./SettingsHelper";
export class DataFileHelper {
/**
* Retrieve the file data
* @param filePath
* @returns
*/
public static get(filePath: string) {
const absPath = Folders.getAbsFilePath(filePath);
if (existsSync(absPath)) {
return readFileSync(absPath, 'utf8');
}
return null;
}
/**
* Get by the id of the data file
* @param id
*/
public static getById(id: string) {
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
if (!files || files.length === 0) {
return;
}
const file = files.find(f => f.id === id);
if (!file) {
return;
}
return DataFileHelper.process(file);
}
/**
* Process the data file
* @param data
* @returns
*/
public static async process(data: DataFile) {
try {
const { file, fileType } = data;
const dataFile = DataFileHelper.get(file);
if (fileType === "yaml") {
return yaml.safeLoad(dataFile || "");
} else {
return dataFile ? JSON.parse(dataFile) : undefined;
}
} catch (ex) {
Logger.error(`DataFileHelper::process: ${(ex as Error).message}`);
const btnClick = await Notifications.error(`Something went wrong while processing the data file. Check your file and output log for more information.`, 'Open output');
if (btnClick && btnClick === 'Open output') {
commands.executeCommand(COMMAND_NAME.showOutputChannel);
}
return;
}
}
}
+38 -2
View File
@@ -1,8 +1,9 @@
import { basename } from "path";
import { extensions, Uri, ExtensionContext, window, workspace, commands, ExtensionMode, DiagnosticCollection, languages } from "vscode";
import { Folders } from "../commands/Folders";
import { EXTENSION_NAME, GITHUB_LINK, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, CONFIG_KEY, SETTING_CONTENT_PAGE_FOLDERS } from "../constants";
import { ContentFolder } from "../models";
import { Template } from "../commands/Template";
import { EXTENSION_NAME, GITHUB_LINK, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, CONFIG_KEY, SETTING_CONTENT_PAGE_FOLDERS, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_CONTENT_SNIPPETS, SETTING_TEMPLATES_ENABLED } from "../constants";
import { ContentFolder, Snippet } from "../models";
import { Notifications } from "./Notifications";
import { Settings } from "./SettingsHelper";
@@ -189,6 +190,41 @@ export class Extension {
}
}
}
if (major <= 7 && minor < 3) {
const mediaSnippet = Settings.get<string[]>(SETTING_DASHBOARD_MEDIA_SNIPPET);
if (mediaSnippet && mediaSnippet.length > 0) {
let snippet = mediaSnippet.join(`\n`);
snippet = snippet.replace(`{mediaUrl}`, `[[&mediaUrl]]`);
snippet = snippet.replace(`{mediaHeight}`, `[[mediaHeight]]`);
snippet = snippet.replace(`{mediaWidth}`, `[[mediaWidth]]`);
snippet = snippet.replace(`{caption}`, `[[&caption]]`);
snippet = snippet.replace(`{alt}`, `[[alt]]`);
snippet = snippet.replace(`{filename}`, `[[filename]]`);
snippet = snippet.replace(`{title}`, `[[title]]`);
const snippets = Settings.get<Snippet[]>(SETTING_CONTENT_SNIPPETS) || {} as any;
snippets[`Media snippet (migrated)`] = {
body: snippet.split(`\n`),
isMediaSnippet: true,
description: `Migrated media snippet from frontMatter.dashboard.mediaSnippet setting`
}
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
}
const templates = await Template.getTemplates();
if (templates && templates.length > 0) {
const answer = await window.showQuickPick(["Yes", "No"], {
title: "Front Matter - Templates",
placeHolder: "Do you want to keep on using the template functionality?",
ignoreFocusOut: true
});
Settings.update(SETTING_TEMPLATES_ENABLED, answer?.toLocaleLowerCase() === "yes", true);
}
}
}
public async setState<T>(propKey: string, propValue: T, type: "workspace" | "global" = "global"): Promise<void> {
+52
View File
@@ -1,6 +1,12 @@
import { existsSync, readFileSync } from "fs";
import jsyaml = require("js-yaml");
import { join, resolve } from "path";
import { commands, Uri } from "vscode";
import { Folders } from "../commands/Folders";
import { COMMAND_NAME } from "../constants";
import { FrameworkDetectors } from "../constants/FrameworkDetectors";
import { Framework } from "../models";
import { Logger } from "./Logger";
export class FrameworkDetector {
@@ -73,4 +79,50 @@ export class FrameworkDetector {
return undefined;
}
public static checkDefaultSettings(framework: Framework) {
if (framework.name.toLowerCase() === "jekyll") {
FrameworkDetector.jekyll();
}
}
private static jekyll() {
try {
const wsFolder = Folders.getWorkspaceFolder();
const jekyllConfig = join(wsFolder?.fsPath || "", '_config.yml');
let collectionDir = "";
if (existsSync(jekyllConfig)) {
const content = readFileSync(jekyllConfig, "utf8");
// Convert YAML to JSON
const config = jsyaml.safeLoad(content);
if (config.collections_dir) {
collectionDir = config.collections_dir;
}
}
const draftsPath = join(wsFolder?.fsPath || "", collectionDir, "_drafts");
const postsPath = join(wsFolder?.fsPath || "", collectionDir, "_posts");
if (existsSync(draftsPath)) {
const folderUri = Uri.file(draftsPath);
commands.executeCommand(COMMAND_NAME.registerFolder, {
title: "drafts",
path: folderUri
});
}
if (existsSync(postsPath)) {
const folderUri = Uri.file(postsPath);
commands.executeCommand(COMMAND_NAME.registerFolder, {
title: "posts",
path: folderUri
});
}
} catch (e) {
Logger.error(`Something failed while processing your Jekyll configuration. ${(e as Error).message}`);
}
}
}
+4 -2
View File
@@ -1,14 +1,16 @@
import { Extension } from './Extension';
import { OutputChannel, window } from 'vscode';
import { commands, OutputChannel, window } from 'vscode';
import { format } from 'date-fns';
import { COMMAND_NAME } from '../constants';
export class Logger {
private static instance: Logger;
private static channel: OutputChannel | null = null;
public static channel: OutputChannel | null = null;
private constructor() {
const displayName = Extension.getInstance().displayName;
Logger.channel = window.createOutputChannel(displayName);
commands.registerCommand(COMMAND_NAME.showOutputChannel, () => { Logger.channel?.show(); });
}
public static getInstance(): Logger {
-43
View File
@@ -1,43 +0,0 @@
import { DashboardMessage } from '../dashboardWebView/DashboardMessage';
import { CommandToCode } from "../panelWebView/CommandToCode";
interface ClientVsCode<T> {
getState: () => T;
setState: (data: T) => void;
postMessage: (msg: unknown) => void;
}
declare const acquireVsCodeApi: <T = unknown>() => ClientVsCode<T>;
export class MessageHelper {
private static vscode: ClientVsCode<any>;
public static getVsCodeAPI() {
if (!MessageHelper.vscode) {
MessageHelper.vscode = acquireVsCodeApi();
}
return MessageHelper.vscode;
}
public static sendMessage = (command: CommandToCode | DashboardMessage, data?: any) => {
const vscode = MessageHelper.getVsCodeAPI();
if (data) {
vscode.postMessage({ command, data });
} else {
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
});
}
}
+11
View File
@@ -5,6 +5,7 @@ import { Settings } from "./SettingsHelper";
export class Notifications {
private static notifications: string[] = [];
public static info(message: string, ...items: any): Thenable<string | undefined> {
Logger.info(`${EXTENSION_NAME}: ${message}`, "INFO");
@@ -36,6 +37,16 @@ export class Notifications {
return Promise.resolve(undefined);
}
public static async errorShowOnce(message: string, ...items: any): Promise<string | undefined> {
if (this.notifications.includes(message)) {
return;
}
this.notifications.push(message);
return this.error(message, ...items);
}
private static shouldShow(level: "INFO" | "WARNING" | "ERROR"): boolean {
let levels = Settings.get<string[]>(SETTING_GLOBAL_NOTIFICATIONS);
+5 -3
View File
@@ -11,7 +11,7 @@ export class Questions {
* @returns
*/
public static async yesOrNo(placeholder: string) {
const answer = await window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: placeholder, ignoreFocusOut: false });
const answer = await window.showQuickPick(["yes", "no"], { canPickMany: false, placeHolder: placeholder, ignoreFocusOut: true });
return answer === "yes";
}
@@ -23,7 +23,8 @@ export class Questions {
public static async ContentTitle(showWarning: boolean = true): Promise<string | undefined> {
const title = await window.showInputBox({
prompt: `What would you like to use as a title for the content to create?`,
placeHolder: `Content title`
placeHolder: `Content title`,
ignoreFocusOut: true
});
if (!title && showWarning) {
@@ -82,7 +83,8 @@ export class Questions {
const selectedOption = await window.showQuickPick(options, {
placeHolder: `Select the content type to create your new content`,
canPickMany: false
canPickMany: false,
ignoreFocusOut: true
});
if (!selectedOption && showWarning) {
+9
View File
@@ -170,6 +170,15 @@ export class Settings {
await Settings.config.update(name, value);
}
/**
* Checks if the project contains the frontmatter.json file
*/
public static hasProjectFile() {
const wsFolder = Folders.getWorkspaceFolder();
const configPath = join(wsFolder?.fsPath || "", Settings.globalFile);
return existsSync(configPath);
}
/**
* Create team settings
*/
+4 -1
View File
@@ -2,6 +2,7 @@ export * from './ArticleHelper';
export * from './ContentType';
export * from './CustomScript';
export * from './DashboardSettings';
export * from './DataFileHelper';
export * from './DateHelper';
export * from './Extension';
export * from './FilesHelper';
@@ -11,13 +12,15 @@ export * from './ImageHelper';
export * from './Logger';
export * from './MediaHelpers';
export * from './MediaLibrary';
export * from './MessageHelper';
export * from './Notifications';
export * from './PanelSettings';
export * from './PlaceholderHelper';
export * from './Questions';
export * from './Sanitize';
export * from './SeoHelper';
export * from './SettingsHelper';
export * from './SlugHelper';
export * from './SnippetParser';
export * from './Sorting';
export * from './StringHelpers';
export * from './Telemetry';
+4 -36
View File
@@ -3,11 +3,10 @@ import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { BaseListener } from "./BaseListener";
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { Folders } from '../../commands/Folders';
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { existsSync, writeFileSync, mkdirSync } from 'fs';
import { dirname } from 'path';
import * as yaml from 'js-yaml';
import { Logger, Notifications } from '../../helpers';
import { commands } from 'vscode';
import { DataFileHelper } from '../../helpers';
export class DataListener extends BaseListener {
@@ -57,38 +56,7 @@ export class DataListener extends BaseListener {
* @param msgData
*/
private static async processDataFile(msgData: DataFile) {
try {
const { file } = msgData;
const dataFile = this.getDataFile(file);
if (msgData.fileType === "yaml") {
const entries = yaml.safeLoad(dataFile || "");
this.sendMsg(DashboardCommand.dataFileEntries, entries);
} else {
const jsonData = dataFile ? JSON.parse(dataFile) : [];
this.sendMsg(DashboardCommand.dataFileEntries, jsonData);
}
} catch (ex) {
Logger.error(`DataListener::processDataFile: ${(ex as Error).message}`);
const btnClick = await Notifications.error(`Something went wrong while processing the data file. Check your file and output log for more information.`, 'Open output');
if (btnClick && btnClick === 'Open output') {
commands.executeCommand(`workbench.panel.output.focus`);
}
}
}
/**
* Retrieve the file data
* @param file
* @returns
*/
private static getDataFile(file: string) {
const absPath = Folders.getAbsFilePath(file);
if (existsSync(absPath)) {
return readFileSync(absPath, 'utf8');
}
return null;
const entries = await DataFileHelper.process(msgData);
this.sendMsg(DashboardCommand.dataFileEntries, entries);
}
}
@@ -67,6 +67,8 @@ export class SettingsListener extends BaseListener {
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
if (framework) {
Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
FrameworkDetector.checkDefaultSettings(framework);
} else {
Settings.update(SETTING_CONTENT_STATIC_FOLDER, "", true);
}
+12 -4
View File
@@ -4,6 +4,7 @@ import { Dashboard } from "../../commands/Dashboard";
import { SETTING_CONTENT_SNIPPETS } from "../../constants";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { Notifications, Settings } from "../../helpers";
import { Snippet } from "../../models";
import { BaseListener } from "./BaseListener";
import { SettingsListener } from "./SettingsListener";
@@ -27,7 +28,7 @@ export class SnippetListener extends BaseListener {
}
private static async addSnippet(data: any) {
const { title, description, body, fields } = data;
const { title, description, body, fields, isMediaSnippet } = data;
if (!title || !body) {
Notifications.warning("Snippet missing title or body");
@@ -42,11 +43,18 @@ export class SnippetListener extends BaseListener {
const snippetLines = body.split("\n");
snippets[title] = {
const snippetContent: any = {
description,
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines,
fields: fields || []
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines
};
if (isMediaSnippet) {
snippetContent.isMediaSnippet = true;
} else {
snippetContent.fields = fields || []
}
snippets[title] = snippetContent;
await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
SettingsListener.getSettings();
+13 -3
View File
@@ -1,21 +1,31 @@
import { GeneralCommands } from './../../constants';
import { GeneralCommands } from './../../constants/GeneralCommands';
import { Dashboard } from "../../commands/Dashboard";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { ExplorerView } from "../../explorerView/ExplorerView";
import { Extension } from "../../helpers";
import { Logger } from "../../helpers/Logger";
import { CommandToCode } from '../../panelWebView/CommandToCode';
import { commands, Uri } from 'vscode';
export abstract class BaseListener {
public static process(msg: { command: DashboardMessage, data: any }) {}
public static process(msg: { command: DashboardMessage | CommandToCode | string , data: any }) {
switch(msg.command) {
case GeneralCommands.toVSCode.openLink:
if (msg.data) {
commands.executeCommand('vscode.open', Uri.parse(msg.data));
}
break;
}
}
/**
* Send a message to the webview
* @param command
* @param data
*/
public static sendMsg(command: GeneralCommands, data: any) {
public static sendMsg(command: string, data: any) {
Logger.info(`Sending message to webview (panel&dashboard): ${command}`);
const extPath = Extension.getInstance().extensionPath;
+2 -2
View File
@@ -35,7 +35,7 @@ export class ModeListener extends BaseListener {
const activeMode = ModeSwitch.getMode();
if (activeMode) {
const mode = modes.find(m => m.id === activeMode);
this.sendMsg(GeneralCommands.setMode as any, mode);
this.sendMsg(GeneralCommands.toWebview.setMode as any, mode);
// Check the commands that need to be enabled/disabled
const snippetsView = mode?.features.find(f => f === FEATURE_FLAG.dashboard.snippets.view);
@@ -44,7 +44,7 @@ export class ModeListener extends BaseListener {
await commands.executeCommand('setContext', CONTEXT.isSnippetsDashboardEnabled, !!snippetsView);
await commands.executeCommand('setContext', CONTEXT.isDataDashboardEnabled, !!dataView);
} else {
this.sendMsg(GeneralCommands.setMode as any, undefined);
this.sendMsg(GeneralCommands.toWebview.setMode as any, undefined);
// Enable dashboards
await this.resetEnablement();
+19 -1
View File
@@ -1,3 +1,4 @@
import { DataFileHelper } from './../../helpers/DataFileHelper';
import { BlockFieldData } from './../../models/BlockFieldData';
import { ImageHelper } from './../../helpers/ImageHelper';
import { Folders } from "../../commands/Folders";
@@ -45,10 +46,16 @@ export class DataListener extends BaseListener {
break;
case CommandToCode.generateContentType:
commands.executeCommand(COMMAND_NAME.generateContentType);
break;
case CommandToCode.addMissingFields:
commands.executeCommand(COMMAND_NAME.addMissingFields);
break;
case CommandToCode.setContentType:
commands.executeCommand(COMMAND_NAME.setContentType);
break;
case CommandToCode.getDataEntries:
this.getDataFileEntries(msg.data);
break;
}
}
@@ -101,7 +108,7 @@ export class DataListener extends BaseListener {
// Get the current content type
const contentType = ArticleHelper.getContentType(updatedMetadata);
if (contentType) {
ImageHelper.processImageFields(updatedMetadata, contentType.fields)
ImageHelper.processImageFields(updatedMetadata, contentType.fields);
}
}
@@ -278,6 +285,17 @@ export class DataListener extends BaseListener {
}
}
/**
* Retrieve the data entries from local data files
* @param data
*/
private static async getDataFileEntries(data: any) {
const entries = await DataFileHelper.getById(data);
if (entries) {
this.sendMsg(Command.dataFileEntries, entries);
}
}
/**
* Open a terminal and run the passed command
* @param command
+1
View File
@@ -2,4 +2,5 @@ export interface DraftField {
name: string;
type: "boolean" | "choice";
choices?: string[];
invert?: boolean;
}
+15 -1
View File
@@ -48,7 +48,7 @@ export interface ContentType {
pageBundle?: boolean;
}
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file";
export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file" | "dataFile";
export interface Field {
title?: string;
@@ -71,6 +71,11 @@ export interface Field {
// Date fields
isPublishDate?: boolean;
isModifiedDate?: boolean;
// Data file
dataFileId?: string;
dataFileKey?: string;
dataFileValue?: string;
}
export interface DateInfo {
@@ -111,6 +116,7 @@ export interface CustomScript {
output?: "notification" | "editor";
outputType?: string;
type?: ScriptType;
command?: CommandType;
}
export interface PreviewSettings {
@@ -127,4 +133,12 @@ export enum ScriptType {
Content = "content",
MediaFolder = "mediaFolder",
MediaFile = "mediaFile"
}
export enum CommandType {
Node = "node",
Shell = "shell",
PowerShell = "powershell",
Python = "python",
Python3 = "python3"
}
+1
View File
@@ -10,6 +10,7 @@ export interface Snippet {
fields: SnippetField[];
openingTags?: string;
closingTags?: string;
isMediaSnippet?: boolean;
}
export type SnippetSpecialPlaceholders = "FM_SELECTED_TEXT" | string;
+1
View File
@@ -9,4 +9,5 @@ export enum Command {
mediaSelectionData = "mediaSelectionData",
sendMediaUrl = "sendMediaUrl",
updatePlaceholder = "updatePlaceholder",
dataFileEntries = "dataFileEntries",
}
+1
View File
@@ -37,4 +37,5 @@ export enum CommandToCode {
generateContentType = "generate-content-type",
addMissingFields = "add-missing-fields",
setContentType = "set-content-type",
getDataEntries = "get-data-entries",
}
+6 -6
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import { CustomScript, FolderInfo, Mode, PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../../helpers/MessageHelper';
import { Collapsible } from './Collapsible';
import { GlobalSettings } from './GlobalSettings';
import { OtherActions } from './OtherActions';
@@ -10,6 +9,7 @@ import { SponsorMsg } from './SponsorMsg';
import { StartServerButton } from './StartServerButton';
import { FeatureFlag } from '../../components/features/FeatureFlag';
import { FEATURE_FLAG } from '../../constants/Features';
import { Messenger } from '@estruyf/vscode/dist/client';
export interface IBaseViewProps {
settings: PanelSettings | undefined;
@@ -20,23 +20,23 @@ export interface IBaseViewProps {
const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings, folderAndFiles, mode}: React.PropsWithChildren<IBaseViewProps>) => {
const openDashboard = () => {
MessageHelper.sendMessage(CommandToCode.openDashboard);
Messenger.send(CommandToCode.openDashboard);
};
const initProject = () => {
MessageHelper.sendMessage(CommandToCode.initProject);
Messenger.send(CommandToCode.initProject);
};
const createContent = () => {
MessageHelper.sendMessage(CommandToCode.createContent);
Messenger.send(CommandToCode.createContent);
};
const openPreview = () => {
MessageHelper.sendMessage(CommandToCode.openPreview);
Messenger.send(CommandToCode.openPreview);
};
const runBulkScript = (script: CustomScript) => {
MessageHelper.sendMessage(CommandToCode.runCustomScript, { title: script.title, script });
Messenger.send(CommandToCode.runCustomScript, { title: script.title, script });
};
const customActions: any[] = (settings?.scripts || []).filter(s => s.bulk && (s.type === "content" || !s.type));
+4 -4
View File
@@ -1,6 +1,6 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { useEffect } from 'react';
import { MessageHelper } from '../../helpers/MessageHelper';
import { Command } from '../Command';
import { VsCollapsible } from './VscodeComponents';
@@ -16,7 +16,7 @@ const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({id, children,
const collapseKey = `collapse-${id}`;
useEffect(() => {
const prevState = MessageHelper.getState();
const prevState: any = Messenger.getState();
if (!prevState || !prevState[collapseKey] || prevState[collapseKey] === null || prevState[collapseKey] === 'true') {
setIsOpen(true);
updateStorage(true);
@@ -32,8 +32,8 @@ const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({id, children,
}, ['']);
const updateStorage = (value: boolean) => {
const prevState = MessageHelper.getState();
MessageHelper.setState({
const prevState: any = Messenger.getState();
Messenger.setState({
...prevState,
[collapseKey]: value.toString()
});
@@ -1,7 +1,7 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { VSCodeButton, VSCodeDivider } from '@vscode/webview-ui-toolkit/react';
import * as React from 'react';
import { useMemo } from 'react';
import { MessageHelper } from '../../../helpers/MessageHelper';
import { Field } from '../../../models';
import { CommandToCode } from '../../CommandToCode';
import { IMetadata } from '../Metadata';
@@ -30,15 +30,15 @@ export const ContentTypeValidator: React.FunctionComponent<IContentTypeValidator
const generateContentType = () => {
MessageHelper.sendMessage(CommandToCode.generateContentType);
Messenger.send(CommandToCode.generateContentType);
};
const addMissingFields = () => {
MessageHelper.sendMessage(CommandToCode.addMissingFields);
Messenger.send(CommandToCode.addMissingFields);
};
const setContentType = () => {
MessageHelper.sendMessage(CommandToCode.setContentType);
Messenger.send(CommandToCode.setContentType);
};
+2 -2
View File
@@ -1,7 +1,7 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../../helpers/MessageHelper';
import { ActionButton } from './ActionButton';
import { Messenger } from '@estruyf/vscode/dist/client';
export interface ICustomScriptProps {
title: string;
@@ -11,7 +11,7 @@ export interface ICustomScriptProps {
const CustomScript: React.FunctionComponent<ICustomScriptProps> = ({title, script}: React.PropsWithChildren<ICustomScriptProps>) => {
const runCustomScript = () => {
MessageHelper.sendMessage(CommandToCode.runCustomScript, { title, script });
Messenger.send(CommandToCode.runCustomScript, { title, script });
};
return (
@@ -0,0 +1,181 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { EventData } from '@estruyf/vscode/dist/models';
import { ChevronDownIcon, DatabaseIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Command } from '../../Command';
import { CommandToCode } from '../../CommandToCode';
import { VsLabel } from '../VscodeComponents';
import Downshift from 'downshift';
import { ChoiceButton } from './ChoiceButton';
export interface IDataFileFieldProps {
label: string;
dataFileId?: string;
dataFileKey?: string;
dataFileValue?: string;
selected: string | string[];
multiSelect?: boolean;
onChange: (value: string | string[]) => void;
}
export const DataFileField: React.FunctionComponent<IDataFileFieldProps> = ({ label, dataFileId, dataFileKey, dataFileValue, selected, multiSelect, onChange }: React.PropsWithChildren<IDataFileFieldProps>) => {
const [ dataEntries, setDataEntries ] = useState<string[] | null>(null);
const [ crntSelected, setCrntSelected ] = React.useState<string | string[] | null>();
const dsRef = React.useRef<Downshift<string> | null>(null);
const messageListener = (message: MessageEvent<EventData<any>>) => {
const { command, data } = message.data;
if (command === Command.dataFileEntries) {
setDataEntries(data || null);
}
};
const onValueChange = useCallback((txtValue: string) => {
if (multiSelect) {
const newValue = [...(crntSelected || []) as string[], txtValue];
setCrntSelected(newValue);
onChange(newValue);
} else {
setCrntSelected(txtValue);
onChange(txtValue);
}
}, [crntSelected, multiSelect, onChange]);
const removeSelected = useCallback((txtValue: string) => {
if (multiSelect) {
const newValue = [...(crntSelected || [])].filter(v => v !== txtValue);
setCrntSelected(newValue);
onChange(newValue);
} else {
setCrntSelected("");
onChange("");
}
}, [crntSelected, multiSelect, onChange]);
const allChoices = useMemo(() => {
if (dataEntries && dataFileKey) {
return dataEntries.map((r: any) => ({
id: r[dataFileKey],
title: r[dataFileValue || dataFileKey] || r[dataFileKey]
})).filter(r => r.id);
}
return [];
}, [crntSelected, dataEntries, dataFileKey, dataFileValue]);
const availableChoices = useMemo(() => {
if (allChoices) {
return allChoices.filter(choice => {
if (choice) {
if (typeof crntSelected === 'string') {
return crntSelected !== choice.id;
} else if (crntSelected instanceof Array) {
return crntSelected.indexOf(choice.id) === -1;
}
return true;
}
return false;
});
}
return [];
}, [allChoices]);
const getChoiceValue = useCallback((id: string) => {
const choice = allChoices.find(r => r.id === id);
if (choice) {
return choice.title;
}
return "";
}, [allChoices]);
useEffect(() => {
if (selected) {
if (multiSelect) {
setCrntSelected(typeof selected === 'string' ? [selected] : selected);
return;
} else {
setCrntSelected(selected instanceof Array ? selected[0] : selected);
return;
}
}
setCrntSelected(multiSelect ? [] : "");
}, [selected, multiSelect]);
useEffect(() => {
if (dataFileId) {
Messenger.send(CommandToCode.getDataEntries, dataFileId);
}
}, [dataFileId]);
useEffect(() => {
Messenger.listen(messageListener);
return () => {
Messenger.unlisten(messageListener);
}
}, []);
return (
<div className={`metadata_field`}>
<VsLabel>
<div className={`metadata_field__label`}>
<DatabaseIcon style={{ width: "16px", height: "16px" }} /> <span style={{ lineHeight: "16px"}}>{label}</span>
</div>
</VsLabel>
<Downshift
ref={dsRef}
onSelect={(selected) => onValueChange(selected || "")}
itemToString={item => (item ? item : '')}>
{({ getToggleButtonProps, getItemProps, getMenuProps, isOpen, getRootProps }) => (
<div {...getRootProps(undefined, {suppressRefError: true})} className={`metadata_field__choice`}>
<button
{...getToggleButtonProps({
className: `metadata_field__choice__toggle`,
disabled: availableChoices.length === 0
})}>
<span>{`Select ${label}`}</span>
<ChevronDownIcon className="icon" />
</button>
<ul className={`metadata_field__choice_list ${isOpen ? "open" : "closed" }`} {...getMenuProps()}>
{
isOpen ? availableChoices.map((choice, index) => (
<li {...getItemProps({
key: choice.id,
index,
item: choice.id,
})}>
{ choice.title || <span className={`metadata_field__choice_list__item`}>Clear value</span> }
</li>
)) : null
}
</ul>
</div>
)}
</Downshift>
{
crntSelected instanceof Array ? crntSelected.map((value: string) => (
<ChoiceButton
key={value}
value={value}
title={getChoiceValue(value)}
onClick={removeSelected} />
)) : (
crntSelected && (
<ChoiceButton
key={crntSelected}
value={crntSelected}
title={getChoiceValue(crntSelected)}
onClick={removeSelected} />
)
)
}
</div>
);
};
@@ -1,8 +1,8 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { DocumentIcon, PaperClipIcon, TrashIcon } from '@heroicons/react/outline';
import { basename } from 'path';
import * as React from 'react';
import { useCallback, useMemo } from 'react';
import { MessageHelper } from '../../../helpers/MessageHelper';
import { BlockFieldData } from '../../../models';
import { CommandToCode } from '../../CommandToCode';
import { VsLabel } from '../VscodeComponents';
@@ -38,7 +38,7 @@ const File = ({ value, onRemove }: { value: string, onRemove: (value: string) =>
export const FileField: React.FunctionComponent<IFileFieldProps> = ({ label, multiple, filePath, fileExtensions, fieldName, value, parents, blockData, onChange }: React.PropsWithChildren<IFileFieldProps>) => {
const selectFile = useCallback(() => {
MessageHelper.sendMessage(CommandToCode.selectFile, {
Messenger.send(CommandToCode.selectFile, {
filePath,
fieldName,
value,
@@ -1,6 +1,6 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { useEffect, useState } from 'react';
import { MessageHelper } from '../../../helpers/MessageHelper';
import { Command } from '../../Command';
import { CommandToCode } from '../../CommandToCode';
import { ImageFallback } from './ImageFallback';
@@ -29,7 +29,7 @@ export const PreviewImage: React.FunctionComponent<IPreviewImageProps> = ({ valu
if (value?.webviewUrl) {
setImgUrl(value.webviewUrl);
} else {
MessageHelper.sendMessage(CommandToCode.getImageUrl, value)
Messenger.send(CommandToCode.getImageUrl, value)
}
}, [value]);
@@ -1,7 +1,7 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import {PhotographIcon} from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback } from 'react';
import { MessageHelper } from '../../../helpers/MessageHelper';
import { BlockFieldData } from '../../../models';
import { CommandToCode } from '../../CommandToCode';
import { VsLabel } from '../VscodeComponents';
@@ -35,7 +35,7 @@ export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps>
}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
const selectImage = useCallback(() => {
MessageHelper.sendMessage(CommandToCode.selectImage, {
Messenger.send(CommandToCode.selectImage, {
filePath: filePath,
fieldName,
value,
@@ -1,9 +1,9 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { useCallback, useEffect, useState } from 'react';
import { DateHelper } from '../../../helpers/DateHelper';
import { MessageHelper } from '../../../helpers/MessageHelper';
import { BlockFieldData, Field, PanelSettings } from '../../../models';
import { Command } from '../../Command';
import { CommandToCode } from '../../CommandToCode';
@@ -17,6 +17,7 @@ import { IMetadata } from '../Metadata';
import { TagPicker } from '../TagPicker';
import { VsLabel } from '../VscodeComponents';
import { ChoiceField } from './ChoiceField';
import { DataFileField } from './DataFileField';
import { DateTimeField } from './DateTimeField';
import { DraftField } from './DraftField';
import { FileField } from './FileField';
@@ -98,7 +99,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
// Check if the field value contains a placeholder
if (value && typeof value === "string" && value.includes(`{{`) && value.includes(`}}`)) {
window.addEventListener('message', listener);
MessageHelper.sendMessage(CommandToCode.updatePlaceholder, {
Messenger.send(CommandToCode.updatePlaceholder, {
field: field.name,
title: metadata["title"],
value
@@ -346,6 +347,19 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
onSubmit={(value) => onSendUpdate(field.name, value, parentFields)} />
</FieldBoundary>
);
} else if (field.type === 'dataFile') {
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<DataFileField
label={field.title || field.name}
dataFileId={field.dataFileId}
dataFileKey={field.dataFileKey}
dataFileValue={field.dataFileValue}
selected={fieldValue as string}
multiSelect={field.multiple}
onChange={(value => onSendUpdate(field.name, value, parentFields))} />
</FieldBoundary>
);
} else {
console.warn(`Unknown field type: ${field.type}`);
return null;
+2 -2
View File
@@ -1,7 +1,7 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { useMemo } from 'react';
import { DEFAULT_FILE_TYPES } from '../../constants/DefaultFileTypes';
import { MessageHelper } from '../../helpers/MessageHelper';
import { CommandToCode } from '../CommandToCode';
import { FileIcon } from './Icons/FileIcon';
import { MarkdownIcon } from './Icons/MarkdownIcon';
@@ -15,7 +15,7 @@ export interface IFileItemProps {
const FileItem: React.FunctionComponent<IFileItemProps> = ({ name, folderName, path }: React.PropsWithChildren<IFileItemProps>) => {
const openFile = () => {
MessageHelper.sendMessage(CommandToCode.openInEditor, path);
Messenger.send(CommandToCode.openInEditor, path);
};
const itemName = useMemo(() => {
@@ -1,12 +1,12 @@
import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../../helpers/MessageHelper';
import { useDebounce } from '../../hooks/useDebounce';
import { Collapsible } from './Collapsible';
import { VsLabel } from './VscodeComponents';
import useStartCommand from '../hooks/useStartCommand';
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
import { Messenger } from '@estruyf/vscode/dist/client';
export interface IGlobalSettingsProps {
settings: PanelSettings | undefined;
@@ -24,11 +24,11 @@ const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings
const debouncePreviewUrl = useDebounce(previewUrl, 1000);
const onDateCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateModifiedUpdating, !modifiedDateUpdate);
Messenger.send(CommandToCode.updateModifiedUpdating, !modifiedDateUpdate);
};
const onHighlightCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateFmHighlight, !fmHighlighting);
Messenger.send(CommandToCode.updateFmHighlight, !fmHighlighting);
};
const previewChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -54,14 +54,14 @@ const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings
React.useEffect(() => {
if (isDirty) {
setIsDirty(false);
MessageHelper.sendMessage(CommandToCode.updatePreviewUrl, debouncePreviewUrl);
Messenger.send(CommandToCode.updatePreviewUrl, debouncePreviewUrl);
}
}, [debouncePreviewUrl]);
React.useEffect(() => {
if (isDirty) {
setIsDirty(false);
MessageHelper.sendMessage(CommandToCode.updateStartCommand, debounceStartCommand);
Messenger.send(CommandToCode.updateStartCommand, debounceStartCommand);
}
}, [debounceStartCommand]);
+2 -2
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import { BlockFieldData, Field, PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../../helpers/MessageHelper';
import { TagType } from '../TagType';
import { Collapsible } from './Collapsible';
import "react-datepicker/dist/react-datepicker.css";
@@ -10,6 +9,7 @@ import { WrapperField } from './Fields/WrapperField';
import { ContentTypeValidator } from './ContentType/ContentTypeValidator';
import { FeatureFlag } from '../../components/features/FeatureFlag';
import { FEATURE_FLAG } from '../../constants';
import { Messenger } from '@estruyf/vscode/dist/client';
export interface IMetadata {
[prop: string]: string[] | string | null | IMetadata;
@@ -30,7 +30,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, features,
return;
}
MessageHelper.sendMessage(CommandToCode.updateMetadata, {
Messenger.send(CommandToCode.updateMetadata, {
field,
parents,
value
+7 -7
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../../helpers/MessageHelper';
import { Collapsible } from './Collapsible';
import { BugIcon } from './Icons/BugIcon';
import { CenterIcon } from './Icons/CenterIcon';
@@ -12,6 +11,7 @@ import { TemplateIcon } from './Icons/TemplateIcon';
import { WritingIcon } from './Icons/WritingIcon';
import { OtherActionButton } from './OtherActionButton';
import { ISSUE_LINK } from '../../constants/Links';
import { Messenger } from '@estruyf/vscode/dist/client';
export interface IOtherActionsProps {
isFile: boolean;
@@ -22,23 +22,23 @@ export interface IOtherActionsProps {
const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFile, settings, isBase}: React.PropsWithChildren<IOtherActionsProps>) => {
const openSettings = () => {
MessageHelper.sendMessage(CommandToCode.openSettings);
Messenger.send(CommandToCode.openSettings);
};
const openFile = () => {
MessageHelper.sendMessage(CommandToCode.openFile);
Messenger.send(CommandToCode.openFile);
};
const openProject = () => {
MessageHelper.sendMessage(CommandToCode.openProject);
Messenger.send(CommandToCode.openProject);
};
const createAsTemplate = () => {
MessageHelper.sendMessage(CommandToCode.createTemplate);
Messenger.send(CommandToCode.createTemplate);
};
const toggleWritingSettings = () => {
MessageHelper.sendMessage(CommandToCode.toggleWritingSettings);
Messenger.send(CommandToCode.toggleWritingSettings);
};
return (
@@ -46,7 +46,7 @@ const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFile, sett
<Collapsible id={`${isBase ? "base_" : ""}other_actions`} title="Other actions" className={`other_actions`}>
<OtherActionButton className={settings?.writingSettingsEnabled ? "active" : ""} onClick={toggleWritingSettings} disabled={typeof settings?.writingSettingsEnabled === "undefined"}><WritingIcon /> <span>{settings?.writingSettingsEnabled ? "Writing settings enabled" : "Enable writing settings"}</span></OtherActionButton>
<OtherActionButton onClick={() => MessageHelper.sendMessage(CommandToCode.toggleCenterMode)}>
<OtherActionButton onClick={() => Messenger.send(CommandToCode.toggleCenterMode)}>
<CenterIcon /> <span>Toggle center mode</span>
</OtherActionButton>
+2 -2
View File
@@ -1,5 +1,5 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { MessageHelper } from '../../helpers/MessageHelper';
import { CommandToCode } from '../CommandToCode';
import { ActionButton } from './ActionButton';
@@ -10,7 +10,7 @@ export interface IPreviewProps {
const Preview: React.FunctionComponent<IPreviewProps> = ({slug}: React.PropsWithChildren<IPreviewProps>) => {
const open = () => {
MessageHelper.sendMessage(CommandToCode.openPreview);
Messenger.send(CommandToCode.openPreview);
};
if (!slug) {
@@ -1,7 +1,7 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { MessageHelper } from '../../helpers/MessageHelper';
import { CommandToCode } from '../CommandToCode';
import { ActionButton } from './ActionButton';
@@ -13,7 +13,7 @@ const PublishAction: React.FunctionComponent<IPublishActionProps> = (props: Reac
const { draft } = props;
const publish = () => {
MessageHelper.sendMessage(CommandToCode.publish);
Messenger.send(CommandToCode.publish);
};
return (
+2 -2
View File
@@ -1,5 +1,5 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { MessageHelper } from '../../helpers/MessageHelper';
import { CommandToCode } from '../CommandToCode';
import { ActionButton } from './ActionButton';
@@ -8,7 +8,7 @@ export interface ISlugActionProps {}
const SlugAction: React.FunctionComponent<ISlugActionProps> = ({}: React.PropsWithChildren<ISlugActionProps>) => {
const optimize = () => {
MessageHelper.sendMessage(CommandToCode.updateSlug);
Messenger.send(CommandToCode.updateSlug);
};
return (
@@ -1,6 +1,5 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { FrameworkDetectors } from '../../constants/FrameworkDetectors';
import { MessageHelper } from '../../helpers/MessageHelper';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import useStartCommand from '../hooks/useStartCommand';
@@ -13,7 +12,7 @@ export const StartServerButton: React.FunctionComponent<IStartServerButtonProps>
const { startCommand } = useStartCommand(settings);
const startLocalServer = (command: string) => {
MessageHelper.sendMessage(CommandToCode.frameworkCommand, { command });
Messenger.send(CommandToCode.frameworkCommand, { command });
};
return (
+8 -8
View File
@@ -6,9 +6,9 @@ import { TagType } from '../TagType';
import Downshift from 'downshift';
import { AddIcon } from './Icons/AddIcon';
import { VsLabel } from './VscodeComponents';
import { MessageHelper } from '../../helpers/MessageHelper';
import { BlockFieldData, CustomTaxonomyData } from '../../models';
import { useCallback, useMemo } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
export interface ITagPickerProps {
type: TagType;
@@ -52,11 +52,11 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
*/
const onCreate = (tag: string) => {
if (type === TagType.tags) {
MessageHelper.sendMessage(CommandToCode.addTagToSettings, tag);
Messenger.send(CommandToCode.addTagToSettings, tag);
} else if (type === TagType.categories) {
MessageHelper.sendMessage(CommandToCode.addCategoryToSettings, tag);
Messenger.send(CommandToCode.addCategoryToSettings, tag);
} else if (type === TagType.custom) {
MessageHelper.sendMessage(CommandToCode.addToCustomTaxonomy, {
Messenger.send(CommandToCode.addToCustomTaxonomy, {
id: taxonomyId,
name: fieldName,
option: tag
@@ -70,26 +70,26 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsW
*/
const sendUpdate = (values: string[]) => {
if (type === TagType.tags) {
MessageHelper.sendMessage(CommandToCode.updateTags, {
Messenger.send(CommandToCode.updateTags, {
fieldName,
values,
parents,
blockData
});
} else if (type === TagType.categories) {
MessageHelper.sendMessage(CommandToCode.updateCategories, {
Messenger.send(CommandToCode.updateCategories, {
fieldName,
values,
parents,
blockData
});
} else if (type === TagType.keywords) {
MessageHelper.sendMessage(CommandToCode.updateKeywords, {
Messenger.send(CommandToCode.updateKeywords, {
values,
parents
});
} else if (type === TagType.custom) {
MessageHelper.sendMessage(CommandToCode.updateCustomTaxonomy, {
Messenger.send(CommandToCode.updateCustomTaxonomy, {
id: taxonomyId,
name: fieldName,
options: values,
+4 -6
View File
@@ -1,14 +1,12 @@
import { useState, useEffect } from 'react';
import { GeneralCommands } from '../../constants';
import { MessageHelper } from '../../helpers/MessageHelper';
import { Mode } from '../../models/Mode';
import { DashboardData } from '../../models/DashboardData';
import { FolderInfo, PanelSettings } from '../../models/PanelSettings';
import { Command } from '../Command';
import { CommandToCode } from '../CommandToCode';
import { TagType } from '../TagType';
const vscode = MessageHelper.getVsCodeAPI();
import { Messenger } from '@estruyf/vscode/dist/client';
export default function useMessages() {
const [metadata, setMetadata] = useState<any>({});
@@ -46,7 +44,7 @@ export default function useMessages() {
case Command.mediaSelectionData:
setMediaSelecting(message.data);
break;
case GeneralCommands.setMode:
case GeneralCommands.toWebview.setMode:
setMode(message.data);
break;
}
@@ -68,8 +66,8 @@ export default function useMessages() {
setLoading(false);
}, 5000);
vscode.postMessage({ command: CommandToCode.getData });
vscode.postMessage({ command: CommandToCode.getMode });
Messenger.send(CommandToCode.getData);
Messenger.send(CommandToCode.getMode);
}, []);
return {
+39 -2
View File
@@ -16,8 +16,13 @@ export interface ParsedFrontMatter {
export class FrontMatterParser {
public static currentContent: string | null = null;
/**
* Convert the current content to a Front Matter object
* @param content
* @returns
*/
public static fromFile(content: string): ParsedFrontMatter {
const format = getFormatOpts(this.getLanguage());
const format = getFormatOpts(this.getLanguageFromContent(content));
FrontMatterParser.currentContent = content;
const result = matter(content, { ...Engines, ...format });
// in the absent of a body when serializing an entry we use an empty one
@@ -29,13 +34,21 @@ export class FrontMatterParser {
};
}
/**
* Convert the Front Matter object to text
* @param content
* @param metadata
* @param options
* @returns
*/
public static toFile(
content: string,
metadata: Object,
originalContent?: string,
options?: any
) {
// Stringify to YAML if the format was not set
const format = getFormatOpts(this.getLanguage());
const format = getFormatOpts(this.getLanguageFromContent(originalContent));
const trimLastLineBreak = content.slice(-1) !== '\n';
const file = matter.stringify(content, metadata, {
@@ -46,6 +59,30 @@ export class FrontMatterParser {
return trimLastLineBreak && file.slice(-1) === '\n' ? file.substring(0, file.length - 1) : file;
}
/**
* Validate the type of front matter language that is used
* @param contents
*/
public static getLanguageFromContent(contents: string | undefined) {
if (!contents) {
return this.getLanguage();
}
if (contents.startsWith(`+++`)) {
return "toml";
} else if (contents.startsWith(`---`)) {
return "yaml";
} else if (contents.startsWith(`{`)) {
return "json";
} else {
return "yaml";
}
}
/**
* Get the front matter language type
* @returns
*/
private static getLanguage() {
const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML";
return language.toLowerCase();
+12 -6
View File
@@ -4,6 +4,7 @@ import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, Fold
import { SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_FRONTMATTER_TYPE } from '../constants';
import { Settings } from '../helpers';
import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider';
import { FrontMatterParser } from '../parsers';
export class MarkdownFoldingProvider implements FoldingRangeProvider {
private static start: number | null = null;
@@ -43,7 +44,7 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider {
if (isSupported) {
const fmHighlight = Settings.get<boolean>(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT);
const range = this.getFrontMatterRange();
const range = MarkdownFoldingProvider.getFrontMatterRange();
if (range) {
if (MarkdownFoldingProvider.decType !== null) {
@@ -64,17 +65,22 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider {
* @returns
*/
public static getFrontMatterRange(document?: TextDocument) {
const language = Settings.get(SETTING_FRONTMATTER_TYPE) as string || "YAML";
const content = document?.getText();
const language = FrontMatterParser.getLanguageFromContent(content);
let lineStart = "---";
let lineEnd = "---";
if (language === "TOML") {
let lineEnd = lineStart;
if (language.toLowerCase() === "toml") {
lineStart = "+++";
lineEnd = lineStart;
} else if (language.toLowerCase() === "json") {
lineStart = "{";
lineEnd = "}";
}
if (document) {
const lines = document.getText().split('\n');
if (content) {
const lines = content.split('\n');
let start = null;
let end = null;