Refactoring explorer view to listeners

This commit is contained in:
Elio Struyf
2022-02-12 17:58:59 +01:00
parent 131150f5a6
commit cbb0d8f72b
34 changed files with 965 additions and 723 deletions
+2 -1
View File
@@ -13,6 +13,7 @@ import { DateHelper } from '../helpers/DateHelper';
import { parseWinPath } from '../helpers/parseWinPath';
import { Telemetry } from '../helpers/Telemetry';
import { ParsedFrontMatter } from '../parsers';
import { MediaListener } from '../listeners/panel';
export class Article {
@@ -337,7 +338,7 @@ export class Article {
} as DashboardData);
// Let the editor panel know you are selecting an image
ExplorerView.getInstance().getMediaSelection();
MediaListener.getMediaSelection();
}
/**
+2 -1
View File
@@ -5,6 +5,7 @@ import { Credentials } from "../services/Credentials";
import fetch from "node-fetch";
import { ExplorerView } from '../explorerView/ExplorerView';
import { Dashboard } from './Dashboard';
import { SettingsListener } from '../listeners/panel';
export class Backers {
private static creds: Credentials | null = null;
@@ -60,7 +61,7 @@ export class Backers {
if (!prevData) {
const explorerView = ExplorerView.getInstance();
if (explorerView.visible) {
explorerView.getSettings();
SettingsListener.getSettings();
}
if (Dashboard.isOpen) {
+4 -7
View File
@@ -1,5 +1,3 @@
import { PagesListener } from './../listeners/PagesListener';
import { ExtensionListener } from './../listeners/ExtensionListener';
import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
import { join } from "path";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode";
@@ -10,7 +8,8 @@ import { WebviewHelper } from '@estruyf/vscode';
import { DashboardData } from '../models/DashboardData';
import { ExplorerView } from '../explorerView/ExplorerView';
import { MediaLibrary } from '../helpers/MediaLibrary';
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener } from '../listeners';
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener } from '../listeners/dashboard';
import { MediaListener as PanelMediaListener } from '../listeners/panel'
export class Dashboard {
private static webview: WebviewPanel | null = null;
@@ -115,8 +114,7 @@ export class Dashboard {
Dashboard.webview.onDidChangeViewState(async () => {
if (!this.webview?.visible) {
Dashboard._viewData = undefined;
const panel = ExplorerView.getInstance(extensionUri);
panel.getMediaSelection();
PanelMediaListener.getMediaSelection();
Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: null });
}
@@ -127,8 +125,7 @@ export class Dashboard {
Dashboard.webview.onDidDispose(async () => {
Dashboard.isDisposed = true;
Dashboard._viewData = undefined;
const panel = ExplorerView.getInstance(extensionUri);
panel.getMediaSelection();
PanelMediaListener.getMediaSelection();
await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false);
});
+1 -1
View File
@@ -12,7 +12,7 @@ import { format } from 'date-fns';
import { Dashboard } from './Dashboard';
import { parseWinPath } from '../helpers/parseWinPath';
import { MediaHelpers } from '../helpers/MediaHelpers';
import { MediaListener, PagesListener } from '../listeners';
import { MediaListener, PagesListener } from '../listeners/dashboard';
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
import { Telemetry } from '../helpers/Telemetry';
+3 -2
View File
@@ -4,6 +4,7 @@ import { ArticleHelper, SeoHelper, Settings } from '../helpers';
import { ExplorerView } from '../explorerView/ExplorerView';
import { DefaultFields } from '../constants';
import { ContentType } from '../helpers/ContentType';
import { DataListener } from '../listeners/panel';
export class StatusListener {
@@ -58,7 +59,7 @@ export class StatusListener {
const panel = ExplorerView.getInstance();
if (panel && panel.visible) {
panel.pushMetadata(article!.data);
DataListener.pushMetadata(article!.data);
}
return;
@@ -68,7 +69,7 @@ export class StatusListener {
} else {
const panel = ExplorerView.getInstance();
if (panel && panel.visible) {
panel.pushMetadata(null);
DataListener.pushMetadata(null);
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ import { Project } from './Project';
import { Folders } from './Folders';
import { ContentType } from '../helpers/ContentType';
import { ContentType as IContentType } from '../models';
import { PagesListener } from '../listeners';
import { PagesListener } from '../listeners/dashboard';
import { extname } from 'path';
import { Telemetry } from '../helpers/Telemetry';
+28 -646
View File
@@ -1,32 +1,12 @@
import { DashboardData } from '../models/DashboardData';
import { Template } from '../commands/Template';
import { DefaultFields, 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, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM, CONTEXT, SETTINGS_FRAMEWORK_ID, SETTINGS_FRAMEWORK_START, TelemetryEvent, SETTINGS_DATA_TYPES } from '../constants';
import * as os from 'os';
import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv, ThemeIcon } from "vscode";
import { ArticleHelper, Settings } from "../helpers";
import { ArticleListener, ExtensionListener, MediaListener, ScriptListener, TaxonomyListener, DataListener, SettingsListener } from './../listeners/panel';
import { TelemetryEvent } from '../constants';
import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window } from "vscode";
import { Logger, Settings } from "../helpers";
import { Command } from "../panelWebView/Command";
import { CommandToCode } from '../panelWebView/CommandToCode';
import { Article } from '../commands';
import { TagType } from '../panelWebView/TagType';
import { CustomTaxonomyData, DraftField, Field, ScriptType, TaxonomyType } from '../models';
import { exec } from 'child_process';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { Content } from 'mdast';
import { COMMAND_NAME, EXTENSION_BETA_ID, EXTENSION_ID } from '../constants/Extension';
import { Folders } from '../commands/Folders';
import { Preview } from '../commands/Preview';
import { openFileInEditor } from '../helpers/openFileInEditor';
import { WebviewHelper } from '@estruyf/vscode';
import { Extension } from '../helpers/Extension';
import { Dashboard } from '../commands/Dashboard';
import { ImageHelper } from '../helpers/ImageHelper';
import { CustomScript } from '../helpers/CustomScript';
import { Link, Parent } from 'mdast-util-from-markdown/lib';
import { Telemetry } from '../helpers/Telemetry';
import { DataType } from '../models/DataType';
const FILE_LIMIT = 10;
export class ExplorerView implements WebviewViewProvider, Disposable {
public static readonly viewType = "frontMatter.explorer";
@@ -65,6 +45,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
}
}
public getWebview() {
return this.panel?.webview;
}
/**
* Default resolve webview panel
* @param webviewView
@@ -87,122 +71,15 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
);
webviewView.webview.onDidReceiveMessage(async (msg) => {
switch(msg.command) {
case CommandToCode.getData:
this.getSettings();
this.getFoldersAndFiles();
this.getFileData();
break;
case CommandToCode.updateSlug:
Article.generateSlug();
break;
case CommandToCode.updateLastMod:
Article.setLastModifiedDate();
break;
case CommandToCode.publish:
Article.toggleDraft();
break;
case CommandToCode.updateTags:
this.updateTags(TagType.tags, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateCategories:
this.updateTags(TagType.categories, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateKeywords:
this.updateTags(TagType.keywords, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateCustomTaxonomy:
this.updateCustomTaxonomy(msg.data);
break;
case CommandToCode.addTagToSettings:
this.addTags(TagType.tags, msg.data);
break;
case CommandToCode.addCategoryToSettings:
this.addTags(TagType.categories, msg.data);
break;
case CommandToCode.addToCustomTaxonomy:
this.addCustomTaxonomy(msg.data);
break;
case CommandToCode.openSettings:
const isBeta = Extension.getInstance().isBetaVersion();
commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`);
break;
case CommandToCode.openFile:
if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
commands.executeCommand('remote-wsl.revealInExplorer');
} else {
commands.executeCommand('revealFileInOS');
}
break;
case CommandToCode.runCustomScript:
this.runCustomScript(msg);
break;
case CommandToCode.openProject:
const wsFolder = Folders.getWorkspaceFolder();
if (wsFolder) {
const wsPath = wsFolder.fsPath;
if (os.type() === "Darwin") {
exec(`open ${wsPath}`);
} else if (os.type() === "Windows_NT") {
exec(`explorer ${wsPath}`);
} else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
exec('explorer.exe `wslpath -w "$PWD"`');
} else {
exec(`xdg-open ${wsPath}`);
}
}
break;
case CommandToCode.initProject:
await commands.executeCommand(COMMAND_NAME.init);
this.getSettings();
break;
case CommandToCode.createContent:
await commands.executeCommand(COMMAND_NAME.createContent);
break;
case CommandToCode.createTemplate:
await commands.executeCommand(COMMAND_NAME.createTemplate);
break;
case CommandToCode.updateModifiedUpdating:
this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false);
break;
case CommandToCode.toggleWritingSettings:
this.toggleWritingSettings();
break;
case CommandToCode.updateFmHighlight:
this.updateSetting(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false);
break;
case CommandToCode.toggleCenterMode:
await commands.executeCommand(`workbench.action.toggleCenteredLayout`);
break;
case CommandToCode.openPreview:
await commands.executeCommand(COMMAND_NAME.preview);
break;
case CommandToCode.openDashboard:
await commands.executeCommand(COMMAND_NAME.dashboard);
break;
case CommandToCode.updatePreviewUrl:
this.updateSetting(SETTING_PREVIEW_HOST, msg.data || "");
break;
case CommandToCode.openInEditor:
openFileInEditor(msg.data);
break;
case CommandToCode.updateMetadata:
this.updateMetadata(msg.data);
break;
case CommandToCode.selectImage:
await commands.executeCommand(COMMAND_NAME.dashboard, {
type: "media",
data: msg.data
} as DashboardData);
this.getMediaSelection();
break;
case CommandToCode.frameworkCommand:
this.openTerminalWithCommand(msg.data.command);
break;
case CommandToCode.updateStartCommand:
await this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || "");
break;
}
Logger.info(`Receiving message from webview to panel: ${msg.command}`);
ArticleListener.process(msg);
DataListener.process(msg);
ExtensionListener.process(msg);
MediaListener.process(msg);
ScriptListener.process(msg);
SettingsListener.process(msg);
TaxonomyListener.process(msg);
});
webviewView.onDidChangeVisibility(() => {
@@ -213,67 +90,24 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
});
window.onDidChangeActiveTextEditor(() => {
this.postWebviewMessage({ command: Command.loading, data: true });
this.sendMessage({ command: Command.loading, data: true });
if (this.visible) {
this.getFileData();
DataListener.getFileData();
}
}, this);
Settings.onConfigChange((global?: any) => {
this.getSettings();
SettingsListener.getSettings();
});
}
/**
* Triggers a metadata change in the panel
* @param metadata
* Post data to the panel
* @param msg
*/
public pushMetadata(metadata: any) {
const wsFolder = Folders.getWorkspaceFolder();
const filePath = window.activeTextEditor?.document.uri.fsPath;
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
const contentTypes = Settings.get<string>(SETTING_TAXONOMY_CONTENT_TYPES);
const articleDetails = this.getArticleDetails();
if (articleDetails) {
metadata.articleDetails = articleDetails;
}
let updatedMetadata = Object.assign({}, metadata);
if (commaSeparated) {
for (const key of commaSeparated) {
if (updatedMetadata[key] && typeof updatedMetadata[key] === "string") {
updatedMetadata[key] = updatedMetadata[key].split(",").map((s: string) => s.trim());
}
}
}
const keys = Object.keys(updatedMetadata);
if (keys.length > 0) {
updatedMetadata.filePath = filePath;
}
if (keys.length > 0 && contentTypes && wsFolder) {
// Get the current content type
const contentType = ArticleHelper.getContentType(updatedMetadata);
if (contentType) {
this.processImageFields(updatedMetadata, contentType.fields)
}
}
// Check slug
if (!updatedMetadata[DefaultFields.Slug]) {
const slug = Article.getSlug();
if (slug) {
updatedMetadata[DefaultFields.Slug] = slug;
}
}
this.postWebviewMessage({ command: Command.metadata, data: {
...updatedMetadata
}});
public sendMessage(msg: { command: Command, data?: any }) {
this.panel?.webview?.postMessage(msg);
}
/**
@@ -282,9 +116,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
*/
public triggerInputFocus(tagType: TagType) {
if (tagType === TagType.tags) {
this.postWebviewMessage({ command: Command.focusOnTags });
this.sendMessage({ command: Command.focusOnTags });
} else {
this.postWebviewMessage({ command: Command.focusOnCategories });
this.sendMessage({ command: Command.focusOnCategories });
}
}
@@ -292,396 +126,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
* Trigger all sections to close
*/
public collapseAll() {
this.postWebviewMessage({ command: Command.closeSections });
}
/**
* Update the metadata of the article
*/
public async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) {
if (!field) {
return;
}
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
if (!article) {
return;
}
const contentType = ArticleHelper.getContentType(article.data);
const dateFields = contentType.fields.filter((field) => field.type === "datetime");
const imageFields = contentType.fields.filter((field) => field.type === "image" && field.multiple);
// Support multi-level fields
let parentObj = article.data;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
for (const dateField of dateFields) {
if ((field === dateField.name) && value) {
parentObj[field] = Article.formatDate(new Date(value));
} else if (!imageFields.find(f => f.name === field)) {
// Only override the field data if it is not an multiselect image field
parentObj[field] = value;
}
}
for (const imageField of imageFields) {
if (field === imageField.name) {
// If value is an array, it means it comes from the explorer view itself (deletion)
if (Array.isArray(value)) {
parentObj[field] = value || [];
} else { // Otherwise it is coming from the media dashboard (addition)
let fieldValue = parentObj[field];
if (fieldValue && !Array.isArray(fieldValue)) {
fieldValue = [fieldValue];
}
const crntData = Object.assign([], fieldValue);
const allRelPaths = [...(crntData || []), value];
parentObj[field] = [...new Set(allRelPaths)].filter(f => f);
}
}
}
ArticleHelper.update(editor, article);
this.pushMetadata(article.data);
}
/**
* Open a terminal and run the passed command
* @param command
*/
private openTerminalWithCommand(command: string) {
if (command) {
let terminal = window.activeTerminal;
if (!terminal || (terminal && terminal.state.isInteractedWith === true)) {
terminal = window.createTerminal({
name: `Starting local server`,
iconPath: new ThemeIcon('server-environment'),
message: `Starting local server`,
});
}
if (terminal) {
terminal.sendText(command);
terminal.show(false);
}
}
}
/**
* Run a custom script
* @param msg
*/
private runCustomScript(msg: { command: string, data: any}) {
const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS);
if (msg?.data?.title && msg?.data?.script && scripts) {
const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title);
if (customScript?.script && customScript?.title) {
CustomScript.run(customScript);
}
}
}
/**
* Return the media selection
*/
public async getMediaSelection() {
this.postWebviewMessage({
command: Command.mediaSelectionData,
data: Dashboard.viewData
});
}
/**
* Retrieve the extension settings
*/
public async getSettings() {
this.postWebviewMessage({
command: Command.settings,
data: {
seo: {
title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1,
slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1,
description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1,
content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1,
descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description
},
slug: {
prefix: Settings.get(SETTING_SLUG_PREFIX) || "",
suffix: Settings.get(SETTING_SLUG_SUFFIX) || "",
updateFileName: !!Settings.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME),
},
date: {
format: Settings.get(SETTING_DATE_FORMAT)
},
tags: Settings.get(SETTING_TAXONOMY_TAGS, true) || [],
categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
freeform: Settings.get(SETTING_PANEL_FREEFORM),
scripts: (Settings.get<ICustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type),
isInitialized: await Template.isInitialized(),
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: Settings.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
preview: Preview.getSettings(),
commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [],
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
dashboardViewData: Dashboard.viewData,
draftField: Settings.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
isBacker: await Extension.getInstance().getState<boolean | undefined>(CONTEXT.backer, 'global'),
framework: Settings.get<string>(SETTINGS_FRAMEWORK_ID),
commands: {
start: Settings.get<string>(SETTINGS_FRAMEWORK_START)
},
dataTypes: Settings.get<DataType[]>(SETTINGS_DATA_TYPES),
} as PanelSettings
});
}
/**
* Retrieve the information about the registered folders and its files
*/
public async getFoldersAndFiles() {
this.postWebviewMessage({
command: Command.folderInfo,
data: await Folders.getInfo(FILE_LIMIT) || null
});
}
/**
* Process the image fields in the content type
* @param updatedMetadata
* @param fields
* @param parents
*/
private processImageFields(updatedMetadata: any, fields: Field[], parents: string[] = []) {
const imageFields = fields.filter((field) => field.type === "image");
// Support multi-level fields
let parentObj = updatedMetadata;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
// Process image fields
if (parentObj) {
for (const field of imageFields) {
if (parentObj[field.name]) {
const imageData = ImageHelper.allRelToAbs(field, parentObj[field.name])
if (imageData) {
if (field.multiple && imageData instanceof Array) {
const preview = imageData.map(preview => preview && preview.absPath ? ({
...preview,
webviewUrl: this.panel?.webview.asWebviewUri(preview.absPath).toString()
}) : null);
parentObj[field.name] = preview || [];
} else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) {
const preview = this.panel?.webview.asWebviewUri(imageData.absPath);
parentObj[field.name] = {
...imageData,
webviewUrl: preview ? preview.toString() : null
};
}
} else {
parentObj[field.name] = field.multiple ? [] : "";
}
}
}
// Check if there are sub-fields to process
const subFields = fields.filter((field) => field.type === "fields");
if (subFields?.length > 0) {
for (const field of subFields) {
this.processImageFields(updatedMetadata, field.fields || [], [...parents, field.name]);
}
}
}
}
/**
* Retrieve the file its front matter
*/
private getFileData() {
const editor = window.activeTextEditor;
if (!editor) {
return "";
}
const article = ArticleHelper.getFrontMatter(editor);
if (article?.data) {
this.pushMetadata(article!.data);
}
}
/**
* Update the tags in the current document
* @param tagType
* @param values
*/
private updateTags(tagType: TagType, values: string[], parents: string[]) {
const editor = window.activeTextEditor;
if (!editor) {
return "";
}
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
// Support multi-level fields
let parentObj = article.data;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
parentObj[tagType.toLowerCase()] = values || [];
ArticleHelper.update(editor, article);
this.pushMetadata(article!.data);
}
}
/**
* Update the tags in the current document
* @param data
*/
private updateCustomTaxonomy(data: CustomTaxonomyData) {
if (!data?.id || !data?.name) {
return;
}
const editor = window.activeTextEditor;
if (!editor) {
return "";
}
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
// Support multi-level fields
let parentObj = article.data;
for (const parent of data.parents || []) {
parentObj = parentObj[parent];
}
parentObj[data.name] = data.options || [];
ArticleHelper.update(editor, article);
this.pushMetadata(article!.data);
}
}
/**
* Add tag to the settings
* @param data
*/
private async addCustomTaxonomy(data: CustomTaxonomyData) {
if (!data?.id || !data?.option) {
return;
}
await Settings.updateCustomTaxonomy(data.id, data.option);
}
/**
* Add tag to the settings
* @param tagType
* @param value
*/
private async addTags(tagType: TagType, value: string) {
if (value) {
let options = tagType === TagType.tags ? Settings.get<string[]>(SETTING_TAXONOMY_TAGS, true) : Settings.get<string[]>(SETTING_TAXONOMY_CATEGORIES, true);
if (!options) {
options = [];
}
options.push(value);
const taxType = tagType === TagType.tags ? TaxonomyType.Tag : TaxonomyType.Category;
await Settings.updateTaxonomy(taxType, options);
}
}
/**
* Get article details
*/
private getArticleDetails() {
const baseUrl = Settings.get<string>(SETTING_SITE_BASEURL);
const editor = window.activeTextEditor;
if (!editor) {
return null;
}
if (!ArticleHelper.isMarkdownFile()) {
return null;
}
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.content) {
let content = article.content;
content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes
const mdTree = fromMarkdown(content);
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
const headings = elms.filter(node => node.type === 'heading');
const paragraphs = elms.filter(node => node.type === 'paragraph').length;
const images = elms.filter(node => node.type === 'image').length;
const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url);
const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length;
let externalLinks = links.filter(link => link.startsWith('http'));
if (baseUrl) {
externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase()));
}
const headers = [];
for (const header of headings) {
const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" ");
if (text) {
headers.push(text);
}
}
const wordCount = this.wordCount(0, mdTree);
return {
headings: headings.length,
headingsText: headers,
paragraphs,
images,
internalLinks,
externalLinks: externalLinks.length,
wordCount,
content: article.content
};
}
return null;
}
private getAllElms(node: Content | any, allElms?: any[]): any[] {
if (!allElms) {
allElms = [];
}
if (node.children?.length > 0) {
for (const child of node.children) {
allElms.push(Object.assign({}, child));
this.getAllElms(child, allElms);
}
}
return allElms;
this.sendMessage({ command: Command.closeSections });
}
private counts(acc: any, node: any) {
@@ -695,69 +140,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable {
);
}
/**
* Get the word count for the current document
*/
private wordCount(count: number, node: Content | any) {
if (node.type === "text") {
return count + node.value.split(" ").length;
} else {
return (node.children || []).reduce((childCount: number, childNode: any) => this.wordCount(childCount, childNode), count);
}
}
/**
* 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;
}
/**
* Updates a setting and refreshes the retrieved settings
* @param setting
* @param value
*/
private async updateSetting(setting: string, value: any) {
await Settings.update(setting, value);
this.getSettings();
}
/**
* Post data to the panel
* @param msg
*/
private postWebviewMessage(msg: { command: Command, data?: any }) {
this.panel?.webview?.postMessage(msg);
}
/**
* Retrieve the webview HTML contents
* @param webView
+5 -5
View File
@@ -19,8 +19,9 @@ import { Content } from './commands/Content';
import ContentProvider from './providers/ContentProvider';
import { Wysiwyg } from './commands/Wysiwyg';
import { Diagnostics } from './commands/Diagnostics';
import { PagesListener } from './listeners';
import { PagesListener } from './listeners/dashboard';
import { Backers } from './commands/Backers';
import { DataListener, SettingsListener } from './listeners/panel';
let frontMatterStatusBar: vscode.StatusBarItem;
let statusDebouncer: { (fnc: any, time: number): void; };
@@ -171,9 +172,8 @@ export async function activate(context: vscode.ExtensionContext) {
Template.init();
Preview.init();
const exView = ExplorerView.getInstance();
exView.getSettings();
exView.getFoldersAndFiles();
SettingsListener.getSettings();
DataListener.getFoldersAndFiles();
MarkdownFoldingProvider.triggerHighlighting();
});
@@ -197,7 +197,7 @@ export async function activate(context: vscode.ExtensionContext) {
subscriptions.push(vscode.workspace.onDidSaveTextDocument((doc: vscode.TextDocument) => {
if (doc.languageId === 'markdown') {
// Optimize the list of recently changed files
ExplorerView.getInstance().getFoldersAndFiles();
DataListener.getFoldersAndFiles();
}
}));
+96 -1
View File
@@ -2,7 +2,7 @@ import { MarkdownFoldingProvider } from './../providers/MarkdownFoldingProvider'
import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType';
import * as vscode from 'vscode';
import * as fs from "fs";
import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants';
import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants';
import { DumpOptions } from 'js-yaml';
import { FrontMatterParser, ParsedFrontMatter } from '../parsers';
import { Extension, Logger, Settings, SlugHelper } from '.';
@@ -17,6 +17,9 @@ import { ContentType } from '../models';
import { DateHelper } from './DateHelper';
import { DiagnosticSeverity, Position, window, Range } from 'vscode';
import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { Link, Parent } from 'mdast-util-from-markdown/lib';
import { Content } from 'mdast';
export class ArticleHelper {
private static notifiedFiles: string[] = [];
@@ -347,6 +350,98 @@ export class ArticleHelper {
return value;
}
/**
* Get the details of the current article
* @returns
*/
public static getDetails() {
const baseUrl = Settings.get<string>(SETTING_SITE_BASEURL);
const editor = window.activeTextEditor;
if (!editor) {
return null;
}
if (!ArticleHelper.isMarkdownFile()) {
return null;
}
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.content) {
let content = article.content;
content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes
const mdTree = fromMarkdown(content);
const elms: Parent[] | Link[] = this.getAllElms(mdTree);
const headings = elms.filter(node => node.type === 'heading');
const paragraphs = elms.filter(node => node.type === 'paragraph').length;
const images = elms.filter(node => node.type === 'image').length;
const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url);
const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length;
let externalLinks = links.filter(link => link.startsWith('http'));
if (baseUrl) {
externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase()));
}
const headers = [];
for (const header of headings) {
const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" ");
if (text) {
headers.push(text);
}
}
const wordCount = this.wordCount(0, mdTree);
return {
headings: headings.length,
headingsText: headers,
paragraphs,
images,
internalLinks,
externalLinks: externalLinks.length,
wordCount,
content: article.content
};
}
return null;
}
/**
* Retrieve all the elements from the markdown content
* @param node
* @param allElms
* @returns
*/
private static getAllElms(node: Content | any, allElms?: any[]): any[] {
if (!allElms) {
allElms = [];
}
if (node.children?.length > 0) {
for (const child of node.children) {
allElms.push(Object.assign({}, child));
this.getAllElms(child, allElms);
}
}
return allElms;
}
/**
* Get the word count for the current document
*/
private static wordCount(count: number, node: Content | any) {
if (node.type === "text") {
return count + node.value.split(" ").length;
} else {
return (node.children || []).reduce((childCount: number, childNode: any) => this.wordCount(childCount, childNode), count);
}
}
/**
* Parse a markdown file and its front matter
* @param fileContents
+1 -1
View File
@@ -1,4 +1,4 @@
import { PagesListener } from './../listeners/PagesListener';
import { PagesListener } from './../listeners/dashboard';
import { ArticleHelper, Settings } from ".";
import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
import { ContentType as IContentType, DraftField, Field } from '../models';
+54
View File
@@ -1,3 +1,4 @@
import { ExplorerView } from './../explorerView/ExplorerView';
import { Uri, window } from 'vscode';
import { dirname, join } from "path";
import { Field } from '../models';
@@ -78,4 +79,57 @@ export class ImageHelper {
}
return relPath;
}
/**
* Process the image fields in the content type
* @param updatedMetadata
* @param fields
* @param parents
*/
public static processImageFields(updatedMetadata: any, fields: Field[], parents: string[] = []) {
const imageFields = fields.filter((field) => field.type === "image");
const panel = ExplorerView.getInstance();
// Support multi-level fields
let parentObj = updatedMetadata;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
// Process image fields
if (parentObj) {
for (const field of imageFields) {
if (parentObj[field.name]) {
const imageData = ImageHelper.allRelToAbs(field, parentObj[field.name])
if (imageData) {
if (field.multiple && imageData instanceof Array) {
const preview = imageData.map(preview => preview && preview.absPath ? ({
...preview,
webviewUrl: panel.getWebview()?.asWebviewUri(preview.absPath).toString()
}) : null);
parentObj[field.name] = preview || [];
} else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) {
const preview = panel.getWebview()?.asWebviewUri(imageData.absPath);
parentObj[field.name] = {
...imageData,
webviewUrl: preview ? preview.toString() : null
};
}
} else {
parentObj[field.name] = field.multiple ? [] : "";
}
}
}
// Check if there are sub-fields to process
const subFields = fields.filter((field) => field.type === "fields");
if (subFields?.length > 0) {
for (const field of subFields) {
this.processImageFields(updatedMetadata, field.fields || [], [...parents, field.name]);
}
}
}
}
}
+4 -3
View File
@@ -11,6 +11,7 @@ import imageSize from "image-size";
import { EditorHelper } from "@estruyf/vscode";
import { ExplorerView } from "../explorerView/ExplorerView";
import { SortOption } from "../dashboardWebView/constants/SortOption";
import { DataListener, MediaListener } from "../listeners/panel";
export class MediaHelpers {
@@ -301,10 +302,10 @@ export class MediaHelpers {
}
});
}
panel.getMediaSelection();
MediaListener.getMediaSelection();
} else {
panel.getMediaSelection();
panel.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents });
MediaListener.getMediaSelection();
DataListener.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents });
}
}
}
+66
View File
@@ -0,0 +1,66 @@
import { workspace } from "vscode"
import { Extension, Settings } from "."
import { Dashboard } from "../commands/Dashboard"
import { Preview } from "../commands/Preview"
import { Template } from "../commands/Template"
import { CONTEXT, DefaultFields, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_CUSTOM_SCRIPTS, SETTING_DATE_FORMAT, SETTING_PANEL_FREEFORM, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_SLUG_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TAXONOMY_TAGS } from "../constants"
import { CustomScript, DataType, DraftField, PanelSettings as IPanelSettings, ScriptType } from "../models"
export class PanelSettings {
public static async get(): Promise<IPanelSettings> {
return {
seo: {
title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1,
slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1,
description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1,
content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1,
descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description
},
slug: {
prefix: Settings.get(SETTING_SLUG_PREFIX) || "",
suffix: Settings.get(SETTING_SLUG_SUFFIX) || "",
updateFileName: !!Settings.get<boolean>(SETTING_SLUG_UPDATE_FILE_NAME),
},
date: {
format: Settings.get<string>(SETTING_DATE_FORMAT) || ""
},
tags: Settings.get(SETTING_TAXONOMY_TAGS, true) || [],
categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [],
customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [],
freeform: Settings.get(SETTING_PANEL_FREEFORM),
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type),
isInitialized: await Template.isInitialized(),
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: Settings.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
preview: Preview.getSettings(),
commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [],
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
dashboardViewData: Dashboard.viewData,
draftField: Settings.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
isBacker: await Extension.getInstance().getState<boolean | undefined>(CONTEXT.backer, 'global'),
framework: Settings.get<string>(SETTINGS_FRAMEWORK_ID),
commands: {
start: Settings.get<string>(SETTINGS_FRAMEWORK_START)
},
dataTypes: Settings.get<DataType[]>(SETTINGS_DATA_TYPES),
}
}
/**
* Check if the writing settings are enabled
*/
public static isWritingSettingsEnabled(): boolean {
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) as boolean;
}
}
@@ -1,7 +1,7 @@
import { Dashboard } from "../commands/Dashboard";
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { Logger } from "../helpers/Logger";
import { Dashboard } from "../../commands/Dashboard";
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { Logger } from "../../helpers/Logger";
export abstract class BaseListener {
@@ -1,8 +1,8 @@
import { Dashboard } from "../commands/Dashboard";
import { ExtensionState } from "../constants";
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { Extension } from "../helpers";
import { Dashboard } from "../../commands/Dashboard";
import { ExtensionState } from "../../constants";
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { Extension } from "../../helpers";
import { BaseListener } from "./BaseListener";
@@ -1,12 +1,12 @@
import { DataFile } from './../models/DataFile';
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { DataFile } from './../../models/DataFile';
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { BaseListener } from "./BaseListener";
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { Folders } from '../commands/Folders';
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { Folders } from '../../commands/Folders';
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { dirname } from 'path';
import * as yaml from 'js-yaml';
import { Logger, Notifications } from '../helpers';
import { Logger, Notifications } from '../../helpers';
import { commands } from 'vscode';
@@ -1,9 +1,9 @@
import { commands, env } from "vscode";
import { SettingsListener } from ".";
import { COMMAND_NAME } from "../constants";
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { CustomScript, Extension } from "../helpers";
import { openFileInEditor } from "../helpers/openFileInEditor";
import { COMMAND_NAME } from "../../constants";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { CustomScript, Extension } from "../../helpers";
import { openFileInEditor } from "../../helpers/openFileInEditor";
import { BaseListener } from "./BaseListener";
@@ -1,11 +1,11 @@
import { Telemetry } from './../helpers/Telemetry';
import { MediaHelpers } from './../helpers/MediaHelpers';
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { Telemetry } from '../../helpers/Telemetry';
import { MediaHelpers } from '../../helpers/MediaHelpers';
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { BaseListener } from "./BaseListener";
import { DashboardCommand } from '../dashboardWebView/DashboardCommand';
import { SortingOption } from '../dashboardWebView/models';
import { DashboardCommand } from '../../dashboardWebView/DashboardCommand';
import { SortingOption } from '../../dashboardWebView/models';
import { commands, env, Uri } from 'vscode';
import { COMMAND_NAME, TelemetryEvent } from '../constants';
import { COMMAND_NAME, TelemetryEvent } from '../../constants';
import * as os from 'os';
@@ -1,17 +1,17 @@
import { isValidFile } from './../helpers/isValidFile';
import { isValidFile } from '../../helpers/isValidFile';
import { existsSync } from "fs";
import { basename, dirname, join } from "path";
import { commands, FileSystemWatcher, RelativePattern, Uri, workspace } from "vscode";
import { Dashboard } from "../commands/Dashboard";
import { Folders } from "../commands/Folders";
import { COMMAND_NAME, DefaultFields, SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../constants";
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { Page } from "../dashboardWebView/models";
import { ArticleHelper, Logger, Settings } from "../helpers";
import { ContentType } from "../helpers/ContentType";
import { DateHelper } from "../helpers/DateHelper";
import { Notifications } from "../helpers/Notifications";
import { Dashboard } from "../../commands/Dashboard";
import { Folders } from "../../commands/Folders";
import { COMMAND_NAME, DefaultFields, SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants";
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { Page } from "../../dashboardWebView/models";
import { ArticleHelper, Logger, Settings } from "../../helpers";
import { ContentType } from "../../helpers/ContentType";
import { DateHelper } from "../../helpers/DateHelper";
import { Notifications } from "../../helpers/Notifications";
import { BaseListener } from "./BaseListener";
@@ -1,9 +1,9 @@
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../constants";
import { DashboardCommand } from "../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { DashboardSettings, Settings } from "../helpers";
import { FrameworkDetector } from "../helpers/FrameworkDetector";
import { Framework } from "../models";
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../../constants";
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { DashboardSettings, Settings } from "../../helpers";
import { FrameworkDetector } from "../../helpers/FrameworkDetector";
import { Framework } from "../../models";
import { BaseListener } from "./BaseListener";
@@ -1,5 +1,5 @@
import { DashboardMessage } from "../dashboardWebView/DashboardMessage";
import { Telemetry } from "../helpers/Telemetry";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { Telemetry } from "../../helpers/Telemetry";
import { BaseListener } from "./BaseListener";
@@ -1,4 +1,3 @@
export * from './BaseListener';
export * from './DashboardListener';
export * from './DataListener';
export * from './ExtensionListener';
+27
View File
@@ -0,0 +1,27 @@
import { Article } from "../../commands";
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { BaseListener } from "./BaseListener";
export class ArticleListener extends BaseListener {
/**
* Process the messages for the dashboard views
* @param msg
*/
public static process(msg: { command: any, data: any }) {
super.process(msg);
switch(msg.command) {
case CommandToCode.updateSlug:
Article.generateSlug();
break;
case CommandToCode.updateLastMod:
Article.setLastModifiedDate();
break;
case CommandToCode.publish:
Article.toggleDraft();
break;
}
}
}
+28
View File
@@ -0,0 +1,28 @@
import { Extension } from './../../helpers/Extension';
import { ExplorerView } from './../../explorerView/ExplorerView';
import { Logger } from "../../helpers";
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { Command } from '../../panelWebView/Command';
export abstract class BaseListener {
public static process(msg: { command: CommandToCode, data: any }) {}
/**
* Send a message to the webview
* @param command
* @param data
*/
public static sendMsg(command: Command, data: any) {
Logger.info(`Sending message to panel: ${command}`);
const extPath = Extension.getInstance().extensionPath;
const panel = ExplorerView.getInstance(extPath);
panel.sendMessage({
command,
data
});
}
}
+196
View File
@@ -0,0 +1,196 @@
import { ImageHelper } from './../../helpers/ImageHelper';
import { Folders } from "../../commands/Folders";
import { Command } from "../../panelWebView/Command";
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { BaseListener } from "./BaseListener";
import { commands, ThemeIcon, window } from 'vscode';
import { ArticleHelper, Settings } from "../../helpers";
import { COMMAND_NAME, DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES } from "../../constants";
import { Article } from '../../commands';
const FILE_LIMIT = 10;
export class DataListener extends BaseListener {
/**
* Process the messages for the dashboard views
* @param msg
*/
public static process(msg: { command: CommandToCode, data: any }) {
super.process(msg);
switch(msg.command) {
case CommandToCode.getData:
this.getFoldersAndFiles();
this.getFileData();
break;
case CommandToCode.createContent:
commands.executeCommand(COMMAND_NAME.createContent);
break;
case CommandToCode.createTemplate:
commands.executeCommand(COMMAND_NAME.createTemplate);
break;
case CommandToCode.updateMetadata:
this.updateMetadata(msg.data);
break;
case CommandToCode.frameworkCommand:
this.openTerminalWithCommand(msg.data.command);
break;
}
}
/**
* Retrieve the information about the registered folders and its files
*/
public static async getFoldersAndFiles() {
const folders = await Folders.getInfo(FILE_LIMIT) || null;
this.sendMsg(Command.folderInfo, folders);
}
/**
* Triggers a metadata change in the panel
* @param metadata
*/
public static pushMetadata(metadata: any) {
const wsFolder = Folders.getWorkspaceFolder();
const filePath = window.activeTextEditor?.document.uri.fsPath;
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
const contentTypes = Settings.get<string>(SETTING_TAXONOMY_CONTENT_TYPES);
const articleDetails = ArticleHelper.getDetails();
if (articleDetails) {
metadata.articleDetails = articleDetails;
}
let updatedMetadata = Object.assign({}, metadata);
if (commaSeparated) {
for (const key of commaSeparated) {
if (updatedMetadata[key] && typeof updatedMetadata[key] === "string") {
updatedMetadata[key] = updatedMetadata[key].split(",").map((s: string) => s.trim());
}
}
}
const keys = Object.keys(updatedMetadata);
if (keys.length > 0) {
updatedMetadata.filePath = filePath;
}
if (keys.length > 0 && contentTypes && wsFolder) {
// Get the current content type
const contentType = ArticleHelper.getContentType(updatedMetadata);
if (contentType) {
ImageHelper.processImageFields(updatedMetadata, contentType.fields)
}
}
// Check slug
if (!updatedMetadata[DefaultFields.Slug]) {
const slug = Article.getSlug();
if (slug) {
updatedMetadata[DefaultFields.Slug] = slug;
}
}
this.sendMsg(Command.metadata, updatedMetadata);
}
/**
* Update the metadata of the article
*/
public static async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) {
if (!field) {
return;
}
const editor = window.activeTextEditor;
if (!editor) {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
if (!article) {
return;
}
const contentType = ArticleHelper.getContentType(article.data);
const dateFields = contentType.fields.filter((field) => field.type === "datetime");
const imageFields = contentType.fields.filter((field) => field.type === "image" && field.multiple);
// Support multi-level fields
let parentObj = article.data;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
for (const dateField of dateFields) {
if ((field === dateField.name) && value) {
parentObj[field] = Article.formatDate(new Date(value));
} else if (!imageFields.find(f => f.name === field)) {
// Only override the field data if it is not an multiselect image field
parentObj[field] = value;
}
}
for (const imageField of imageFields) {
if (field === imageField.name) {
// If value is an array, it means it comes from the explorer view itself (deletion)
if (Array.isArray(value)) {
parentObj[field] = value || [];
} else { // Otherwise it is coming from the media dashboard (addition)
let fieldValue = parentObj[field];
if (fieldValue && !Array.isArray(fieldValue)) {
fieldValue = [fieldValue];
}
const crntData = Object.assign([], fieldValue);
const allRelPaths = [...(crntData || []), value];
parentObj[field] = [...new Set(allRelPaths)].filter(f => f);
}
}
}
ArticleHelper.update(editor, article);
this.pushMetadata(article.data);
}
/**
* Retrieve the file its front matter
*/
public static async getFileData() {
const editor = window.activeTextEditor;
if (!editor) {
return "";
}
const article = ArticleHelper.getFrontMatter(editor);
if (article?.data) {
this.pushMetadata(article!.data);
}
}
/**
* Open a terminal and run the passed command
* @param command
*/
private static openTerminalWithCommand(command: string) {
if (command) {
let terminal = window.activeTerminal;
if (!terminal || (terminal && terminal.state.isInteractedWith === true)) {
terminal = window.createTerminal({
name: `Starting local server`,
iconPath: new ThemeIcon('server-environment'),
message: `Starting local server`,
});
}
if (terminal) {
terminal.sendText(command);
terminal.show(false);
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { BaseListener } from "./BaseListener";
import { commands, env as vscodeEnv } from 'vscode';
import * as os from 'os';
import { exec } from 'child_process';
import { Folders } from "../../commands/Folders";
import { COMMAND_NAME } from "../../constants";
import { SettingsListener } from ".";
import { openFileInEditor } from "../../helpers";
export class ExtensionListener extends BaseListener {
/**
* Process the messages for the dashboard views
* @param msg
*/
public static process(msg: { command: any, data: any }) {
super.process(msg);
switch(msg.command) {
case CommandToCode.openFile:
this.openFile();
break;
case CommandToCode.openProject:
this.openFolder();
break;
case CommandToCode.openInEditor:
openFileInEditor(msg.data);
break;
case CommandToCode.initProject:
this.initialize();
break;
case CommandToCode.toggleCenterMode:
commands.executeCommand(`workbench.action.toggleCenteredLayout`);
break;
case CommandToCode.openPreview:
commands.executeCommand(COMMAND_NAME.preview);
break;
case CommandToCode.openDashboard:
commands.executeCommand(COMMAND_NAME.dashboard);
break;
}
}
/**
* Initialize project
*/
private static async initialize() {
await commands.executeCommand(COMMAND_NAME.init);
SettingsListener.getSettings();
}
/**
* Open the file in your explorer
*/
private static openFile() {
if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
commands.executeCommand('remote-wsl.revealInExplorer');
} else {
commands.executeCommand('revealFileInOS');
}
}
/**
* Opens the project folder
*/
private static openFolder() {
const wsFolder = Folders.getWorkspaceFolder();
if (wsFolder) {
const wsPath = wsFolder.fsPath;
if (os.type() === "Darwin") {
exec(`open ${wsPath}`);
} else if (os.type() === "Windows_NT") {
exec(`explorer ${wsPath}`);
} else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") {
exec('explorer.exe `wslpath -w "$PWD"`');
} else {
exec(`xdg-open ${wsPath}`);
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
import { commands } from "vscode";
import { Dashboard } from "../../commands/Dashboard";
import { COMMAND_NAME } from "../../constants";
import { DashboardData } from "../../models";
import { Command } from "../../panelWebView/Command";
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { BaseListener } from "./BaseListener";
export class MediaListener extends BaseListener {
/**
* Process the messages for the dashboard views
* @param msg
*/
public static process(msg: { command: any, data: any }) {
super.process(msg);
switch(msg.command) {
case CommandToCode.selectImage:
this.selectMedia(msg);
break;
}
}
/**
* Select a media file
*/
private static async selectMedia(msg: { data: any }) {
await commands.executeCommand(COMMAND_NAME.dashboard, {
type: "media",
data: msg.data
} as DashboardData);
this.getMediaSelection();
}
/**
* Return the media selection
*/
public static async getMediaSelection() {
this.sendMsg(Command.mediaSelectionData, Dashboard.viewData);
}
}
+38
View File
@@ -0,0 +1,38 @@
import { SETTING_CUSTOM_SCRIPTS } from "../../constants";
import { CustomScript, Settings } from "../../helpers";
import { CustomScript as ICustomScript } from "../../models";
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { BaseListener } from "./BaseListener";
export class ScriptListener extends BaseListener {
/**
* Process the messages for the dashboard views
* @param msg
*/
public static process(msg: { command: any, data: any }) {
super.process(msg);
switch(msg.command) {
case CommandToCode.runCustomScript:
this.runCustomScript(msg);
break;
}
}
/**
* Run a custom script
* @param msg
*/
private static runCustomScript(msg: { command: string, data: any}) {
const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS);
if (msg?.data?.title && msg?.data?.script && scripts) {
const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title);
if (customScript?.script && customScript?.title) {
CustomScript.run(customScript);
}
}
}
}
+87
View File
@@ -0,0 +1,87 @@
import { commands, workspace } from "vscode";
import { EXTENSION_BETA_ID, EXTENSION_ID, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_PREVIEW_HOST } from "../../constants";
import { Extension, Settings } from "../../helpers";
import { PanelSettings } from "../../helpers/PanelSettings";
import { Command } from "../../panelWebView/Command";
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { BaseListener } from "./BaseListener";
export class SettingsListener extends BaseListener {
/**
* Process the messages for the dashboard views
* @param msg
*/
public static process(msg: { command: CommandToCode, data: any }) {
super.process(msg);
switch(msg.command) {
case CommandToCode.getData:
this.getSettings();
break;
case CommandToCode.openSettings:
this.openVSCodeSettings();
break;
case CommandToCode.toggleWritingSettings:
this.toggleWritingSettings();
break;
case CommandToCode.updateModifiedUpdating:
this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false);
break;
case CommandToCode.updateFmHighlight:
this.updateSetting(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false);
break;
case CommandToCode.updatePreviewUrl:
this.updateSetting(SETTING_PREVIEW_HOST, msg.data || "");
break;
case CommandToCode.updateStartCommand:
this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || "");
break;
}
}
/**
* Retrieve the extension settings required to render the panel
*/
public static async getSettings() {
const panelSettings = await PanelSettings.get();
this.sendMsg(Command.settings, panelSettings);
}
/**
* Open the settings view of VS Code
*/
public static openVSCodeSettings() {
const isBeta = Extension.getInstance().isBetaVersion();
commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`);
}
/**
* Updates a setting and refreshes the retrieved settings
* @param setting
* @param value
*/
private static async updateSetting(setting: string, value: any) {
await Settings.update(setting, value);
this.getSettings();
}
/**
* Toggle the writing settings
*/
private static async toggleWritingSettings() {
const config = workspace.getConfiguration("", { languageId: "markdown" });
const enabled = PanelSettings.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();
}
}
+130
View File
@@ -0,0 +1,130 @@
import { CommandToCode } from "../../panelWebView/CommandToCode";
import { TagType } from "../../panelWebView/TagType";
import { BaseListener } from "./BaseListener";
import { window } from "vscode";
import { ArticleHelper, Settings } from "../../helpers";
import { CustomTaxonomyData, TaxonomyType } from "../../models";
import { DataListener } from ".";
import { SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../../constants";
export class TaxonomyListener extends BaseListener {
/**
* Process the messages for the dashboard views
* @param msg
*/
public static process(msg: { command: any, data: any }) {
super.process(msg);
switch(msg.command) {
case CommandToCode.updateTags:
this.updateTags(TagType.tags, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateCategories:
this.updateTags(TagType.categories, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateKeywords:
this.updateTags(TagType.keywords, msg.data?.values || [], msg.data?.parents || []);
break;
case CommandToCode.updateCustomTaxonomy:
this.updateCustomTaxonomy(msg.data);
break;
case CommandToCode.addTagToSettings:
this.addTags(TagType.tags, msg.data);
break;
case CommandToCode.addCategoryToSettings:
this.addTags(TagType.categories, msg.data);
break;
case CommandToCode.addToCustomTaxonomy:
this.addCustomTaxonomy(msg.data);
break;
}
}
/**
* Update the tags in the current document
* @param tagType
* @param values
*/
private static updateTags(tagType: TagType, values: string[], parents: string[]) {
const editor = window.activeTextEditor;
if (!editor) {
return "";
}
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
// Support multi-level fields
let parentObj = article.data;
for (const parent of parents || []) {
parentObj = parentObj[parent];
}
parentObj[tagType.toLowerCase()] = values || [];
ArticleHelper.update(editor, article);
DataListener.pushMetadata(article!.data);
}
}
/**
* Update the tags in the current document
* @param data
*/
private static updateCustomTaxonomy(data: CustomTaxonomyData) {
if (!data?.id || !data?.name) {
return;
}
const editor = window.activeTextEditor;
if (!editor) {
return "";
}
const article = ArticleHelper.getFrontMatter(editor);
if (article && article.data) {
// Support multi-level fields
let parentObj = article.data;
for (const parent of data.parents || []) {
parentObj = parentObj[parent];
}
parentObj[data.name] = data.options || [];
ArticleHelper.update(editor, article);
DataListener.pushMetadata(article!.data);
}
}
/**
* Add tag to the settings
* @param data
*/
private static async addCustomTaxonomy(data: CustomTaxonomyData) {
if (!data?.id || !data?.option) {
return;
}
await Settings.updateCustomTaxonomy(data.id, data.option);
}
/**
* Add tag to the settings
* @param tagType
* @param value
*/
private static async addTags(tagType: TagType, value: string) {
if (value) {
let options = tagType === TagType.tags ? Settings.get<string[]>(SETTING_TAXONOMY_TAGS, true) : Settings.get<string[]>(SETTING_TAXONOMY_CATEGORIES, true);
if (!options) {
options = [];
}
options.push(value);
const taxType = tagType === TagType.tags ? TaxonomyType.Tag : TaxonomyType.Category;
await Settings.updateTaxonomy(taxType, options);
}
}
}
+8
View File
@@ -0,0 +1,8 @@
export * from './ArticleListener';
export * from './BaseListener';
export * from './DataListener';
export * from './ExtensionListener';
export * from './MediaListener';
export * from './ScriptListener';
export * from './SettingsListener';
export * from './TaxonomyListener';
+7 -5
View File
@@ -11,20 +11,21 @@ export interface PanelSettings {
date: DateInfo;
categories: string[];
customTaxonomy: CustomTaxonomy[];
freeform: boolean;
freeform: boolean | undefined;
scripts: CustomScript[];
isInitialized: boolean;
modifiedDateUpdate: boolean;
writingSettingsEnabled: boolean;
fmHighlighting: boolean;
fmHighlighting: boolean | undefined;
preview: PreviewSettings;
contentTypes: ContentType[];
dashboardViewData: DashboardData | undefined;
draftField: DraftField;
draftField: DraftField | undefined;
isBacker: boolean | undefined;
framework: string | undefined;
commands: FrameworkCommands;
dataTypes: DataType[] | undefined;
commaSeparatedFields: string[];
}
export interface FrameworkCommands {
@@ -70,8 +71,9 @@ export interface SEO {
}
export interface Slug {
prefix: number;
suffix: number;
prefix: number | string;
suffix: number | string;
updateFileName?: boolean;
}
export interface FolderInfo {
+3
View File
@@ -2,6 +2,9 @@ export * from './Choice';
export * from './ContentFolder';
export * from './CustomTaxonomyData';
export * from './DashboardData';
export * from './DataFile';
export * from './DataFolder';
export * from './DataType';
export * from './DraftField';
export * from './Framework';
export * from './MediaPaths';
+5 -5
View File
@@ -161,7 +161,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
icon={<TagIcon />}
crntSelected={parent[field.name] as string[] || []}
options={settings?.tags || []}
freeform={settings.freeform}
freeform={settings.freeform || false}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus}
parents={parentFields} />
@@ -179,7 +179,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
icon={<ListUnorderedIcon />}
crntSelected={selectedValues as string[] || []}
options={taxonomyData?.options || []}
freeform={settings.freeform}
freeform={settings.freeform || false}
focussed={focusElm === TagType.custom}
unsetFocus={unsetFocus}
fieldName={field.name}
@@ -196,7 +196,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
icon={<ListUnorderedIcon />}
crntSelected={parent.categories as string[] || []}
options={settings.categories}
freeform={settings.freeform}
freeform={settings.freeform || false}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus}
parents={parentFields} />
@@ -210,8 +210,8 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<DraftField
label={field.title || field.name}
type={draftField.type}
choices={draftField.choices || []}
type={draftField?.type || "boolean"}
choices={draftField?.choices || []}
value={value as boolean | string | null | undefined}
onChanged={(value: boolean | string) => sendUpdate(field.name, value, parentFields)} />
</FieldBoundary>