Preparing the 1.10.0 release

This commit is contained in:
Elio Struyf
2020-12-03 09:40:39 +01:00
parent 12002ebea0
commit 04a4d2cdd6
15 changed files with 148 additions and 46 deletions

View File

@@ -1,5 +1,9 @@
# Change Log
## [1.10.0] - 2020-12-03
- FrontMatter panel implemented. This panel allows you to control all extension actions, but not only that. It makes adding tags and categories in a easier way to your page.
## [1.9.0] - 2020-11-25
- [#23](https://github.com/estruyf/vscode-front-matter/issues/23): Implemented the option to create and use templates for article creation (front matter will be updates as well).

View File

@@ -10,6 +10,18 @@ The extension will automatically verify if your title and description are SEO co
> If you see something missing in your article creation flow, please feel free to reach out.
## FrontMatter Panel (introduced in 1.10.0)
In version `1.10.0` of this extension, the FrontMatter panel got introduced. This panel allows you to perform most of the extension actions by just a click on the button.
![FrontMatter Panel](./assets/frontmatter-panel.png)
Originally this panel was created to make it easier to add tags and categories to your articles. As the current vscode multi-select is not optimal to use.
To leverage most of the capabilities of the extension. SEO information and common actions like slug optimization, updating the date and publish/drafting the article.
> **Info**: By default the tags/categories picker allows you to insert none existing tags/categories. When you enter a none existing tag/category, the panel shows an add `+` icon in front of that button. This allows you to store this tag/category to your settings. If you want to disable this feature, you can do that by setting the `frontMatter.panel.freeform` setting to `false`.
## Creating articles from templates
By default, the extension looks for files stored in a `.templates` folder which should be located in the root of your website project.

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,23 +0,0 @@
(function () {
const vscode = acquireVsCodeApi();
window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case 'addColor':
addColor();
break;
case 'clearColors':
colors = [];
updateColorList(colors);
break;
}
});
window.onload = function() {
vscode.postMessage({ command: 'get-data' });
console.log('Ready to accept data.');
};
}());

View File

@@ -101,6 +101,10 @@
border: 1px solid rgba(0, 0, 0, .9);
}
.article__tags input {
border: 1px solid var(--vscode-inputValidation-infoBorder);
}
.article__tags ul {
color: var(--vscode-dropdown-foreground);
background-color: var(--vscode-dropdown-background);
@@ -117,6 +121,7 @@
}
.article__tags li[data-focus="true"] {
color: var(--vscode-button-foreground);
background-color: var(--vscode-button-hoverBackground);
}
@@ -128,18 +133,50 @@
margin-top: 1rem;
}
.article__tags__items__btn {
.article__tags__items__item {
display: inline-block;
margin-bottom: .5rem;
margin-right: .5rem;
}
.article__tags__items__item_add,
.article__tags__items__item_delete {
display: inline-block;
width: auto;
}
.article__tags__items__btn span {
.article__tags__items__item svg {
display: inline;
vertical-align: bottom;
}
.article__tags__items__item_delete span {
margin-left: .5rem;
}
.article__tags__items__pill_notexists {
color: var(--vscode-inputValidation-errorForeground);
background-color: var(--vscode-inputValidation-errorBackground);
padding-left: .5rem;
}
.article__tags__items__pill_notexists:hover {
color: var(--vscode-inputValidation-errorForeground);
background-color: var(--vscode-inputValidation-errorBackground);
filter: contrast(60%);
}
.article__tags__items__item_add {
color: var(--vscode-inputValidation-infoForeground);
background-color: var(--vscode-inputValidation-infoBackground);
border-right: 1px solid var(--vscode-inputValidation-infoBorder);
}
.article__tags__items__item_add:hover {
color: var(--vscode-inputValidation-infoForeground);
background-color: var(--vscode-inputValidation-infoBackground);
border-right: 1px solid var(--vscode-inputValidation-infoBorder);
filter: contrast(60%);
}

9
package-lock.json generated
View File

@@ -71,6 +71,15 @@
"react-transition-group": "^4.4.0"
}
},
"@material-ui/icons": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz",
"integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.4.4"
}
},
"@material-ui/lab": {
"version": "4.0.0-alpha.56",
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz",

View File

@@ -146,6 +146,11 @@
"type": "string",
"default": "yyyy-MM-dd",
"description": "Specify the prefix you want to add for your new article filenames."
},
"frontMatter.panel.freeform": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies if you want to allow yourself from entering unknown tags/categories in the tag picker (when enabled, you will have the option to store them afterwards). Default: true."
}
}
},
@@ -217,6 +222,10 @@
"test-compile": "tsc -p ./"
},
"devDependencies": {
"@iarna/toml": "2.2.3",
"@material-ui/core": "4.11.1",
"@material-ui/icons": "4.11.2",
"@material-ui/lab": "4.0.0-alpha.56",
"@types/glob": "7.1.3",
"@types/js-yaml": "3.12.1",
"@types/mocha": "^5.2.6",
@@ -229,16 +238,13 @@
"gray-matter": "4.0.2",
"html-loader": "1.3.2",
"html-webpack-plugin": "4.5.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"ts-loader": "8.0.3",
"tslint": "6.1.3",
"typescript": "4.0.2",
"webpack": "4.44.2",
"webpack-cli": "3.3.12",
"@iarna/toml": "2.2.3",
"@material-ui/core": "4.11.1",
"@material-ui/lab": "4.0.0-alpha.56",
"react": "17.0.1",
"react-dom": "17.0.1"
"webpack-cli": "3.3.12"
},
"dependencies": {}
}

