Compare commits

...

19 Commits

Author SHA1 Message Date
Elio Struyf
fdfdfda6ce 2.4.1 2021-08-16 14:30:18 +02:00
Elio Struyf
f016ae27ec Better highlight 2021-08-16 14:30:10 +02:00
Elio Struyf
c9d3eca431 Merge branch 'main' of github.com:estruyf/vscode-front-matter into main 2021-08-16 12:30:29 +02:00
Elio Struyf
486beaf650 2.4.0 2021-08-16 12:30:17 +02:00
Elio Struyf
6e82cf221e Updated changelog 2021-08-16 12:30:10 +02:00
Elio Struyf
46b9591859 Merge pull request #54 from estruyf/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2021-08-16 12:29:40 +02:00
Elio Struyf
3e76da58f5 Updated documentation 2021-08-16 12:28:55 +02:00
Elio Struyf
9d42bd2f97 Update highlighting + documentation 2021-08-16 11:38:51 +02:00
Elio Struyf
75890ec6e8 #55 #56 #57 #58 #59 - multiple enhancements that are linked 2021-08-13 13:33:05 +02:00
Elio Struyf
fb0429e40f Updated changelog 2021-08-12 17:24:17 +02:00
Elio Struyf
8db813a661 #21 - Implemented the folding provider for front matter 2021-08-12 17:15:08 +02:00
dependabot[bot]
2655be9aae Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-11 09:39:10 +00:00
Elio Struyf
fc99756136 Merge branch 'main' of github.com:estruyf/vscode-front-matter into main 2021-08-10 20:09:53 +02:00
Elio Struyf
9159a98dbe Fix image URL 2021-08-10 20:09:46 +02:00
Elio
310f8e4a5e 2.3.0 2021-08-10 15:10:54 +02:00
Elio
d79f3416a3 updated changelog 2021-08-10 15:10:49 +02:00
Elio Struyf
e194928291 #53 - Add save as template 2021-08-09 17:05:40 +02:00
Elio Struyf
32aa8f4223 #31 - Better change detection 2021-08-09 14:46:30 +02:00
Elio Struyf
c88d6be1d7 #31 - implementation of auto-update 2021-08-09 10:47:13 +02:00
48 changed files with 732 additions and 127 deletions

View File

