diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f01394..ed646023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## [2.3.0] - 2020-08-XX +- 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 diff --git a/README.md b/README.md index bb4c5f95..3a64fa88 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,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:

- Base view + Base view

+> **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** + +

+ Global settings +

+ **SEO Status**

@@ -83,10 +91,8 @@ 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: -

- Other actions + Other actions

## Custom actions @@ -143,14 +149,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. -

- Create content -

- You can register and unregister folders by right-clicking on the folder in your VSCode explorer panel.

diff --git a/assets/media/styles.css b/assets/media/styles.css index d74a8202..d006584d 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -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; @@ -288,7 +300,7 @@ padding: 0px 14px; user-select: none; text-decoration: none; - width: auto; + width: 100%; } .ext_link_block a:hover, diff --git a/assets/v2.3.0/baseview.png b/assets/v2.3.0/baseview.png new file mode 100644 index 00000000..d2a80508 Binary files /dev/null and b/assets/v2.3.0/baseview.png differ diff --git a/assets/v2.3.0/global-settings.png b/assets/v2.3.0/global-settings.png new file mode 100644 index 00000000..d9fb74f0 Binary files /dev/null and b/assets/v2.3.0/global-settings.png differ diff --git a/assets/v2.3.0/other-actions.png b/assets/v2.3.0/other-actions.png new file mode 100644 index 00000000..33d2f9be Binary files /dev/null and b/assets/v2.3.0/other-actions.png differ diff --git a/package.json b/package.json index 960ff977..69d94879 100644 --- a/package.json +++ b/package.json @@ -256,13 +256,18 @@ }, { "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" } ], "menus": { @@ -287,6 +292,10 @@ { "command": "frontMatter.init", "when": "frontMatterCanInit" + }, + { + "command": "frontMatter.createTemplate", + "when": "!frontMatterCanInit" } ] }, diff --git a/src/commands/Article.ts b/src/commands/Article.ts index efaa704c..bf0e2995 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -230,7 +230,7 @@ export class Article { const txtChanges = fileChanges.contentChanges.map(c => c.text); const editor = vscode.window.activeTextEditor; - if (txtChanges.length > 0 && editor) { + if (txtChanges.length > 0 && editor && ArticleHelper.isMarkdownFile()) { const config = vscode.workspace.getConfiguration(CONFIG_KEY); const autoUpdate = config.get(SETTING_AUTO_UPDATE_DATE); diff --git a/src/commands/Project.ts b/src/commands/Project.ts index e2c61f9b..85896815 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -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(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; + } } \ No newline at end of file diff --git a/src/commands/StatusListener.ts b/src/commands/StatusListener.ts index af1df44b..66b3d128 100644 --- a/src/commands/StatusListener.ts +++ b/src/commands/StatusListener.ts @@ -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(); diff --git a/src/commands/Template.ts b/src/commands/Template.ts index 47d24af5..90045af2 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -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(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(SETTING_TEMPLATES_FOLDER); + return folder; + } } \ No newline at end of file diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 663fa3c6..4645c3cb 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -19,5 +19,6 @@ export const COMMAND_NAME = { toggleDraft: getCommandName("toggleDraft"), registerFolder: getCommandName("registerFolder"), unregisterFolder: getCommandName("unregisterFolder"), - createContent: getCommandName("createContent") + createContent: getCommandName("createContent"), + createTemplate: getCommandName("createTemplate") }; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 65c0c1f9..ef30a011 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -70,6 +70,8 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension } }); + let createTemplate = vscode.commands.registerCommand(COMMAND_NAME.createTemplate, Template.generate); + const toggleDraftCommand = COMMAND_NAME.toggleDraft; const toggleDraft = vscode.commands.registerCommand(toggleDraftCommand, async () => { await Article.toggleDraft(); @@ -125,6 +127,7 @@ export async function activate({ subscriptions, extensionUri }: vscode.Extension setLastModifiedDate, generateSlug, createFromTemplate, + createTemplate, toggleDraft, registerFolder, unregisterFolder, diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index b4947721..adfecbe9 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -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")); } diff --git a/src/viewpanel/CommandToCode.ts b/src/viewpanel/CommandToCode.ts index 3e9eb4a1..eab2f4af 100644 --- a/src/viewpanel/CommandToCode.ts +++ b/src/viewpanel/CommandToCode.ts @@ -16,4 +16,5 @@ export enum CommandToCode { initProject = "init-project", createContent = "create-content", updateModifiedUpdating = "update-modified-updates", + createTemplate = "create-template", } \ No newline at end of file diff --git a/src/viewpanel/ViewPanel.tsx b/src/viewpanel/ViewPanel.tsx index aeaa7223..1423a91d 100644 --- a/src/viewpanel/ViewPanel.tsx +++ b/src/viewpanel/ViewPanel.tsx @@ -1,20 +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 { GlobalSettings } from './components/GlobalSettings'; -import { BugIcon } from './components/Icons/BugIcon'; -import { FileIcon } from './components/Icons/FileIcon'; -import { FolderOpenedIcon } from './components/Icons/FolderOpenedIcon'; 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'; @@ -36,18 +31,6 @@ export const ViewPanel: React.FunctionComponent = (props: React ); } - const openSettings = () => { - MessageHelper.sendMessage(CommandToCode.openSettings); - }; - - const openFile = () => { - MessageHelper.sendMessage(CommandToCode.openFile); - }; - - const openProject = () => { - MessageHelper.sendMessage(CommandToCode.openProject); - }; - return (

@@ -60,7 +43,7 @@ export const ViewPanel: React.FunctionComponent = (props: React settings && metadata && } - + { } @@ -94,24 +77,8 @@ export const ViewPanel: React.FunctionComponent = (props: React ) } -
-
-
- -
- -
- -
- -
- -
- -
- Report an issue -
+
); diff --git a/src/viewpanel/components/BaseView.tsx b/src/viewpanel/components/BaseView.tsx index d22ea754..d33b2058 100644 --- a/src/viewpanel/components/BaseView.tsx +++ b/src/viewpanel/components/BaseView.tsx @@ -4,6 +4,7 @@ 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; @@ -46,6 +47,8 @@ export const BaseView: React.FunctionComponent = ({settings}: Re ) } + + ); diff --git a/src/viewpanel/components/Icons/TemplateIcon.tsx b/src/viewpanel/components/Icons/TemplateIcon.tsx new file mode 100644 index 00000000..b6c2eaa9 --- /dev/null +++ b/src/viewpanel/components/Icons/TemplateIcon.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +export interface ITemplateIconProps {} + +export const TemplateIcon: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( + + + + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/OtherActions.tsx b/src/viewpanel/components/OtherActions.tsx new file mode 100644 index 00000000..c2bbc80a --- /dev/null +++ b/src/viewpanel/components/OtherActions.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +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'; + +export interface IOtherActionsProps { + isFile: boolean; +} + +export const OtherActions: React.FunctionComponent = ({isFile}: React.PropsWithChildren) => { + + const openSettings = () => { + MessageHelper.sendMessage(CommandToCode.openSettings); + }; + + const openFile = () => { + MessageHelper.sendMessage(CommandToCode.openFile); + }; + + const openProject = () => { + MessageHelper.sendMessage(CommandToCode.openProject); + }; + + const createAsTemplate = () => { + MessageHelper.sendMessage(CommandToCode.createTemplate); + }; + + return ( + <> + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ + ); +}; \ No newline at end of file diff --git a/src/viewpanel/components/TagPicker.tsx b/src/viewpanel/components/TagPicker.tsx index 010e6e52..64b2a872 100644 --- a/src/viewpanel/components/TagPicker.tsx +++ b/src/viewpanel/components/TagPicker.tsx @@ -126,7 +126,7 @@ export const TagPicker: React.FunctionComponent = (props: React }, [crntSelected]); return ( -
+

{icon} {type}