View File

@@ -20,4 +20,6 @@ export const SETTING_SEO_TITLE_LENGTH = "taxonomy.seoTitleLength";
export const SETTING_SEO_DESCRIPTION_LENGTH = "taxonomy.seoDescriptionLength";
export const SETTING_TEMPLATES_FOLDER = "templates.folder";
export const SETTING_TEMPLATES_PREFIX = "templates.prefix";
export const SETTING_TEMPLATES_PREFIX = "templates.prefix";
export const SETTING_PANEL_FREEFORM = "panel.freeform";

View File

@@ -4,6 +4,7 @@ export interface PanelSettings {
slug: Slug;
tags: string[];
categories: string[];
freeform: boolean;
}
export interface SEO {

View File

@@ -4,5 +4,7 @@ export enum CommandToCode {
updateDate = 'update-date',
publish = 'publish',
updateTags = "update-tags",
updateCategories = "update-categories"
updateCategories = "update-categories",
addTagToSettings = "add-tag",
addCategoryToSettings = "add-category"
}

View File

@@ -35,10 +35,10 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
settings && metadata && <Actions metadata={metadata} settings={settings} />
}
{
(settings && settings.tags && settings.tags.length > 0) && <TagPicker type={TagType.tags} crntSelected={metadata.tags || []} options={settings.tags} />
(settings && settings.tags && settings.tags.length > 0) && <TagPicker type={TagType.tags} crntSelected={metadata.tags || []} options={settings.tags} freeform={settings.freeform} />
}
{
(settings && settings.categories && settings.categories.length > 0) && <TagPicker type={TagType.categories} crntSelected={metadata.categories || []} options={settings.categories} />
(settings && settings.categories && settings.categories.length > 0) && <TagPicker type={TagType.categories} crntSelected={metadata.categories || []} options={settings.categories} freeform={settings.freeform} />
}
</div>
);

View File

@@ -1,17 +1,26 @@
import * as React from 'react';
import AddIcon from '@material-ui/icons/Add';
import DeleteIcon from '@material-ui/icons/Delete';
export interface ITagProps {
className: string;
value: string;
title: string;
onCreate?: (tags: string) => void;
onRemove: (tags: string) => void;
}
export const Tag: React.FunctionComponent<ITagProps> = (props: React.PropsWithChildren<ITagProps>) => {
const { value, className, title, onRemove } = props;
const { value, className, title, onRemove, onCreate } = props;
return (
<button title={title} className={`article__tags__items__btn ${className}`} onClick={() => onRemove(value)}>{value} <span>x</span></button>
<div className={`article__tags__items__item`}>
{
onCreate &&
<button title={`Add ${value} to your settings`} className={`article__tags__items__item_add`} onClick={() => onCreate(value)}><AddIcon /></button>
}
<button title={title} className={`article__tags__items__item_delete ${className}`} onClick={() => onRemove(value)}>{value} <span><DeleteIcon /></span></button>
</div>
);
};

View File

