mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-06 09:51:29 +02:00
#53 - Add save as template
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -16,4 +16,5 @@ export enum CommandToCode {
|
||||
initProject = "init-project",
|
||||
createContent = "create-content",
|
||||
updateModifiedUpdating = "update-modified-updates",
|
||||
createTemplate = "create-template",
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user