@@ -1,5 +1,25 @@
# Change Log
## [2.4.1] - 2020-08-16
- Better editor highlighting functionality
## [2.4.0] - 2020-08-16
- [#21](https://github.com/estruyf/vscode-front-matter/issues/21): Folding provider for Front Matter implemented
- [#55](https://github.com/estruyf/vscode-front-matter/issues/55): Highlight Front Matter in Markdown files
- [#56](https://github.com/estruyf/vscode-front-matter/issues/56): Action to collapse all Front Matter panel sections at once
- [#57](https://github.com/estruyf/vscode-front-matter/issues/57): New action added to provide better writing settings (only for Markdown files)
- [#58](https://github.com/estruyf/vscode-front-matter/issues/58): Sections remember their previous state (folded/unfolded)
- [#59](https://github.com/estruyf/vscode-front-matter/issues/59): Center layout view toggle action added
## [2.3.0] - 2020-08-10
- Refactoring and showing other actions in the base view
- Show `BaseView` in Front Matter panel when switching to `welcome` tab
- [#31](https://github.com/estruyf/vscode-front-matter/issues/31): Automatically update the last modification date of the file when performing changes
- [#53](https://github.com/estruyf/vscode-front-matter/issues/53): Create current Markdown file as template
## [2.2.0] - 2020-08-06
- [#28](https://github.com/estruyf/vscode-front-matter/issues/28): Align the file its name with the article slug

View File

@@ -35,6 +35,7 @@ In version v2.0.0 we released the newly redesigned sidebar panel with improved S
<details open="open">
<summary>Table of Contents</summary>
<ol>
<li><a href="#markdown-features">Markdown features</a></li>
<li><a href="#the-panel">The panel</a></li>
<li><a href="#custom-actions">Custom actions</a></li>
<li><a href="#creating-articles-from-templates">Create articles from templates</a></li>
@@ -45,6 +46,24 @@ In version v2.0.0 we released the newly redesigned sidebar panel with improved S
</ol>
</details>
## Markdown features
The Front Matter extension tries to make it easy to manage your Markdown pages/content. Within a Markdown page, we allow you to fold the file's Front Matter to be less distracting when writing. Also, do we highlight the Front Matter content to create a visual difference between content and metadata.
### Front Matter folding
<p align="center">
<img src="./assets/v2.4.0/folding.png" alt="Front Matter folding" style="display: inline-block" />
</p>
### Front Matter highlighting
<p align="center">
<img src="./assets/v2.4.0/fm-highlight.png" alt="Front Matter highlighting" style="display: inline-block" />
</p>
> **Info**: If you do not want this feature, you can disable it in the extension settings -> `Highlight Front Matter` or by setting the `frontMatter.content.fmHighlight` setting to `false`.
## The panel
The Front Matter panel allows you to perform most of the extension actions by just a click on the button and it shows the SEO statuses of your title, description, and more.
@@ -53,14 +72,22 @@ Initially, this panel has been created to make it easier to add tags and categor
To leverage most of the capabilities of the extension. SEO information and everyday actions like slug optimization, updating the date, and publish/drafting the article.
When the panel opens on a none markdown file, it will contain the following sections:
When you open the panel and the current file is not a Markdown file, it will contain the following sections:
<p align="center">
<img src="./assets/v2.2.0/baseview.png" alt="Base view" style="display: inline-block" />
<img src="./assets/v2.4.0/baseview.png" alt="Base view" style="display: inline-block" />
</p>
> **Info**: both **Global Settings** and **Other Actions** sections are shown for the base view as when a Markdown file is openend.
When you open the Front Matter panel on a Markdown file, you get to see the following sections:
**Global Settings**
<p align="center">
<img src="./assets/v2.4.0/global-settings.png" alt="Global settings" style="display: inline-block" />
</p>
**SEO Status**
<p align="center">
@@ -70,7 +97,7 @@ When you open the Front Matter panel on a Markdown file, you get to see the foll
**Actions**
<p align="center">
<img src="./assets/v2.0.0/actions.png" alt="Actions" style="display: inline-block" />
<img src="./assets/v2.4.0/actions.png" alt="Actions" style="display: inline-block" />
</p>
**Metadata: Keywords, Tags, Categories**
@@ -83,12 +110,12 @@ When you open the Front Matter panel on a Markdown file, you get to see the foll
**Other actions**
At the bottom of the panel you can find the following actions:
<p align="center">
<img src="./assets/v2.0.0/other-actions.png" alt="Other actions" style="display: inline-block" />
<img src="./assets/v2.4.0/other-actions.png" alt="Other actions" style="display: inline-block" />
</p>
**Info**: The `Enable write settings` action allow you to make Markdown specific changes to optimize the writing of your articles. It will change settings like the `fontSize`, `lineHeight`, `wordWrap`, `lineNumbers` and more.
## Custom actions
Since version `1.15.0`, the extension allows you to create your own custom actions, by running Node.js scripts from your project. In order to use this functionality, you will need to configure the [`frontMatter.custom.scripts`](#frontmattercustomscripts) setting for your project.
@@ -143,14 +170,16 @@ When adding files in the folder, you'll be able to run the `Front Matter: New ar
This command will initialize the project with a template folder and an article template. It makes it easier to get you started with the extension and creating your content.
**Front Matter: Create content**
**Front Matter: Create a template from current file**
This command allows you to create a new template from the current open Markdown file. It will ask you for the name of the template and if you want to keep the current file its content in the template.
> **Info**: The create as template action is also available from the `other actions` section in the Front Matter panel.
**Front Matter: New article from template**
With this command, you can easily create content in your project within the registered folders and provided templates.
<p align="center">
<img src="./assets/v2.1.0/create-content.png" alt="Create content" style="display: inline-block" />
</p>
You can register and unregister folders by right-clicking on the folder in your VSCode explorer panel.
<p align="center">
@@ -193,7 +222,7 @@ Update the `date` property of the current article/post/... to the current date &
**Front Matter: Set lastmod date**
Update the `lastmod` (last modified) property of the current article/post/... to the current date & time.
Update the `lastmod` (last modified) property of the current article/post/... to the current date & time. By setting the `frontMatter.content.autoUpdateDate` setting, it can be done automatically when performing changes to your markdown files.
> **note**: Uses the same date format settings key as current date: `frontMatter.taxonomy.dateFormat`.
@@ -355,6 +384,26 @@ This array of folders defines where the extension can easily create new content
> **Important**: This setting can be configured by right-clicking on a folder in the VSCode file explorer view and clicking on the `Front Matter: Register folder` menu item.
### `frontMatter.content.autoUpdateDate`
Specify if you want to automatically update the modification date of your markdown page when doing changes to it. Default: `false`.
```json
{
"frontMatter.content.autoUpdateDate": false
}
```
### `frontMatter.content.fmHighlight`
Specify if you want to highlight the Front Matter in the Markdown file. Default: `true`.
```json
{
"frontMatter.content.fmHighlight": true
}
```
## Feedback / issues / ideas
Please submit them via creating an issue in the project repository: [issue list](https://github.com/estruyf/vscode-front-matter/issues).

View File

@@ -0,0 +1,4 @@
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#F3EFF5">
<path d="M9 9H4v1h5V9z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3l1-1h7l1 1v7l-1 1h-2v2l-1 1H3l-1-1V6l1-1h2V3zm1 2h4l1 1v4h2V3H6v2zm4 1H3v7h7V6z" />
</svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@@ -0,0 +1,4 @@
<svg width="32px" height="32px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentcolor">
<path d="M9 9H4v1h5V9z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3l1-1h7l1 1v7l-1 1h-2v2l-1 1H3l-1-1V6l1-1h2V3zm1 2h4l1 1v4h2V3H6v2zm4 1H3v7h7V6z" />
</svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@@ -21,10 +21,21 @@
}
}
.relative {
position: relative !important;
}
.absolute {
position: absolute !important;
}
.inherit {
position: inherit !important;
}
.z-10 { z-index: 10 !important; }
.z-20 { z-index: 10 !important; }
.w-full {
width: 100% !important;
}
@@ -249,6 +260,7 @@
}
.article__actions > * + *,
.other_actions > * + *,
.base__actions > * + *,
.base__information > * + * {
--tw-space-y-reverse: 0;
@@ -266,10 +278,21 @@
.ext_link_block {
display: flex;
align-items: center;
width: 100%;
}
.ext_link_block svg {
margin-right: .5rem;
display: block;
width: 16px;
height: 16px;
min-width: 16px;
}
.ext_link_block button span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ext_link_block button,
@@ -288,7 +311,17 @@
padding: 0px 14px;
user-select: none;
text-decoration: none;
width: auto;
width: 100%;
white-space: nowrap;
}
.ext_link_block button.active {
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
}
.ext_link_block button.active:hover {
cursor: pointer;
background: var(--vscode-button-hoverBackground);
}
.ext_link_block a:hover,

BIN
assets/v2.3.0/baseview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/v2.4.0/actions.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
assets/v2.4.0/baseview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
assets/v2.4.0/folding.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "vscode-front-matter",
"version": "2.2.0",
"version": "2.4.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3565,9 +3565,9 @@
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"pbkdf2": {

View File

@@ -3,7 +3,7 @@
"displayName": "Front Matter",
"description": "Simplifies working with front matter of your articles. Useful extension when you are using a static site generator like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
"icon": "assets/front-matter.png",
"version": "2.2.0",
"version": "2.4.1",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -57,6 +57,7 @@
"onCommand:frontMatter.unregisterFolder",
"onCommand:frontMatter.createContent",
"onCommand:frontMatter.init",
"onCommand:frontMatter.collapseSections",
"onView:frontMatter.explorer"
],
"main": "./dist/extension",
@@ -84,6 +85,31 @@
"configuration": {
"title": "Front Matter: Configuration",
"properties": {
"frontMatter.content.autoUpdateDate": {
"type": "boolean",
"default": false,
"description": "Specify if you want to automatically update the modified date of your article/page."
},
"frontMatter.content.fmHighlight": {
"type": "boolean",
"default": true,
"description": "Specify if you want to highlight the Front Matter in the Markdown file."
},
"frontMatter.content.folders": {
"type": "array",
"default": [],
"markdownDescription": "This array of folders defines where the extension can easily create new content by running the create article command."
},
"frontMatter.custom.scripts": {
"type": "array",
"default": [],
"markdownDescription": "Specify the path to a Node.js script to execute. The current file path will be provided as an argument."
},
"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."
},
"frontMatter.taxonomy.dateField": {
"type": "string",
"default": "date",
@@ -170,21 +196,6 @@
"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."
},
"frontMatter.custom.scripts": {
"type": "array",
"default": [],
"markdownDescription": "Specify the path to a Node.js script to execute. The current file path will be provided as an argument."
},
"frontMatter.content.folders": {
"type": "array",
"default": [],
"markdownDescription": "This array of folders defines where the extension can easily create new content by running the create article command."
}
}
},
@@ -251,13 +262,27 @@
},
{
"command": "frontMatter.createContent",
"title": "Create content",
"title": "New article from template",
"category": "Front matter"
},
{
"command": "frontMatter.init",
"title": "Initialize project",
"category": "Front matter"
},
{
"command": "frontMatter.createTemplate",
"title": "Create a template from current file",
"category": "Front matter"
},
{
"command": "frontMatter.collapseSections",
"title": "Collapse sections",
"category": "Front matter",
"icon": {
"light": "assets/icons/close-light.svg",
"dark": "assets/icons/close-dark.svg"
}
}
],
"menus": {
@@ -282,6 +307,20 @@
{
"command": "frontMatter.init",
"when": "frontMatterCanInit"
},
{
"command": "frontMatter.createTemplate",
"when": "!frontMatterCanInit"
},
{
"command": "frontMatter.collapseSections",
"when": "false"
}
],
"view/title": [
{
"command": "frontMatter.collapseSections",
"group": "navigation"
}
]
},

View File

@@ -1,4 +1,4 @@
import { SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX } from './../constants/settings';
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX } from './../constants/settings';
import * as vscode from 'vscode';
import { TaxonomyType } from "../models";
import { CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_DATE_FIELD } from "../constants/settings";
@@ -10,6 +10,7 @@ import { extname, basename } from 'path';
export class Article {
private static prevContent = "";
/**
* Insert taxonomy
@@ -126,16 +127,17 @@ export class Article {
return;
}
const cloneArticle = Object.assign({}, article);
const dateFormat = config.get(SETTING_DATE_FORMAT) as string;
const dateField = config.get(SETTING_MODIFIED_FIELD) as string || "lastmod";
try {
if (dateFormat && typeof dateFormat === "string") {
article.data[dateField] = format(new Date(), dateFormat);
cloneArticle.data[dateField] = format(new Date(), dateFormat);
} else {
article.data[dateField] = new Date().toISOString();
cloneArticle.data[dateField] = new Date().toISOString();
}
ArticleHelper.update(editor, article);
ArticleHelper.update(editor, cloneArticle);
} catch (e) {
Notifications.error(`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
console.log(e.message);
@@ -220,6 +222,35 @@ export class Article {
ArticleHelper.update(editor, article);
}
/**
* Article auto updater
* @param fileChanges
*/
public static async autoUpdate(fileChanges: vscode.TextDocumentChangeEvent) {
const txtChanges = fileChanges.contentChanges.map(c => c.text);
const editor = vscode.window.activeTextEditor;
if (txtChanges.length > 0 && editor && ArticleHelper.isMarkdownFile()) {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const autoUpdate = config.get(SETTING_AUTO_UPDATE_DATE);
if (autoUpdate) {
const article = ArticleHelper.getFrontMatter(editor);
if (!article) {
return;
}
if (article.content === Article.prevContent) {
return;
}
Article.prevContent = article.content;
Article.setLastModifiedDate();
}
}
}
/**
* Update the article date and return it
* @param article

View File

@@ -3,6 +3,7 @@ import { CONFIG_KEY, SETTING_TEMPLATES_FOLDER } from "../constants";
import { join } from "path";
import * as fs from "fs";
import { Notifications } from "../helpers/Notifications";
import { Template } from "./Template";
export class Project {
@@ -22,28 +23,43 @@ categories: []
/**
* Initialize a new "Project" instance.
*/
public static async init() {
public static async init(sampleTemplate: boolean = true) {
try {
const config = workspace.getConfiguration(CONFIG_KEY);
const folder = config.get<string>(SETTING_TEMPLATES_FOLDER);
const folder = Template.getSettings();
const templatePath = Project.templatePath();
const workspaceFolders = workspace.workspaceFolders;
if (!folder || !workspaceFolders || workspaceFolders.length === 0) {
if (!folder || !templatePath) {
return;
}
const workspaceFolder = workspaceFolders[0];
const templatePath = Uri.file(join(workspaceFolder.uri.fsPath, folder));
const article = Uri.file(join(templatePath.fsPath, "article.md"));
await workspace.fs.createDirectory(templatePath);
if (!fs.existsSync(templatePath.fsPath)) {
await workspace.fs.createDirectory(templatePath);
}
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
Notifications.info("Project initialized successfully.");
if (sampleTemplate) {
fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" });
Notifications.info("Project initialized successfully.");
}
} catch (err) {
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
}
}
/**
* Get the template path for the current project
*/
public static templatePath() {
const folder = Template.getSettings();
const workspaceFolders = workspace.workspaceFolders;
if (!folder || !workspaceFolders || workspaceFolders.length === 0) {
return null;
}
const workspaceFolder = workspaceFolders[0];
const templatePath = Uri.file(join(workspaceFolder.uri.fsPath, folder));
return templatePath;
}
}

View File

@@ -17,7 +17,7 @@ export class StatusListener {
const publishMsg = "to publish";
let editor = vscode.window.activeTextEditor;
if (editor && ArticleHelper.isMarkdownDile()) {
if (editor && ArticleHelper.isMarkdownFile()) {
try {
const article = ArticleHelper.getFrontMatter(editor);
@@ -61,6 +61,11 @@ export class StatusListener {
} catch (e) {
// Nothing to do
}
} else {
const panel = ExplorerView.getInstance();
if (panel && panel.visible) {
panel.pushMetadata(null);
}
}
frontMatterSB.hide();

View File

@@ -8,6 +8,7 @@ import { ArticleHelper } from '../helpers';
import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
import { CONTEXT } from '../constants/context';
import { Project } from './Project';
export class Template {
@@ -23,9 +24,8 @@ export class Template {
* Check if the project is already initialized
*/
public static async isInitialized() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const folder = config.get<string>(SETTING_TEMPLATES_FOLDER);
const workspaceFolders = vscode.workspace.workspaceFolders;
const folder = Template.getSettings();
if (!folder || !workspaceFolders || workspaceFolders.length === 0) {
return false;
@@ -42,6 +42,53 @@ export class Template {
}
}
/**
* Generate a template
*/
public static async generate() {
const folder = Template.getSettings();
const editor = vscode.window.activeTextEditor;
if (folder && editor && ArticleHelper.isMarkdownFile()) {
const article = ArticleHelper.getFrontMatter(editor);
const clonedArticle = Object.assign({}, article);
const titleValue = await vscode.window.showInputBox({
prompt: `What name would you like to give your template?`,
placeHolder: `article`
});
if (!titleValue) {
Notifications.warning(`You did not specify a template title.`);
return;
}
const keepContents = await vscode.window.showQuickPick(
["yes", "no"],
{
canPickMany: false,
placeHolder: `Do you want to keep the article its contents for the template?`,
}
);
if (!keepContents) {
Notifications.warning(`You did not pick any of the options for keeping the template its content.`);
return;
}
await Project.init(false);
const templatePath = Project.templatePath();
if (templatePath) {
let fileContents = ArticleHelper.stringifyFrontMatter(keepContents === "no" ? "" : clonedArticle.content, clonedArticle.data);
const templateFile = path.join(templatePath.fsPath, `${titleValue}.md`);
fs.writeFileSync(templateFile, fileContents, { encoding: "utf-8" });
Notifications.info(`Template created and is now available in your ${folder} folder.`);
}
}
}
/**
* Create from a template
*/
@@ -136,4 +183,13 @@ export class Template {
Notifications.info(`Your new article has been created.`);
}
/**
* Get the folder settings
*/
public static getSettings() {
const config = vscode.workspace.getConfiguration(CONFIG_KEY);
const folder = config.get<string>(SETTING_TEMPLATES_FOLDER);
return folder;
}
}

View File

@@ -19,5 +19,7 @@ export const COMMAND_NAME = {
toggleDraft: getCommandName("toggleDraft"),
registerFolder: getCommandName("registerFolder"),
unregisterFolder: getCommandName("unregisterFolder"),
createContent: getCommandName("createContent")
createContent: getCommandName("createContent"),
createTemplate: getCommandName("createTemplate"),
collapseSections: getCommandName("collapseSections"),
};

View File

@@ -29,4 +29,6 @@ export const SETTING_PANEL_FREEFORM = "panel.freeform";
export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
export const SETTINGS_CONTENT_FOLDERS = "content.folders";
export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate";
export const SETTINGS_CONTENT_FOLDERS = "content.folders";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";

View File

@@ -5,13 +5,17 @@ import { Project } from './commands/Project';
import { Template } from './commands/Template';
import { COMMAND_NAME } from './constants/Extension';
import { TaxonomyType } from './models';
import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider';
import { TagType } from './viewpanel/TagType';
import { ExplorerView } from './webview/ExplorerView';
let frontMatterStatusBar: vscode.StatusBarItem;
let debouncer: { (fnc: any, time: number): void; };
let statusDebouncer: { (fnc: any, time: number): void; };
let editDebounce: { (fnc: any, time: number): void; };
let collection: vscode.DiagnosticCollection;
const mdSelector: vscode.DocumentSelector = { language: 'markdown', scheme: 'file' };
export async function activate({ subscriptions, extensionUri }: vscode.ExtensionContext) {
collection = vscode.languages.createDiagnosticCollection('frontMatter');
@@ -22,6 +26,9 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
}
});
// Folding the front matter of markdown files
vscode.languages.registerFoldingRangeProvider(mdSelector, new MarkdownFoldingProvider());
let insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => {
await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer');
await vscode.commands.executeCommand('workbench.action.focusSideBar');
@@ -67,7 +74,9 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
if (folderPath) {
Template.create(folderPath);
}
});
});
let createTemplate = vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate);
const toggleDraftCommand = COMMAND_NAME.toggleDraft;
const toggleDraft = vscode.commands.registerCommand(toggleDraftCommand, async () => {
@@ -88,6 +97,11 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
Template.init();
const projectInit = vscode.commands.registerCommand(COMMAND_NAME.init, Project.init);
// Collapse all sections in the webview
const collapseAll = vscode.commands.registerCommand(COMMAND_NAME.collapseSections, () => {
ExplorerView.getInstance()?.collapseAll();
});
// Things to do when configuration changes
vscode.workspace.onDidChangeConfiguration(() => {
Template.init();
@@ -98,13 +112,22 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
frontMatterStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
frontMatterStatusBar.command = toggleDraftCommand;
subscriptions.push(frontMatterStatusBar);
debouncer = debounceShowDraftTrigger();
statusDebouncer = debounceCallback();
// Register listeners that make sure the status bar updates
subscriptions.push(vscode.window.onDidChangeActiveTextEditor(triggerShowDraftStatus));
subscriptions.push(vscode.window.onDidChangeTextEditorSelection(triggerShowDraftStatus));
// Automatically run the command
triggerShowDraftStatus();
// Listener for file edit changes
editDebounce = debounceCallback();
subscriptions.push(vscode.workspace.onDidChangeTextDocument(triggerFileChange));
// Listener for setting changes
subscriptions.push(vscode.workspace.onDidChangeConfiguration(MarkdownFoldingProvider.triggerHighlighting));
// Subscribe all commands
subscriptions.push(
insertTags,
@@ -118,21 +141,27 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension
setLastModifiedDate,
generateSlug,
createFromTemplate,
createTemplate,
toggleDraft,
registerFolder,
unregisterFolder,
createContent,
projectInit
projectInit,
collapseAll
);
}
export function deactivate() {}
const triggerShowDraftStatus = () => {
debouncer(() => { StatusListener.verify(frontMatterStatusBar, collection); }, 1000);
const triggerFileChange = (e: vscode.TextDocumentChangeEvent) => {
editDebounce(() => Article.autoUpdate(e), 1000);
};
const debounceShowDraftTrigger = () => {
const triggerShowDraftStatus = () => {
statusDebouncer(() => { StatusListener.verify(frontMatterStatusBar, collection); }, 1000);
};
const debounceCallback = () => {
let timeout: NodeJS.Timeout;
return (fnc: any, time: number) => {

View File

@@ -100,7 +100,7 @@ export class ArticleHelper {
/**
* Checks if the current file is a markdown file
*/
public static isMarkdownDile() {
public static isMarkdownFile() {
const editor = vscode.window.activeTextEditor;
return (editor && editor.document && (editor.document.languageId.toLowerCase() === "markdown" || editor.document.languageId.toLowerCase() === "mdx"));
}

View File

@@ -7,7 +7,10 @@ export interface PanelSettings {
freeform: boolean;
scripts: CustomScript[];
isInitialized: boolean;
modifiedDateUpdate: boolean;
contentInfo: FolderInfo[] | null;
writingSettingsEnabled: boolean;
fmHighlighting: boolean;
}
export interface SEO {

View File

@@ -0,0 +1,13 @@
import { TextEditorDecorationType, window, ColorThemeKind } from "vscode";
export class FrontMatterDecorationProvider {
get(): TextEditorDecorationType {
const colorThemeKind = window.activeColorTheme.kind;
return window.createTextEditorDecorationType({
backgroundColor: colorThemeKind === ColorThemeKind.Dark ? "#ffffff14" : "#00000014",
isWholeLine: true,
});
}
}

View File

@@ -0,0 +1,62 @@
import { TextEditorDecorationType, workspace } from 'vscode';
import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, Range, TextDocument, window, Position } from 'vscode';
import { CONFIG_KEY, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT } from '../constants';
import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider';
export class MarkdownFoldingProvider implements FoldingRangeProvider {
private static start: number | null = null;
private static end: number | null = null;
private static endLine: number | null = null;
private static decType: TextEditorDecorationType | null = null;
public async provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken): Promise<FoldingRange[]> {
const ranges: FoldingRange[] = [];
const lines = document.getText().split('\n');
let start: number | null = null;
let end: number | null = null;
MarkdownFoldingProvider.start = null;
MarkdownFoldingProvider.end = null;
MarkdownFoldingProvider.endLine = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('---')) {
if (i === 0 && start === null) {
start = i;
MarkdownFoldingProvider.start = start;
} else if (start !== null && end === null) {
end = i;
MarkdownFoldingProvider.end = end;
MarkdownFoldingProvider.endLine = line.length;
MarkdownFoldingProvider.triggerHighlighting();
ranges.push(new FoldingRange(start, end, FoldingRangeKind.Region));
return ranges;
}
}
}
return ranges;
}
public static triggerHighlighting() {
const config = workspace.getConfiguration(CONFIG_KEY);
const fmHighlight = config.get<boolean>(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT);
if (MarkdownFoldingProvider.start !== null && MarkdownFoldingProvider.end !== null && MarkdownFoldingProvider.endLine !== null) {
const range = new Range(new Position(MarkdownFoldingProvider.start, 0), new Position(MarkdownFoldingProvider.end, MarkdownFoldingProvider.endLine));
if (MarkdownFoldingProvider.decType !== null) {
MarkdownFoldingProvider.decType.dispose();
}
if (fmHighlight) {
MarkdownFoldingProvider.decType = new FrontMatterDecorationProvider().get();
window.activeTextEditor?.setDecorations(MarkdownFoldingProvider.decType, [range]);
}
}
}
}

View File

@@ -3,5 +3,6 @@ export enum Command {
metadata = "metadata",
settings = "settings",
focusOnTags = "focusOnTags",
focusOnCategories = "focusOnCategories"
focusOnCategories = "focusOnCategories",
closeSections = "closeSections",
}

View File

@@ -15,4 +15,9 @@ export enum CommandToCode {
runCustomScript = "custom-script",
initProject = "init-project",
createContent = "create-content",
updateModifiedUpdating = "update-modified-updates",
updateFmHighlight = "update-fm-highlight",
createTemplate = "create-template",
toggleCenterMode = "toggle-center-mode",
toggleWritingSettings = "toggle-writing-settings",
}

View File

@@ -1,19 +1,15 @@
import * as React from 'react';
import { CommandToCode } from './CommandToCode';
import { Actions } from './components/Actions';
import { BaseView } from './components/BaseView';
import { Collapsible } from './components/Collapsible';
import { BugIcon } from './components/Icons/BugIcon';
import { FileIcon } from './components/Icons/FileIcon';
import { FolderOpenedIcon } from './components/Icons/FolderOpenedIcon';
import { GlobalSettings } from './components/GlobalSettings';
import { ListUnorderedIcon } from './components/Icons/ListUnorderedIcon';
import { SettingsIcon } from './components/Icons/SettingsIcon';
import { SymbolKeywordIcon } from './components/Icons/SymbolKeywordIcon';
import { TagIcon } from './components/Icons/TagIcon';
import { OtherActions } from './components/OtherActions';
import { SeoStatus } from './components/SeoStatus';
import { Spinner } from './components/Spinner';
import { TagPicker } from './components/TagPicker';
import { MessageHelper } from './helper/MessageHelper';
import useMessages from './hooks/useMessages';
import { TagType } from './TagType';
@@ -35,21 +31,11 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
);
}
const openSettings = () => {
MessageHelper.sendMessage(CommandToCode.openSettings);
};
const openFile = () => {
MessageHelper.sendMessage(CommandToCode.openFile);
};
const openProject = () => {
MessageHelper.sendMessage(CommandToCode.openProject);
};
return (
<div className="frontmatter">
<div className={`ext_actions`}>
<GlobalSettings settings={settings} />
{
settings && settings.seo && <SeoStatus seo={settings.seo} data={metadata} />
}
@@ -57,7 +43,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
settings && metadata && <Actions metadata={metadata} settings={settings} />
}
<Collapsible title="Metadata" className={`absolute w-full`}>
<Collapsible id={`tags`} title="Metadata" className={`inherit z-20`}>
{
<TagPicker type={TagType.keywords}
icon={<SymbolKeywordIcon />}
@@ -91,24 +77,8 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
)
}
</Collapsible>
</div>
<div className={`ext_settings`}>
<div className="ext_link_block">
<button onClick={openSettings}><SettingsIcon /> Open settings</button>
</div>
<div className="ext_link_block">
<button onClick={openFile}><FileIcon /> Reveal file in folder</button>
</div>
<div className="ext_link_block">
<button onClick={openProject}><FolderOpenedIcon /> Reveal project folder</button>
</div>
<div className="ext_link_block">
<a href="https://github.com/estruyf/vscode-front-matter/issues" title="Open an issue on GitHub"><BugIcon /> Report an issue</a>
</div>
<OtherActions settings={settings} isFile={true} />
</div>
</div>
);

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
export interface IActionButtonProps {
title: string;
className?: string;
disabled?: boolean;
onClick: (e: React.SyntheticEvent<HTMLButtonElement>) => void;
}
export const ActionButton: React.FunctionComponent<IActionButtonProps> = ({className, onClick, disabled,title}: React.PropsWithChildren<IActionButtonProps>) => {
return (
<div className={`article__action`}>
<button onClick={onClick} className={className || ""} disabled={disabled}>{title}</button>
</div>
);
};

View File

@@ -1,5 +1,8 @@
import * as React from 'react';
import { PanelSettings } from '../../models/PanelSettings';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
import { Collapsible } from './Collapsible';
import { CustomScript } from './CustomScript';
import { DateAction } from './DateAction';
@@ -19,8 +22,11 @@ export const Actions: React.FunctionComponent<IActionsProps> = (props: React.Pro
}
return (
<Collapsible title="Actions">
<Collapsible id={`actions`} title="Actions">
<div className={`article__actions`}>
<ActionButton onClick={() => MessageHelper.sendMessage(CommandToCode.toggleCenterMode)} title={`Toggle center mode`} />
{ metadata && metadata.title && <SlugAction value={metadata.title} crntValue={metadata.slug} slugOpts={settings.slug} /> }
<DateAction />

View File

@@ -3,6 +3,8 @@ import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { Collapsible } from './Collapsible';
import { GlobalSettings } from './GlobalSettings';
import { OtherActions } from './OtherActions';
export interface IBaseViewProps {
settings: PanelSettings | undefined;
@@ -21,7 +23,9 @@ export const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings}: Re
return (
<div className="frontmatter">
<div className={`ext_actions`}>
<Collapsible title="Actions">
<GlobalSettings settings={settings} isBase />
<Collapsible id={`base_actions`} title="Actions">
<div className={`base__actions`}>
<button onClick={initProject} disabled={settings?.isInitialized}>Initialize project</button>
<button onClick={createContent} disabled={!settings?.isInitialized}>Create new content</button>
@@ -30,7 +34,7 @@ export const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings}: Re
{
settings?.contentInfo && (
<Collapsible title="Content information">
<Collapsible id={`base_content`} title="Content information">
<div className="base__information">
{
settings.contentInfo.map(folder => (
@@ -43,6 +47,8 @@ export const BaseView: React.FunctionComponent<IBaseViewProps> = ({settings}: Re
</Collapsible>
)
}
<OtherActions settings={settings} isFile={false} isBase />
</div>
</div>
);

View File

@@ -1,14 +1,22 @@
import * as React from 'react';
import { useEffect } from 'react';
import { Command } from '../Command';
import { VsCollapsible } from './VscodeComponents';
export interface ICollapsibleProps {
id: string;
title: string;
className?: string;
sendUpdate?: (open: boolean) => void;
}
export const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({children, title, sendUpdate, className}: React.PropsWithChildren<ICollapsibleProps>) => {
const [ isOpen, setIsOpen ] = React.useState(true);
export const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({id, children, title, sendUpdate, className}: React.PropsWithChildren<ICollapsibleProps>) => {
const [ isOpen, setIsOpen ] = React.useState(false);
const collapseKey = `collapse-${id}`;
const updateStorage = (value: boolean) => {
window.localStorage.setItem(collapseKey, value.toString());
}
// This is a work around for a lit-element issue of duplicate slot names
const triggerClick = (e: React.MouseEvent<HTMLElement>) => {
@@ -17,11 +25,29 @@ export const Collapsible: React.FunctionComponent<ICollapsibleProps> = ({childre
if (sendUpdate) {
sendUpdate(!prev);
}
updateStorage(!prev);
return !prev;
});
}
}
useEffect(() => {
const collapsed = window.localStorage.getItem(collapseKey);
if (collapsed === null || collapsed === 'true') {
setIsOpen(true);
updateStorage(true);
}
window.addEventListener('message', event => {
const message = event.data;
if (message.command === Command.closeSections) {
setIsOpen(false);
updateStorage(false);
}
});
}, ['']);
return (
<VsCollapsible title={title} onClick={triggerClick} open={isOpen}>
<div className={`section collapsible__body ${className || ""}`} slot="body">

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface ICustomScriptProps {
title: string;
@@ -14,8 +15,6 @@ export const CustomScript: React.FunctionComponent<ICustomScriptProps> = ({title
};
return (
<div className={`article__action`}>
<button onClick={runCustomScript}>{title}</button>
</div>
<ActionButton onClick={runCustomScript} title={title} />
);
};

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface IDateActionProps {}
@@ -16,12 +17,8 @@ export const DateAction: React.FunctionComponent<IDateActionProps> = (props: Rea
return (
<>
<div className={`article__action`}>
<button onClick={setDate}>Set publish date</button>
</div>
<div className={`article__action`}>
<button onClick={setLastMod}>Set modified date</button>
</div>
<ActionButton onClick={setDate} title={`Set publish date`} />
<ActionButton onClick={setLastMod} title={`Set modified date`} />
</>
);
};

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { Collapsible } from './Collapsible';
import { VsCheckbox } from './VscodeComponents';
export interface IGlobalSettingsProps {
settings: PanelSettings | undefined;
isBase?: boolean;
}
export const GlobalSettings: React.FunctionComponent<IGlobalSettingsProps> = ({settings, isBase}: React.PropsWithChildren<IGlobalSettingsProps>) => {
const { modifiedDateUpdate, fmHighlighting } = settings || {};
const onDateCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateModifiedUpdating, !modifiedDateUpdate);
};
const onHighlightCheck = () => {
MessageHelper.sendMessage(CommandToCode.updateFmHighlight, !fmHighlighting);
};
return (
<>
<Collapsible id={`${isBase ? "base_" : ""}settings`} title="Global settings">
<div className={`base__actions`}>
<VsCheckbox label="Auto-update modified date" checked={modifiedDateUpdate} onClick={onDateCheck} />
</div>
<div className={`base__actions`}>
<VsCheckbox label="Highlight Front Matter" checked={fmHighlighting} onClick={onHighlightCheck} />
</div>
</Collapsible>
</>
);
};

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
export interface ITemplateIconProps {}
export const TemplateIcon: React.FunctionComponent<ITemplateIconProps> = (props: React.PropsWithChildren<ITemplateIconProps>) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.3} d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
</svg>
);
};

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
export interface IWritingIconProps {}
export const WritingIcon: React.FunctionComponent<IWritingIconProps> = (props: React.PropsWithChildren<IWritingIconProps>) => {
return (
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fillRule="evenodd" clipRule="evenodd" d="M7.223 10.933c.326.192.699.29 1.077.282a2.159 2.159 0 0 0 1.754-.842 3.291 3.291 0 0 0 .654-2.113 2.886 2.886 0 0 0-.576-1.877 1.99 1.99 0 0 0-1.634-.733 2.294 2.294 0 0 0-1.523.567V3.475h-.991V11.1h.995v-.344c.076.066.158.125.244.177zM7.85 6.7c.186-.079.388-.113.59-.1a1.08 1.08 0 0 1 .896.428c.257.363.382.802.357 1.245a2.485 2.485 0 0 1-.4 1.484 1.133 1.133 0 0 1-.96.508 1.224 1.224 0 0 1-.976-.417A1.522 1.522 0 0 1 6.975 8.8v-.6a1.722 1.722 0 0 1 .393-1.145c.13-.154.296-.276.482-.355zM3.289 5.675a3.03 3.03 0 0 0-.937.162 2.59 2.59 0 0 0-.8.4l-.1.077v1.2l.423-.359a2.1 2.1 0 0 1 1.366-.572.758.758 0 0 1 .661.282c.15.232.23.503.231.779L2.9 7.825a2.6 2.6 0 0 0-1.378.575 1.65 1.65 0 0 0-.022 2.336 1.737 1.737 0 0 0 1.253.454 1.96 1.96 0 0 0 1.107-.332c.102-.068.197-.145.286-.229v.444h.941V7.715a2.193 2.193 0 0 0-.469-1.5 1.687 1.687 0 0 0-1.329-.54zm.857 3.041c.02.418-.12.829-.391 1.148a1.221 1.221 0 0 1-.955.422.832.832 0 0 1-.608-.2.833.833 0 0 1 0-1.091c.281-.174.6-.277.93-.3l1.02-.148.004.169zm8.313 2.317c.307.13.64.193.973.182.495.012.983-.114 1.41-.365l.123-.075.013-.007V9.615l-.446.32c-.316.224-.696.34-1.084.329A1.3 1.3 0 0 1 12.4 9.8a1.975 1.975 0 0 1-.4-1.312 2.01 2.01 0 0 1 .453-1.381A1.432 1.432 0 0 1 13.6 6.6a1.8 1.8 0 0 1 .971.279l.43.265V5.97l-.17-.073a2.9 2.9 0 0 0-1.17-.247 2.52 2.52 0 0 0-1.929.817 2.9 2.9 0 0 0-.747 2.049c-.028.707.21 1.4.67 1.939.222.249.497.446.804.578z"/></svg>
);
};

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
export interface IOtherActionButtonProps {
className?: string;
disabled?: boolean;
onClick: (e: React.SyntheticEvent<HTMLButtonElement>) => void;
}
export const OtherActionButton: React.FunctionComponent<IOtherActionButtonProps> = ({ className, disabled, onClick, children}: React.PropsWithChildren<IOtherActionButtonProps>) => {
return (
<div className={`ext_link_block`}>
<button onClick={onClick} className={className || ""} disabled={disabled}>{children}</button>
</div>
);
};

View File

@@ -0,0 +1,61 @@
import * as React from 'react';
import { PanelSettings } from '../../models';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { Collapsible } from './Collapsible';
import { BugIcon } from './Icons/BugIcon';
import { FileIcon } from './Icons/FileIcon';
import { FolderOpenedIcon } from './Icons/FolderOpenedIcon';
import { SettingsIcon } from './Icons/SettingsIcon';
import { TemplateIcon } from './Icons/TemplateIcon';
import { WritingIcon } from './Icons/WritingIcon';
import { OtherActionButton } from './OtherActionButton';
export interface IOtherActionsProps {
isFile: boolean;
settings: PanelSettings | undefined;
isBase?: boolean;
}
export const OtherActions: React.FunctionComponent<IOtherActionsProps> = ({isFile, settings, isBase}: React.PropsWithChildren<IOtherActionsProps>) => {
const openSettings = () => {
MessageHelper.sendMessage(CommandToCode.openSettings);
};
const openFile = () => {
MessageHelper.sendMessage(CommandToCode.openFile);
};
const openProject = () => {
MessageHelper.sendMessage(CommandToCode.openProject);
};
const createAsTemplate = () => {
MessageHelper.sendMessage(CommandToCode.createTemplate);
};
const toggleWritingSettings = () => {
MessageHelper.sendMessage(CommandToCode.toggleWritingSettings);
};
return (
<>
<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={createAsTemplate} disabled={!isFile}><TemplateIcon /> <span>Create as template</span></OtherActionButton>
<OtherActionButton onClick={openSettings}><SettingsIcon /> <span>Open settings</span></OtherActionButton>
<OtherActionButton onClick={openFile} disabled={!isFile}><FileIcon /> <span>Reveal file in folder</span></OtherActionButton>
<OtherActionButton onClick={openProject}><FolderOpenedIcon /> <span>Reveal project folder</span></OtherActionButton>
<div className="ext_link_block">
<a href="https://github.com/estruyf/vscode-front-matter/issues" title="Open an issue on GitHub"><BugIcon /> <span>Report an issue</span></a>
</div>
</Collapsible>
</>
);
};

View File

@@ -3,6 +3,7 @@
import * as React from 'react';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface IPublishActionProps {
draft: boolean;
@@ -16,8 +17,6 @@ export const PublishAction: React.FunctionComponent<IPublishActionProps> = (prop
};
return (
<div className={`article__action`}>
<button onClick={publish} className={`${draft ? "" : "secondary"}`}>{draft ? "Publish" : "Revert to draft"}</button>
</div>
<ActionButton onClick={publish} className={`${draft ? "" : "secondary"}`} title={draft ? "Publish" : "Revert to draft"} />
);
};

View File

@@ -88,7 +88,7 @@ export const SeoStatus: React.FunctionComponent<ISeoStatusProps> = (props: React
}, [title, data[descriptionField], data?.articleDetails?.wordCount]);
return (
<Collapsible title="SEO Status" sendUpdate={(value) => setIsOpen(value)}>
<Collapsible id={`seo`} title="SEO Status" sendUpdate={(value) => setIsOpen(value)}>
{ renderContent() }
</Collapsible>
);

View File

@@ -3,6 +3,7 @@ import { SlugHelper } from '../../helpers/SlugHelper';
import { Slug } from '../../models/PanelSettings';
import { CommandToCode } from '../CommandToCode';
import { MessageHelper } from '../helper/MessageHelper';
import { ActionButton } from './ActionButton';
export interface ISlugActionProps {
value: string;
@@ -21,8 +22,6 @@ export const SlugAction: React.FunctionComponent<ISlugActionProps> = (props: Rea
};
return (
<div className={`article__action`}>
<button onClick={optimize} disabled={crntValue === slug}>Optimize slug</button>
</div>
<ActionButton onClick={optimize} disabled={crntValue === slug} title={`Optimize slug`} />
);
};

View File

@@ -126,7 +126,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
}, [crntSelected]);
return (
<div className={`section article__tags`}>
<div className={`article__tags`}>
<h3>{icon} {type}</h3>
<Downshift ref={dsRef}

View File

@@ -6,4 +6,5 @@ export const VsTableHeaderCell = wrapWc(`vscode-table-header-cell`);
export const VsTableBody = wrapWc(`vscode-table-body`);
export const VsTableRow = wrapWc(`vscode-table-row`);
export const VsTableCell = wrapWc(`vscode-table-cell`);
export const VsCollapsible = wrapWc(`vscode-collapsible`);
export const VsCollapsible = wrapWc(`vscode-collapsible`);
export const VsCheckbox = wrapWc(`vscode-checkbox`);

View File

@@ -10,6 +10,7 @@ import '@bendera/vscode-webview-elements/dist/vscode-table-body';
import '@bendera/vscode-webview-elements/dist/vscode-table-row';
import '@bendera/vscode-webview-elements/dist/vscode-table-cell';
import '@bendera/vscode-webview-elements/dist/vscode-collapsible';
import '@bendera/vscode-webview-elements/dist/vscode-checkbox';
declare const acquireVsCodeApi: <T = unknown>() => {
getState: () => T;

View File

@@ -1,5 +1,5 @@
import { Template } from './../commands/Template';
import { SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME } from './../constants/settings';
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME } from './../constants/settings';
import * as os from 'os';
import { PanelSettings, CustomScript } from './../models/PanelSettings';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode";
@@ -146,6 +146,21 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
case CommandToCode.createContent:
await commands.executeCommand(COMMAND_NAME.createContent);
break;
case CommandToCode.createTemplate:
await commands.executeCommand(COMMAND_NAME.createTemplate);
break;
case CommandToCode.updateModifiedUpdating:
this.updateModifiedUpdating(msg.data || false);
break;
case CommandToCode.toggleWritingSettings:
this.toggleWritingSettings();
break;
case CommandToCode.updateFmHighlight:
this.updateFmHighlight((msg.data !== null && msg.data !== undefined) ? msg.data : false);
break;
case CommandToCode.toggleCenterMode:
await commands.executeCommand(`workbench.action.toggleCenteredLayout`);
break;
}
});
@@ -195,6 +210,13 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
/**
* Trigger all sections to close
*/
public collapseAll() {
this.postWebviewMessage({ command: Command.closeSections });
}
/**
* Run a custom script
* @param msg
@@ -262,7 +284,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
freeform: config.get(SETTING_PANEL_FREEFORM),
scripts: config.get(SETTING_CUSTOM_SCRIPTS),
isInitialized: await Template.isInitialized(),
contentInfo: await Folders.getInfo() || null
contentInfo: await Folders.getInfo() || null,
modifiedDateUpdate: config.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: config.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
} as PanelSettings
});
}
@@ -328,7 +353,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
return null;
}
if (!ArticleHelper.isMarkdownDile()) {
if (!ArticleHelper.isMarkdownFile()) {
return null;
}
@@ -376,6 +401,58 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
/**
* Toggle the writing settings
*/
private async toggleWritingSettings() {
const config = workspace.getConfiguration("", { languageId: "markdown" });
const enabled = this.isWritingSettingsEnabled();
await config.update("editor.fontSize", enabled ? undefined : 14, false, true);
await config.update("editor.lineHeight", enabled ? undefined : 26, false, true);
await config.update("editor.wordWrap", enabled ? undefined : "wordWrapColumn", false, true);
await config.update("editor.wordWrapColumn", enabled ? undefined : 64, false, true);
await config.update("editor.lineNumbers", enabled ? undefined : "off", false, true);
await config.update("editor.quickSuggestions", enabled ? undefined : false, false, true);
await config.update("editor.minimap.enabled", enabled ? undefined : false, false, true);
this.getSettings();
}
/**
* Check if the writing settings are enabled
*/
private isWritingSettingsEnabled() {
const config = workspace.getConfiguration("", { languageId: "markdown" });
const fontSize = config.get("editor.fontSize");
const lineHeight = config.get("editor.lineHeight");
const wordWrap = config.get("editor.wordWrap");
const wordWrapColumn = config.get("editor.wordWrapColumn");
const lineNumbers = config.get("editor.lineNumbers");
const quickSuggestions = config.get<boolean>("editor.quickSuggestions");
return fontSize && lineHeight && wordWrap && wordWrapColumn && lineNumbers && quickSuggestions !== undefined;
}
/**
* Toggle the Front Matter highlighting
*/
private async updateFmHighlight(autoUpdate: boolean) {
const config = workspace.getConfiguration(CONFIG_KEY);
await config.update(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, autoUpdate);
this.getSettings();
}
/**
* Toggle the modified auto-update setting
*/
private async updateModifiedUpdating(autoUpdate: boolean) {
const config = workspace.getConfiguration(CONFIG_KEY);
await config.update(SETTING_AUTO_UPDATE_DATE, autoUpdate);
this.getSettings();
}
/**
* Post data to the panel
* @param msg
@@ -384,7 +461,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
this.panel!.webview.postMessage(msg);
}
/**
* Generate a unique nonce
*/
private getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';