@@ -10,10 +10,11 @@ export interface ITagPickerProps {
type: string;
crntSelected: string[];
options: string[];
freeform: boolean;
}
export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React.PropsWithChildren<ITagPickerProps>) => {
const { type, crntSelected, options } = props;
const { type, crntSelected, options, freeform } = props;
const [ selected, setSelected ] = React.useState<string[]>([]);
const prevSelected = usePrevious(crntSelected);
@@ -21,7 +22,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
id: 'use-autocomplete',
options: options,
multiple: true,
autoComplete: true,
freeSolo: freeform,
value: crntSelected,
getOptionDisabled: (option) => selected.includes(option),
onChange: (e, values: string[]) => {
@@ -37,6 +38,11 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
sendUpdate(newSelection);
};
const onCreate = (tag: string) => {
const cmdType = type === TagType.tags ? CommandToCode.addTagToSettings : CommandToCode.addCategoryToSettings;
MessageHelper.sendMessage(cmdType, tag);
};
const sendUpdate = (values: string[]) => {
const cmdType = type === TagType.tags ? CommandToCode.updateTags : CommandToCode.updateCategories;
MessageHelper.sendMessage(cmdType, values);
@@ -66,7 +72,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
) : null
}
<Tags values={selected} onRemove={onRemove} options={options} />
<Tags values={selected} onRemove={onRemove} onCreate={onCreate} options={options} />
</div>
);
};

View File

@@ -5,17 +5,26 @@ export interface ITagsProps {
values: string[];
options: string[];
onCreate: (tags: string) => void;
onRemove: (tags: string) => void;
}
export const Tags: React.FunctionComponent<ITagsProps> = (props: React.PropsWithChildren<ITagsProps>) => {
const { values, options, onRemove } = props;
const { values, options, onCreate, onRemove } = props;
const knownTags = values.filter(v => options.includes(v));
const unknownTags = values.filter(v => !options.includes(v));
return (
<div className={`article__tags__items`}>
{
values.map(t => (
<Tag key={t.replace(/ /g, "_")} value={t} className={`${options.includes(t) ? 'article__tags__items__pill_exists' : 'article__tags__items__pill_notexists'}`} onRemove={onRemove} title={`${options.includes(t) ? `Remove ${t}` : `Be aware, this tag "${t}" is not saved in your settings.`}`} />
knownTags.map(t => (
<Tag key={t.replace(/ /g, "_")} value={t} className={`article__tags__items__pill_exists`} onRemove={onRemove} title={`Remove ${t}`} />
))
}
{
unknownTags.map(t => (
<Tag key={t.replace(/ /g, "_")} value={t} className={`article__tags__items__pill_notexists`} onRemove={onRemove} onCreate={onCreate} title={`Be aware, this tag "${t}" is not saved in your settings. Once removed, it will be gone forever.`} />
))
}
</div>

View File

@@ -1,11 +1,12 @@
import { PanelSettings } from './../models/PanelSettings';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace } from "vscode";
import { CONFIG_KEY, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../constants";
import { ArticleHelper } from "../helpers";
import { CONFIG_KEY, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../constants";
import { ArticleHelper, SettingsHelper } from "../helpers";
import { Command } from "../viewpanel/Command";
import { CommandToCode } from '../viewpanel/CommandToCode';
import { Article } from '../commands';
import { TagType } from '../viewpanel/TagType';
import { TaxonomyType } from '../models';
export class ExplorerView implements WebviewViewProvider, Disposable {
@@ -88,6 +89,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
case CommandToCode.updateCategories:
this.updateTags(TagType.categories, msg.data || []);
break;
case CommandToCode.addTagToSettings:
this.addTags(TagType.tags, msg.data);
break;
case CommandToCode.addCategoryToSettings:
this.addTags(TagType.categories, msg.data);
break;
}
});
@@ -135,7 +142,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
suffix: config.get(SETTING_SLUG_SUFFIX) || ""
},
tags: config.get(SETTING_TAXONOMY_TAGS) || [],
categories: config.get(SETTING_TAXONOMY_CATEGORIES) || []
categories: config.get(SETTING_TAXONOMY_CATEGORIES) || [],
freeform: config.get(SETTING_PANEL_FREEFORM)
} as PanelSettings
});
}
@@ -172,6 +180,26 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
/**
* Add tag to the settings
* @param tagType
* @param value
*/
private async addTags(tagType: TagType, value: string) {
if (value) {
const config = workspace.getConfiguration(CONFIG_KEY);
let options = tagType === TagType.tags ? config.get<string[]>(SETTING_TAXONOMY_TAGS) : config.get<string[]>(SETTING_TAXONOMY_CATEGORIES);
if (!options) {
options = [];
}
options.push(value);
const taxType = tagType === TagType.tags ? TaxonomyType.Tag : TaxonomyType.Category;
await SettingsHelper.update(taxType, options);
}
}
/**
* Post data to the panel
* @param msg