#53 - Add save as template

This commit is contained in:
Elio Struyf
2021-08-09 17:05:40 +02:00
parent 32aa8f4223
commit e194928291
21 changed files with 224 additions and 68 deletions
+1 -1
View File
@@ -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);
+28 -12
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;
}
}
+6 -1
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();
+58 -2
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;
}
}
+2 -1
View File
@@ -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")
};
+3
View File
@@ -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,
+1 -1
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"));
}
+1
View File
@@ -16,4 +16,5 @@ export enum CommandToCode {
initProject = "init-project",
createContent = "create-content",
updateModifiedUpdating = "update-modified-updates",
createTemplate = "create-template",
}
+3 -36
View File
@@ -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<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`}>
@@ -60,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 title="Metadata" className={`inherit z-20`}>
{
<TagPicker type={TagType.keywords}
icon={<SymbolKeywordIcon />}
@@ -94,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 isFile={true} />
</div>
</div>
);
+3
View File
@@ -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<IBaseViewProps> = ({settings}: Re
</Collapsible>
)
}
<OtherActions isFile={false} />
</div>
</div>
);
@@ -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>
);
};
+58
View File
@@ -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<IOtherActionsProps> = ({isFile}: 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);
};
return (
<>
<Collapsible title="Other actions" className={`other_actions`}>
<div className="ext_link_block">
<button onClick={createAsTemplate} disabled={!isFile}><TemplateIcon /> Create as template</button>
</div>
<div className="ext_link_block">
<button onClick={openSettings}><SettingsIcon /> Open settings</button>
</div>
<div className="ext_link_block">
<button onClick={openFile} disabled={!isFile}><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>
</Collapsible>
</>
);
};
+1 -1
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}
+4 -1
View File
@@ -146,6 +146,9 @@ 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;
@@ -332,7 +335,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
return null;
}
if (!ArticleHelper.isMarkdownDile()) {
if (!ArticleHelper.isMarkdownFile()) {
return null;
}