Merge branch 'issue/175' into dev

This commit is contained in:
Elio Struyf
2022-03-04 15:05:31 +01:00
60 changed files with 2521 additions and 136 deletions
+26 -2
View File
@@ -1,5 +1,5 @@
import { isValidFile } from './../helpers/isValidFile';
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTINGS_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants';
import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants';
import * as vscode from 'vscode';
import { Field, TaxonomyType } from "../models";
import { format } from "date-fns";
@@ -13,6 +13,7 @@ import { parseWinPath } from '../helpers/parseWinPath';
import { Telemetry } from '../helpers/Telemetry';
import { ParsedFrontMatter } from '../parsers';
import { MediaListener } from '../listeners/panel';
import { NavigationType } from '../dashboardWebView/models';
export class Article {
@@ -200,7 +201,7 @@ export class Article {
}
// Update the fields containing a custom placeholder that depends on slug
const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS);
const placeholders = Settings.get<{id: string, value: string}[]>(SETTING_CONTENT_PLACEHOLDERS);
const customPlaceholders = placeholders?.filter(p => p.value.includes("{{slug}}"));
for (const customPlaceholder of (customPlaceholders || [])) {
const customPlaceholderFields = contentType.fields.filter(f => f.default === `{{${customPlaceholder.id}}}`);
@@ -340,6 +341,29 @@ export class Article {
MediaListener.getMediaSelection();
}
/**
* Insert a snippet into the article
*/
public static async insertSnippet() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const position = editor.selection.active;
const selectionText = editor.document.getText(editor.selection);
await vscode.commands.executeCommand(COMMAND_NAME.dashboard, {
type: NavigationType.Snippets,
data: {
filePath: editor.document.uri.fsPath,
fieldName: basename(editor.document.uri.fsPath),
position,
selection: selectionText
}
} as DashboardData);
}
/**
* Get the current article
*/
+4 -3
View File
@@ -1,4 +1,4 @@
import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
import { SETTING_DASHBOARD_OPENONSTART, CONTEXT } from '../constants';
import { join } from "path";
import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode";
import { Logger, Settings as SettingsHelper } from '../helpers';
@@ -8,7 +8,7 @@ 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, PagesListener, ExtensionListener } from '../listeners/dashboard';
import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener } from '../listeners/dashboard';
import { MediaListener as PanelMediaListener } from '../listeners/panel'
export class Dashboard {
@@ -24,7 +24,7 @@ export class Dashboard {
* Init the dashboard
*/
public static async init() {
const openOnStartup = SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART);
const openOnStartup = SettingsHelper.get(SETTING_DASHBOARD_OPENONSTART);
if (openOnStartup) {
Dashboard.open();
}
@@ -143,6 +143,7 @@ export class Dashboard {
SettingsListener.process(msg);
DataListener.process(msg);
TelemetryListener.process(msg);
SnippetListener.process(msg);
});
}
+5 -5
View File
@@ -1,5 +1,5 @@
import { Questions } from './../helpers/Questions';
import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants';
import { SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_STATIC_FOLDER, SETTING_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants';
import { commands, Uri, workspace, window } from "vscode";
import { basename, join } from "path";
import { ContentFolder, FileInfo, FolderInfo } from "../models";
@@ -26,7 +26,7 @@ export class Folders {
*/
public static async addMediaFolder(data?: {selectedFolder?: string}) {
let wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
let startPath = "";
@@ -210,7 +210,7 @@ export class Folders {
* Get the registered folders information
*/
public static async getInfo(limit?: number): Promise<FolderInfo[] | null> {
const supportedFiles = Settings.get<string[]>(SETTINGS_CONTENT_SUPPORTED_FILETYPES);
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES);
const folders = Folders.get();
if (folders && folders.length > 0) {
let folderInfo: FolderInfo[] = [];
@@ -281,7 +281,7 @@ export class Folders {
*/
public static get(): ContentFolder[] {
const wsFolder = Folders.getWorkspaceFolder();
const folders: ContentFolder[] = Settings.get(SETTINGS_CONTENT_PAGE_FOLDERS) as ContentFolder[];
const folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[];
return folders.map(folder => ({
...folder,
@@ -301,7 +301,7 @@ export class Folders {
path: Folders.relWsFolder(folder, wsFolder)
}));
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true);
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true);
// Reinitialize the folder listeners
PagesListener.startWatchers();
+5 -2
View File
@@ -6,7 +6,8 @@ import { Notifications } from "../helpers/Notifications";
import { Template } from "./Template";
import { Folders } from "./Folders";
import { Settings } from "../helpers";
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants";
import { SETTING_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants";
import { SettingsListener } from '../listeners/dashboard';
export class Project {
@@ -29,7 +30,7 @@ categories: []
public static async init(sampleTemplate: boolean = true) {
try {
Settings.createTeamSettings();
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
const folder = Template.getSettings();
const templatePath = Project.templatePath();
@@ -50,6 +51,8 @@ categories: []
}
Telemetry.send(TelemetryEvent.initialization)
SettingsListener.getSettings();
} catch (err: any) {
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
}
+6 -2
View File
@@ -2,7 +2,7 @@ import { Questions } from './../helpers/Questions';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants';
import { SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants';
import { ArticleHelper, Settings } from '../helpers';
import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
@@ -23,6 +23,10 @@ export class Template {
public static async init() {
const isInitialized = await Template.isInitialized();
await vscode.commands.executeCommand('setContext', CONTEXT.canInit, !isInitialized);
if (isInitialized) {
await vscode.commands.executeCommand('setContext', CONTEXT.initialized, true);
}
}
/**
@@ -52,7 +56,7 @@ export class Template {
public static async generate() {
const folder = Template.getSettings();
const editor = vscode.window.activeTextEditor;
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
if (folder && editor && ArticleHelper.isMarkdownFile()) {
const article = ArticleHelper.getFrontMatter(editor);
+5 -2
View File
@@ -1,5 +1,5 @@
import { commands, window, Selection, QuickPickItem } from "vscode";
import { COMMAND_NAME, CONTEXT, SETTINGS_CONTENT_WYSIWYG } from "../constants";
import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from "../constants";
import { Settings } from "../helpers";
enum MarkupType {
@@ -24,7 +24,7 @@ export class Wysiwyg {
*/
public static async registerCommands(subscriptions: any) {
const wysiwygEnabled = Settings.get(SETTINGS_CONTENT_WYSIWYG);
const wysiwygEnabled = Settings.get(SETTING_CONTENT_WYSIWYG);
if (!wysiwygEnabled) {
return;
@@ -54,6 +54,7 @@ export class Wysiwyg {
{ label: "$(tasklist) Task list", detail: "Add a task list", alwaysShow: true },
{ label: "$(code) Code", detail: "Add inline code snippet", alwaysShow: true },
{ label: "$(symbol-namespace) Code block", detail: "Add a code block", alwaysShow: true },
{ label: "$(quote) Blockquote", detail: "Add a blockquote", alwaysShow: true },
]
const option = await window.showQuickPick([ ...qpItems ], {
@@ -73,6 +74,8 @@ export class Wysiwyg {
await this.addMarkup(MarkupType.code);
} else if (option.label === qpItems[4].label) {
await this.addMarkup(MarkupType.codeblock);
} else if (option.label === qpItems[5].label) {
await this.addMarkup(MarkupType.blockquote);
}
}
}));
+5 -1
View File
@@ -29,13 +29,17 @@ export const COMMAND_NAME = {
preview: getCommandName("preview"),
dashboard: getCommandName("dashboard"),
dashboardMedia: getCommandName("dashboard.media"),
dashboardSnippets: getCommandName("dashboard.snippets"),
dashboardData: getCommandName("dashboard.data"),
dashboardClose: getCommandName("dashboard.close"),
promote: getCommandName("promoteSettings"),
insertImage: getCommandName("insertImage"),
createFolder: getCommandName("createFolder"),
diagnostics: getCommandName("diagnostics"),
// Insert dashboards
insertImage: getCommandName("insertImage"),
insertSnippet: getCommandName("insertSnippet"),
// WYSIWYG
bold: getCommandName("markup.bold"),
italic: getCommandName("markup.italic"),
+7
View File
@@ -0,0 +1,7 @@
export const SnippetVariables = {
FM_SELECTED_TEXT: 'FM_SELECTED_TEXT',
FM_TEXT: 'FM_TEXT_',
FM_MULTILINE: 'FM_MULTILINE_',
};
+1
View File
@@ -4,6 +4,7 @@ export const TelemetryEvent = {
openContentDashboard: 'openContentDashboard',
openMediaDashboard: 'openMediaDashboard',
openDataDashboard: 'openDataDashboard',
openSnippetsDashboard: 'openSnippetsDashboard',
closeDashboard: 'closeDashboard',
generateSlug: 'generateSlug',
createContentFromTemplate: 'createContentFromTemplate',
+439
View File
@@ -0,0 +1,439 @@
/**
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
*
* SOURCE: https://github.com/microsoft/vscode/blob/32b031eeefc4fd27a21659d35070967bfe965bcc/src/vs/base/common/charCode.ts
*/
export const enum CharCode {
Null = 0,
/**
* The `\b` character.
*/
Backspace = 8,
/**
* The `\t` character.
*/
Tab = 9,
/**
* The `\n` character.
*/
LineFeed = 10,
/**
* The `\r` character.
*/
CarriageReturn = 13,
Space = 32,
/**
* The `!` character.
*/
ExclamationMark = 33,
/**
* The `"` character.
*/
DoubleQuote = 34,
/**
* The `#` character.
*/
Hash = 35,
/**
* The `$` character.
*/
DollarSign = 36,
/**
* The `%` character.
*/
PercentSign = 37,
/**
* The `&` character.
*/
Ampersand = 38,
/**
* The `'` character.
*/
SingleQuote = 39,
/**
* The `(` character.
*/
OpenParen = 40,
/**
* The `)` character.
*/
CloseParen = 41,
/**
* The `*` character.
*/
Asterisk = 42,
/**
* The `+` character.
*/
Plus = 43,
/**
* The `,` character.
*/
Comma = 44,
/**
* The `-` character.
*/
Dash = 45,
/**
* The `.` character.
*/
Period = 46,
/**
* The `/` character.
*/
Slash = 47,
Digit0 = 48,
Digit1 = 49,
Digit2 = 50,
Digit3 = 51,
Digit4 = 52,
Digit5 = 53,
Digit6 = 54,
Digit7 = 55,
Digit8 = 56,
Digit9 = 57,
/**
* The `:` character.
*/
Colon = 58,
/**
* The `;` character.
*/
Semicolon = 59,
/**
* The `<` character.
*/
LessThan = 60,
/**
* The `=` character.
*/
Equals = 61,
/**
* The `>` character.
*/
GreaterThan = 62,
/**
* The `?` character.
*/
QuestionMark = 63,
/**
* The `@` character.
*/
AtSign = 64,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
/**
* The `[` character.
*/
OpenSquareBracket = 91,
/**
* The `\` character.
*/
Backslash = 92,
/**
* The `]` character.
*/
CloseSquareBracket = 93,
/**
* The `^` character.
*/
Caret = 94,
/**
* The `_` character.
*/
Underline = 95,
/**
* The ``(`)`` character.
*/
BackTick = 96,
a = 97,
b = 98,
c = 99,
d = 100,
e = 101,
f = 102,
g = 103,
h = 104,
i = 105,
j = 106,
k = 107,
l = 108,
m = 109,
n = 110,
o = 111,
p = 112,
q = 113,
r = 114,
s = 115,
t = 116,
u = 117,
v = 118,
w = 119,
x = 120,
y = 121,
z = 122,
/**
* The `{` character.
*/
OpenCurlyBrace = 123,
/**
* The `|` character.
*/
Pipe = 124,
/**
* The `}` character.
*/
CloseCurlyBrace = 125,
/**
* The `~` character.
*/
Tilde = 126,
U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent
U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent
U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent
U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde
U_Combining_Macron = 0x0304, // U+0304 Combining Macron
U_Combining_Overline = 0x0305, // U+0305 Combining Overline
U_Combining_Breve = 0x0306, // U+0306 Combining Breve
U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above
U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis
U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above
U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above
U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent
U_Combining_Caron = 0x030C, // U+030C Combining Caron
U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above
U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above
U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent
U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu
U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve
U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above
U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above
U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above
U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right
U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below
U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below
U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below
U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below
U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above
U_Combining_Horn = 0x031B, // U+031B Combining Horn
U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below
U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below
U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below
U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below
U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below
U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below
U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below
U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below
U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below
U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below
U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below
U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla
U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek
U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below
U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below
U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below
U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below
U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below
U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below
U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below
U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below
U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below
U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line
U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line
U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay
U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay
U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay
U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay
U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay
U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below
U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below
U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below
U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below
U_Combining_X_Above = 0x033D, // U+033D Combining X Above
U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde
U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline
U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark
U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark
U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni
U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis
U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos
U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni
U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above
U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below
U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below
U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below
U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above
U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above
U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above
U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below
U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below
U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner
U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above
U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above
U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata
U_Combining_X_Below = 0x0353, // U+0353 Combining X Below
U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below
U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below
U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below
U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above
U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right
U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below
U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below
U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above
U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below
U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve
U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron
U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below
U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde
U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve
U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below
U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A
U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E
U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I
U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O
U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U
U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C
U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D
U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H
U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M
U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R
U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T
U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V
U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X
/**
* Unicode Character 'LINE SEPARATOR' (U+2028)
* http://www.fileformat.info/info/unicode/char/2028/index.htm
*/
LINE_SEPARATOR = 0x2028,
/**
* Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
* http://www.fileformat.info/info/unicode/char/2029/index.htm
*/
PARAGRAPH_SEPARATOR = 0x2029,
/**
* Unicode Character 'NEXT LINE' (U+0085)
* http://www.fileformat.info/info/unicode/char/0085/index.htm
*/
NEXT_LINE = 0x0085,
// http://www.fileformat.info/info/unicode/category/Sk/list.htm
U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX
U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT
U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS
U_MACRON = 0x00AF, // U+00AF MACRON
U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT
U_CEDILLA = 0x00B8, // U+00B8 CEDILLA
U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD
U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD
U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD
U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD
U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING
U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING
U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK
U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK
U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN
U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN
U_BREVE = 0x02D8, // U+02D8 BREVE
U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE
U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE
U_OGONEK = 0x02DB, // U+02DB OGONEK
U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE
U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT
U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK
U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT
U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR
U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR
U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR
U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR
U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR
U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK
U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK
U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED
U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD
U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD
U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD
U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD
U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING
U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT
U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT
U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT
U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE
U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON
U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE
U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE
U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE
U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE
U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF
U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF
U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW
U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN
U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS
U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS
U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS
U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI
U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI
U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI
U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA
U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA
U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI
U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA
U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA
U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI
U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA
U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA
U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA
U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA
U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA
U_IDEOGRAPHIC_FULL_STOP = 0x3002, // U+3002 IDEOGRAPHIC FULL STOP
U_LEFT_CORNER_BRACKET = 0x300C, // U+300C LEFT CORNER BRACKET
U_RIGHT_CORNER_BRACKET = 0x300D, // U+300D RIGHT CORNER BRACKET
U_LEFT_BLACK_LENTICULAR_BRACKET = 0x3010, // U+3010 LEFT BLACK LENTICULAR BRACKET
U_RIGHT_BLACK_LENTICULAR_BRACKET = 0x3011, // U+3011 RIGHT BLACK LENTICULAR BRACKET
U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE'
/**
* UTF-8 BOM
* Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)
* http://www.fileformat.info/info/unicode/char/feff/index.htm
*/
UTF8_BOM = 65279,
U_FULLWIDTH_SEMICOLON = 0xFF1B, // U+FF1B FULLWIDTH SEMICOLON
U_FULLWIDTH_COMMA = 0xFF0C, // U+FF0C FULLWIDTH COMMA
}
+1
View File
@@ -1,5 +1,6 @@
export const CONTEXT = {
canInit: "frontMatterCanInit",
initialized: "frontMatterInitialized",
canOpenPreview: "frontMatterCanOpenPreview",
canOpenDashboard: "frontMatterCanOpenDashboard",
isEnabled: "frontMatter:enabled",
+2
View File
@@ -7,7 +7,9 @@ export * from './FrameworkDetectors';
export * from './Links';
export * from './LocalStore';
export * from './Navigation';
export * from './SnippetVariables';
export * from './TelemetryEvent';
export * from './charCode';
export * from './charMap';
export * from './context';
export * from './settings';
+21 -20
View File
@@ -43,35 +43,36 @@ export const SETTING_PREVIEW_PATHNAME = "preview.pathName";
export const SETTING_CUSTOM_SCRIPTS = "custom.scripts";
export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate";
export const SETTINGS_CONTENT_PAGE_FOLDERS = "content.pageFolders";
export const SETTINGS_CONTENT_STATIC_FOLDER = "content.publicFolder";
export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField";
export const SETTINGS_CONTENT_SORTING = "content.sorting";
export const SETTINGS_CONTENT_WYSIWYG = "content.wysiwyg";
export const SETTINGS_CONTENT_PLACEHOLDERS = "content.placeholders";
export const SETTING_CONTENT_PAGE_FOLDERS = "content.pageFolders";
export const SETTING_CONTENT_STATIC_FOLDER = "content.publicFolder";
export const SETTING_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight";
export const SETTING_CONTENT_DRAFT_FIELD = "content.draftField";
export const SETTING_CONTENT_SORTING = "content.sorting";
export const SETTING_CONTENT_WYSIWYG = "content.wysiwyg";
export const SETTING_CONTENT_PLACEHOLDERS = "content.placeholders";
export const SETTING_CONTENT_SNIPPETS = "content.snippets";
export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
export const SETTING_CONTENT_SORTING_DEFAULT = "content.defaultSorting";
export const SETTING_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType";
export const SETTINGS_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes";
export const SETTING_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType";
export const SETTING_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
export const SETTINGS_DATA_FILES = "data.files";
export const SETTINGS_DATA_FOLDERS = "data.folders";
export const SETTINGS_DATA_TYPES = "data.types";
export const SETTING_DATA_FILES = "data.files";
export const SETTING_DATA_FOLDERS = "data.folders";
export const SETTING_DATA_TYPES = "data.types";
export const SETTINGS_FILE_PRESERVE_CASING = "file.preserveCasing";
export const SETTING_FILE_PRESERVE_CASING = "file.preserveCasing";
export const SETTINGS_FRAMEWORK_ID = "framework.id";
export const SETTINGS_FRAMEWORK_START = "framework.startCommand";
export const SETTING_FRAMEWORK_ID = "framework.id";
export const SETTING_FRAMEWORK_START = "framework.startCommand";
export const SETTING_SITE_BASEURL = "site.baseURL";
/**
* @deprecated
*/
export const SETTINGS_CONTENT_FOLDERS = "content.folders";
export const SETTING_CONTENT_FOLDERS = "content.folders";
+3
View File
@@ -25,4 +25,7 @@ export enum DashboardMessage {
getDataEntries = 'getDataEntries',
putDataEntries = 'putDataEntries',
sendTelemetry = 'sendTelemetry',
insertSnippet = 'insertSnippet',
addSnippet = 'addSnippet',
updateSnippet = 'updateSnippet',
}
@@ -2,7 +2,6 @@ import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { Page } from '../../models';
import { SettingsSelector } from '../../state';
import { Header } from '../Header';
import { Overview } from './Overview';
import { Spinner } from '../Spinner';
import { SponsorMsg } from '../SponsorMsg';
@@ -11,6 +10,7 @@ import { useEffect } from 'react';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../../DashboardMessage';
import { TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
export interface IContentsProps {
pages: Page[];
@@ -30,17 +30,14 @@ export const Contents: React.FunctionComponent<IContentsProps> = ({pages, loadin
}, []);
return (
<div className="flex flex-col h-full overflow-auto">
<Header
folders={pageFolders}
totalPages={pageItems.length}
settings={settings} />
<PageLayout
folders={pageFolders}
totalPages={pageItems.length}>
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
{ loading ? <Spinner /> : <Overview pages={pageItems} settings={settings} /> }
</div>
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} isBacker={settings?.isBacker} />
</div>
</PageLayout>
);
};
@@ -23,7 +23,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({ fmFilePath, date, ti
if (view === DashboardViewType.Grid) {
return (
<li className="relative">
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md dark:shadow-none hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-100 dark:border-vulcan-50`}
<button className={`group cursor-pointer flex flex-wrap items-start content-start h-full w-full bg-gray-50 dark:bg-vulcan-200 text-vulcan-500 dark:text-whisper-500 text-left overflow-hidden shadow-md dark:shadow-none hover:shadow-xl dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50`}
onClick={openFile}>
<div className="relative h-36 w-full overflow-hidden border-b border-gray-100 dark:border-vulcan-100 dark:group-hover:border-vulcan-200">
{
@@ -9,6 +9,7 @@ import { Contents } from './Contents/Contents';
import { Media } from './Media/Media';
import { NavigationType } from '../models';
import { DataView } from './DataView';
import { Snippets } from './SnippetsView/Snippets';
export interface IDashboardProps {
showWelcome: boolean;
@@ -31,6 +32,14 @@ export const Dashboard: React.FunctionComponent<IDashboardProps> = ({showWelcome
return <WelcomeScreen settings={settings} />;
}
if (view === NavigationType.Snippets) {
return (
<main className={`h-full w-full`}>
<Snippets />
</main>
);
}
if (view === NavigationType.Media) {
return (
<main className={`h-full w-full`}>
@@ -21,6 +21,7 @@ import { CustomScript } from '../../../models';
import { LightningBoltIcon, PlusIcon } from '@heroicons/react/outline';
export interface IHeaderProps {
header?: React.ReactNode;
settings: Settings | null;
// Navigation
@@ -30,7 +31,7 @@ export interface IHeaderProps {
folders?: string[];
}
export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
export const Header: React.FunctionComponent<IHeaderProps> = ({header, totalPages, folders, settings }: React.PropsWithChildren<IHeaderProps>) => {
const [ crntTag, setCrntTag ] = useRecoilState(TagAtom);
const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom);
const [ view, setView ] = useRecoilState(DashboardViewAtom);
@@ -148,6 +149,10 @@ export const Header: React.FunctionComponent<IHeaderProps> = ({totalPages, folde
</>
)
}
{
header
}
</div>
);
};
@@ -1,4 +1,4 @@
import { DatabaseIcon, PhotographIcon } from '@heroicons/react/outline';
import { DatabaseIcon, PhotographIcon, ScissorsIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon';
@@ -29,6 +29,13 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({ onNavigate }: React.
<PhotographIcon className={`h-6 w-auto mr-2`} /><span>Media</span>
</Tab>
</li>
<li className="mr-2" role="presentation">
<Tab
navigationType={NavigationType.Snippets}
onNavigate={onNavigate}>
<ScissorsIcon className={`h-6 w-auto mr-2`} /><span>Snippets</span>
</Tab>
</li>
{
(settings?.dataFiles && settings.dataFiles.length > 0) && (
<li className="mr-2" role="presentation">
@@ -0,0 +1,28 @@
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { SettingsSelector } from '../../state';
import { Header } from '../Header';
export interface IPageLayoutProps {
header?: React.ReactNode;
folders?: string[] | undefined
totalPages?: number | undefined
}
export const PageLayout: React.FunctionComponent<IPageLayoutProps> = ({ header, folders, totalPages, children }: React.PropsWithChildren<IPageLayoutProps>) => {
const settings = useRecoilValue(SettingsSelector);
return (
<div className="flex flex-col h-full overflow-auto">
<Header
header={header}
folders={folders}
totalPages={totalPages}
settings={settings} />
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4">
{ children }
</div>
</div>
);
};
@@ -205,7 +205,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({media}: React.PropsWi
return (
<>
<li className="group relative bg-gray-50 dark:bg-vulcan-200 shadow-md hover:shadow-xl dark:shadow-none dark:hover:bg-vulcan-100 border border-gray-100 dark:border-vulcan-50">
<li className="group relative bg-gray-50 dark:bg-vulcan-200 shadow-md hover:shadow-xl dark:shadow-none dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50">
<button className="relative bg-gray-200 dark:bg-vulcan-300 block w-full aspect-w-10 aspect-h-7 overflow-hidden cursor-pointer h-48" onClick={openLightbox}>
<div className={`absolute top-0 right-0 bottom-0 left-0 flex items-center justify-center`}>
<PhotographIcon className={`h-1/2 text-gray-300 dark:text-vulcan-200`} />
@@ -3,7 +3,6 @@ import {UploadIcon} from '@heroicons/react/outline';
import * as React from 'react';
import { useRecoilValue } from 'recoil';
import { LoadingAtom, MediaFoldersAtom, SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state';
import { Header } from '../Header';
import { Spinner } from '../Spinner';
import { SponsorMsg } from '../SponsorMsg';
import { Item } from './Item';
@@ -16,6 +15,7 @@ import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMat
import { FolderItem } from './FolderItem';
import useMedia from '../../hooks/useMedia';
import { TelemetryEvent } from '../../../constants';
import { PageLayout } from '../Layout/PageLayout';
export interface IMediaProps {}
@@ -56,16 +56,13 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
});
return (
<div className="flex flex-col h-full overflow-auto">
<Header settings={settings} />
<div className="w-full flex-grow max-w-7xl mx-auto py-6 px-4" {...getRootProps()}>
<PageLayout>
<div className="w-full h-full" {...getRootProps()}>
{
viewData?.data?.filePath && (
<div className={`text-lg text-center mb-6`}>
<p>Select the image you want to use for your article.</p>
<p className={`opacity-80 text-base`}>You can also drag and drop images from your desktop and select that once uploaded.</p>
<p>Select the media file to add to your content.</p>
<p className={`opacity-80 text-base`}>You can also drag and drop images from your desktop and select them once uploaded.</p>
</div>
)
}
@@ -123,6 +120,6 @@ export const Media: React.FunctionComponent<IMediaProps> = (props: React.PropsWi
<Lightbox />
<SponsorMsg beta={settings?.beta} version={settings?.versionInfo} isBacker={settings?.isBacker} />
</div>
</PageLayout>
);
};
@@ -2,7 +2,7 @@ import { Dialog, Transition } from '@headlessui/react';
import * as React from 'react';
import { Fragment, useRef } from 'react';
export interface IMetadataProps {
export interface IFormDialogProps {
title: string;
description: string;
okBtnText: string;
@@ -13,11 +13,10 @@ export interface IMetadataProps {
trigger: () => void;
}
export const Metadata: React.FunctionComponent<IMetadataProps> = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren<IMetadataProps>) => {
export const FormDialog: React.FunctionComponent<IFormDialogProps> = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren<IFormDialogProps>) => {
const cancelButtonRef = useRef(null);
return (
<Transition.Root show={true} as={Fragment}>
<Dialog className="fixed z-10 inset-0 overflow-y-auto" initialFocus={cancelButtonRef} onClose={() => dismiss()}>
@@ -67,7 +66,7 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({title, descri
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30"
className="w-full inline-flex justify-center border border-transparent shadow-sm px-4 py-2 bg-teal-600 text-base font-medium text-white hover:bg-teal-700 dark:hover:bg-teal-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-teal-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-30"
onClick={() => trigger()}
disabled={isSaveDisabled}
>
@@ -75,7 +74,7 @@ export const Metadata: React.FunctionComponent<IMetadataProps> = ({title, descri
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
className="mt-3 w-full inline-flex justify-center border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 dark:hover:bg-gray-200 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
onClick={() => dismiss()}
ref={cancelButtonRef}
>
@@ -0,0 +1,183 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { CodeIcon, DotsHorizontalIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { DashboardMessage } from '../../DashboardMessage';
import { SettingsSelector, ViewDataSelector } from '../../state';
import { Alert } from '../Modals/Alert';
import { FormDialog } from '../Modals/FormDialog';
import { NewForm } from './NewForm';
import SnippetForm, { SnippetFormHandle } from './SnippetForm';
export interface IItemProps {
title: string;
snippet: any;
}
export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: React.PropsWithChildren<IItemProps>) => {
const viewData = useRecoilValue(ViewDataSelector);
const settings = useRecoilValue(SettingsSelector);
const [ showInsertDialog, setShowInsertDialog ] = useState(false);
const [ showEditDialog, setShowEditDialog ] = useState(false);
const [ showAlert, setShowAlert ] = React.useState(false);
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
const [ snippetOriginalBody, setSnippetOriginalBody ] = useState<string>('');
const formRef = useRef<SnippetFormHandle>(null);
const insertToArticle = () => {
formRef.current?.onSave();
setShowInsertDialog(false);
};
const reset = () => {
setShowEditDialog(false);
setSnippetTitle('');
setSnippetDescription('');
setSnippetOriginalBody('');
};
const onOpenEdit = useCallback(() => {
setSnippetTitle(title);
setSnippetDescription(snippet.description);
setSnippetOriginalBody(typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`));
setShowEditDialog(true);
}, [snippet]);
const onSnippetUpdate = useCallback(() => {
if (!snippetTitle || !snippetOriginalBody) {
reset();
return;
}
const snippets = Object.assign({}, settings?.snippets || {});
const snippetLines = snippetOriginalBody.split("\n");
const snippetContents = {
description: snippetDescription || '',
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines
};
if (title === snippetTitle) {
snippets[title] = snippetContents;
} else {
delete snippets[title];
snippets[snippetTitle] = snippetContents;
}
Messenger.send(DashboardMessage.updateSnippet, { snippets });
reset();
}, [settings?.snippets, title, snippetTitle, snippetDescription, snippetOriginalBody]);
const onDelete = useCallback(() => {
const snippets = Object.assign({}, settings?.snippets || {});
delete snippets[title];
Messenger.send(DashboardMessage.updateSnippet, { snippets });
setShowAlert(false);
}, [settings?.snippets, title]);
return (
<>
<li className="group relative overflow-hidden bg-gray-50 dark:bg-vulcan-200 shadow-md hover:shadow-xl dark:shadow-none dark:hover:bg-vulcan-100 border border-gray-200 dark:border-vulcan-50 p-4 space-y-2">
<div className='absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'>
<CodeIcon className='w-64 h-64 opacity-5 text-vulcan-200 dark:text-gray-400' />
</div>
<h2 className="mt-2 mb-2 font-bold">{title}</h2>
<div className={`absolute top-4 right-4 flex flex-col space-y-4`}>
<div className="flex items-center border border-transparent group-hover:bg-gray-200 dark:group-hover:bg-vulcan-200 group-hover:border-gray-100 dark:group-hover:border-vulcan-50 rounded-full p-2 -mr-2 -mt-2">
<div className='group-hover:hidden'>
<DotsHorizontalIcon className="w-4 h-4" />
</div>
<div className='hidden group-hover:flex space-x-2'>
{
viewData?.data?.filePath && (
<>
<button onClick={() => setShowInsertDialog(true)}>
<PlusIcon className='w-4 h-4' />
<span className='sr-only'>Insert snippet</span>
</button>
</>
)
}
<button onClick={onOpenEdit}>
<PencilIcon className='w-4 h-4' />
<span className='sr-only'>Edit snippet</span>
</button>
<button onClick={() => setShowAlert(true)}>
<TrashIcon className='w-4 h-4' />
<span className='sr-only'>Delete snippet</span>
</button>
</div>
</div>
</div>
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{snippet.description}</p>
</li>
{
showInsertDialog && (
<FormDialog
title={`Insert snippet: ${title}`}
description={`Insert the ${title.toLowerCase()} snippet into the current article`}
isSaveDisabled={!viewData?.data?.filePath}
trigger={insertToArticle}
dismiss={() => setShowInsertDialog(false)}
okBtnText='Insert'
cancelBtnText='Cancel'>
<SnippetForm
ref={formRef}
snippet={snippet}
selection={viewData?.data?.selection} />
</FormDialog>
)
}
{
showEditDialog && (
<FormDialog
title={`Edit snippet: ${title}`}
description={`Edit the ${title.toLowerCase()} snippet`}
isSaveDisabled={!snippetTitle || !snippetOriginalBody}
trigger={onSnippetUpdate}
dismiss={reset}
okBtnText='Update'
cancelBtnText='Cancel'>
<NewForm
title={snippetTitle}
description={snippetDescription}
body={snippetOriginalBody}
onTitleUpdate={(value: string) => setSnippetTitle(value)}
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetOriginalBody(value)} />
</FormDialog>
)
}
{
showAlert && (
<Alert
title={`Delete snippet: ${title}`}
description={`Are you sure you want to delete the ${title.toLowerCase()} snippet?`}
okBtnText={`Delete`}
cancelBtnText={`Cancel`}
dismiss={() => setShowAlert(false)}
trigger={onDelete} />
)
}
</>
);
};
@@ -0,0 +1,109 @@
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { SnippetVariables } from '../../../constants';
export interface INewFormProps {
title: string;
description: string;
body: string;
onTitleUpdate: (value: string) => void;
onDescriptionUpdate: (value: string) => void;
onBodyUpdate: (value: string) => void;
}
export const NewForm: React.FunctionComponent<INewFormProps> = ({ title, description, body, onTitleUpdate, onDescriptionUpdate, onBodyUpdate }: React.PropsWithChildren<INewFormProps>) => {
const [ showDetails, setShowDetails ] = React.useState(false);
return (
<div className='space-y-4'>
<div>
<label htmlFor={`title`} className="block text-sm font-medium capitalize">
Title <span className='text-red-400' title='Required field'>*</span>
</label>
<div className="mt-1">
<input
type='text'
name={`title`}
value={title || ""}
placeholder={`Snippet title`}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onTitleUpdate(e.currentTarget.value)}
/>
</div>
</div>
<div>
<label htmlFor={`description`} className="block text-sm font-medium capitalize">
Description
</label>
<div className="mt-1">
<input
type='text'
name={`description`}
value={description || ""}
placeholder={`Snippet description`}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onDescriptionUpdate(e.currentTarget.value)}
/>
</div>
</div>
<div>
<label htmlFor={`snippet`} className="block text-sm font-medium capitalize">
Snippet <span className='text-red-400' title='Required field'>*</span>
</label>
<div className="mt-1">
<textarea
name={`snippet`}
value={body || ""}
placeholder={`Snippet content`}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onBodyUpdate(e.currentTarget.value)}
/>
</div>
</div>
<div>
<h3 className="text-base text-vulcan-300 dark:text-whisper-500 flex items-center">
<span>Placeholders guidelines</span>
{
showDetails ? (
<button onClick={() => setShowDetails(false)}>
<ChevronUpIcon className="w-4 h-4 text-gray-500" />
</button>
) : (
<button onClick={() => setShowDetails(true)}>
<ChevronDownIcon className="w-4 h-4 text-gray-500" />
</button>
)
}
</h3>
<dl className="divide-y divide-gray-200 dark:divide-vulcan-200" style={{ zIndex: -1 }}>
<div className="py-2 flex justify-between text-xs font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Insert selected text (can still be updated)</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{`\${${SnippetVariables.FM_SELECTED_TEXT}}`}</dd>
</div>
<div className="py-2 flex justify-between text-xs font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Variable without default</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{`\${variable}`}</dd>
</div>
<div className="py-2 flex justify-between text-xs font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Variable with default</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{`\${variable:default}`}</dd>
</div>
<div className="py-2 flex justify-between text-xs font-medium">
<dt className="text-vulcan-100 dark:text-whisper-900">Variable with choices</dt>
<dd className="text-vulcan-300 dark:text-whisper-500 text-right">{`\${variable|choice 1,choice 2,choice 3|}`}</dd>
</div>
</dl>
</div>
</div>
);
};
@@ -0,0 +1,171 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import * as React from 'react';
import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { SnippetVariables } from '../../../constants';
import { Choice, SnippetParser, Variable, VariableResolver } from '../../../helpers/SnippetParser';
import { SnippetField } from '../../../models';
import { DashboardMessage } from '../../DashboardMessage';
import { ViewDataSelector } from '../../state';
import { SnippetInputField } from './SnippetInputField';
export interface ISnippetFormProps {
snippet: any;
selection: string | undefined;
}
export interface SnippetFormHandle {
onSave: () => void;
}
const SnippetForm: React.ForwardRefRenderFunction<SnippetFormHandle, ISnippetFormProps> = ({ snippet, selection }, ref) => {
const viewData = useRecoilValue(ViewDataSelector);
const [ fields, setFields ] = useState<SnippetField[]>([]);
const onTextChange = useCallback((field: SnippetField, value: string) => {
setFields(prevFields => prevFields.map(f => f.name === field.name ? { ...f, value } : f));
}, [setFields]);
const insertSelectionValue = useCallback((value: string) => {
if (selection && value === SnippetVariables.FM_SELECTED_TEXT) {
return selection;
}
return;
}, [selection]);
const snippetBody = useMemo(() => {
let body = typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`);
for (const field of fields) {
body = body.replace(field.tmString, field.value);
}
return body;
}, [fields, selection]);
const shouldShowField = (fieldName: string, idx: number, allFields: SnippetField[]) => {
const crntField = allFields.findIndex(f => f.name === fieldName);
if (crntField < idx) {
return false;
}
return true;
}
const fieldNameRender = (fieldName: string) => {
if (fieldName === SnippetVariables.FM_SELECTED_TEXT) {
return 'Selected Text';
}
if (fieldName.startsWith(SnippetVariables.FM_TEXT)) {
return fieldName.replace(SnippetVariables.FM_TEXT, '');
}
if (fieldName.startsWith(SnippetVariables.FM_MULTILINE)) {
return fieldName.replace(SnippetVariables.FM_MULTILINE, '');
}
return fieldName;
}
useImperativeHandle(ref, () => ({
onSave() {
if (!snippetBody) {
return;
}
Messenger.send(DashboardMessage.insertSnippet, {
file: viewData?.data?.filePath,
snippet: snippetBody
});
}
}));
useEffect(() => {
// Defines the type of field that needs to be rendered
const getFieldType = (fieldName: string) => {
if (fieldName.startsWith(SnippetVariables.FM_MULTILINE)) {
return 'textarea';
}
return 'text';
}
// Get all placeholder variables from the snippet
const snippetParser = new SnippetParser();
const body = typeof snippet.body === "string" ? snippet.body : snippet.body.join(`\n`);
const parsed = snippetParser.parse(body);
const placeholders = parsed.placeholderInfo.all;
const allFields: any[] = [];
for (const placeholder of placeholders) {
const tmString = placeholder.toTextmateString();
// If only a variable is defined, it will not contain children
if (placeholder.children.length === 0) {
allFields.push({
type: getFieldType(placeholder.index as string),
name: placeholder.index,
value: insertSelectionValue(placeholder.index as string) || '',
tmString
});
} else {
// Children are defined, so it means it is a choice field or the variable has a default value
for (const child of placeholder.children as any[]) {
if (child instanceof Choice) {
const options = child.options.map(o => o.value);
allFields.push({
type: 'select',
name: placeholder.index,
value: (child as any).value || options[0] || "",
options,
tmString
});
} else {
allFields.push({
type: getFieldType(placeholder.index as string),
name: placeholder.index,
value: insertSelectionValue((child as any).value as string) || insertSelectionValue(placeholder.index as string) || (child as any).value || "",
tmString
});
}
}
}
}
setFields(allFields);
}, []);
return (
<div>
<pre className='border border-opacity-40 p-2 whitespace-pre-wrap break-words'>
{snippetBody}
</pre>
<div className='space-y-4 mt-4'>
{
fields.map((field: SnippetField, index: number, allFields: SnippetField[]) => (
shouldShowField(field.name, index, allFields) && (
<div key={index}>
<label htmlFor={field.name} className="block text-sm font-medium capitalize">
{fieldNameRender(field.name)}
</label>
<div className="mt-1">
<SnippetInputField
field={field}
onValueChange={onTextChange} />
</div>
</div>
)
))
}
</div>
</div>
);
};
export default React.forwardRef(SnippetForm);
@@ -0,0 +1,53 @@
import * as React from 'react';
import { ChevronDownIcon } from '@heroicons/react/outline';
import { SnippetField } from '../../../models';
import { SnippetVariables } from '../../../constants';
export interface ISnippetInputFieldProps {
field: SnippetField;
onValueChange: (field: SnippetField, value: string) => void
}
export const SnippetInputField: React.FunctionComponent<ISnippetInputFieldProps> = ({ field, onValueChange }: React.PropsWithChildren<ISnippetInputFieldProps>) => {
if (field.type === 'select') {
return (
<div className='relative'>
<select
name={field.name}
value={field.value || ""}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={e => onValueChange(field, e.target.value)}>
{
field.options?.map((option: string, index: number) => (
<option key={index} value={option}>{option}</option>
))
}
</select>
<ChevronDownIcon className="absolute top-3 right-2 w-4 h-4 text-gray-500" />
</div>
)
}
if (field.type === 'textarea') {
return (
<textarea
name={field.name}
value={field.value || ""}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onValueChange(field, e.currentTarget.value)}
/>
)
}
return (
<input
type="text"
name={field.name}
value={field.value || ""}
className="focus:outline-none block w-full sm:text-sm border-gray-300 text-vulcan-500"
onChange={(e) => onValueChange(field, e.currentTarget.value)}
/>
);
};
@@ -0,0 +1,121 @@
import { Messenger } from '@estruyf/vscode/dist/client';
import { CodeIcon, PlusSmIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { DashboardMessage } from '../../DashboardMessage';
import { SettingsSelector, ViewDataSelector } from '../../state';
import { PageLayout } from '../Layout/PageLayout';
import { FormDialog } from '../Modals/FormDialog';
import { Item } from './Item';
import { NewForm } from './NewForm';
export interface ISnippetsProps {}
export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.PropsWithChildren<ISnippetsProps>) => {
const settings = useRecoilValue(SettingsSelector);
const viewData = useRecoilValue(ViewDataSelector);
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
const [ snippetBody, setSnippetBody ] = useState<string>('');
const [ showCreateDialog, setShowCreateDialog ] = useState(false);
const snippetKeys = useMemo(() => Object.keys(settings?.snippets) || [], [settings?.snippets]);
const snippets = settings?.snippets || {};
const onSnippetAdd = useCallback(() => {
if (!snippetTitle || !snippetBody) {
reset();
return;
}
Messenger.send(DashboardMessage.addSnippet, {
title: snippetTitle,
description: snippetDescription || '',
body: snippetBody
});
reset();
}, [snippetTitle, snippetDescription, snippetBody]);
const reset = () => {
setShowCreateDialog(false);
setSnippetTitle('');
setSnippetDescription('');
setSnippetBody('');
};
return (
<PageLayout
header={(
<div
className="py-3 px-4 flex items-center justify-between border-b border-gray-300 dark:border-vulcan-100"
aria-label="Pagination"
>
<div className="flex flex-1 justify-end">
<button
className={`inline-flex items-center px-3 py-1 border border-transparent text-xs leading-4 font-medium text-white dark:text-vulcan-500 bg-teal-600 hover:bg-teal-700 focus:outline-none disabled:bg-gray-500`}
title={`Create new snippet`}
onClick={() => setShowCreateDialog(true)}>
<PlusSmIcon className={`mr-2 h-6 w-6`} />
<span className={`text-sm`}>Create new snippet</span>
</button>
</div>
</div>
)}>
{
viewData?.data?.filePath && (
<div className={`text-xl text-center mb-6`}>
<p>Select the snippet to add to your content.</p>
</div>
)
}
{
snippetKeys && snippetKeys.length > 0 ? (
<ul role="list" className={`grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8`}>
{
snippetKeys.map((snippetKey: any, index: number) => (
<Item
key={index}
title={snippetKey}
snippet={snippets[snippetKey]} />
))
}
</ul>
) : (
<div className='w-full h-full flex items-center justify-center'>
<div className='flex flex-col items-center'>
<CodeIcon className='w-32 h-32' />
<p className='text-3xl'>No snippets found</p>
</div>
</div>
)
}
{
showCreateDialog && (
<FormDialog
title={`Create a snippet`}
description={``}
isSaveDisabled={!snippetTitle || !snippetBody}
trigger={onSnippetAdd}
dismiss={reset}
okBtnText='Save'
cancelBtnText='Cancel'>
<NewForm
title={snippetTitle}
description={snippetDescription}
body={snippetBody}
onTitleUpdate={(value: string) => setSnippetTitle(value)}
onDescriptionUpdate={(value: string) => setSnippetDescription(value)}
onBodyUpdate={(value: string) => setSnippetBody(value)} />
</FormDialog>
)
}
</PageLayout>
);
};
+2 -2
View File
@@ -1,5 +1,5 @@
import * as React from 'react';
import { SETTINGS_DASHBOARD_OPENONSTART } from '../../constants';
import { SETTING_DASHBOARD_OPENONSTART } from '../../constants';
import { Messenger } from '@estruyf/vscode/dist/client';
import { DashboardMessage } from '../DashboardMessage';
import { Settings } from '../models/Settings';
@@ -13,7 +13,7 @@ export const Startup: React.FunctionComponent<IStartupProps> = ({settings}: Reac
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setIsChecked(e.target.checked);
Messenger.send(DashboardMessage.updateSetting, { name: SETTINGS_DASHBOARD_OPENONSTART, value: e.target.checked });
Messenger.send(DashboardMessage.updateSetting, { name: SETTING_DASHBOARD_OPENONSTART, value: e.target.checked });
};
React.useEffect(() => {
@@ -28,6 +28,8 @@ export default function useMessages() {
setView(NavigationType.Contents);
} else if (message.data.data?.type === NavigationType.Data) {
setView(NavigationType.Data);
} else if (message.data.data?.type === NavigationType.Snippets) {
setView(NavigationType.Snippets);
}
break;
case DashboardCommand.settings:
@@ -2,4 +2,5 @@ export enum NavigationType {
Contents = "contents",
Media = "media",
Data = "data",
Snippets = "snippets",
}
+1
View File
@@ -29,6 +29,7 @@ export interface Settings {
dataFiles: DataFile[] | undefined;
dataTypes: DataType[] | undefined;
isBacker: boolean | undefined;
snippets: any | undefined;
}
export interface DashboardState {
+12 -3
View File
@@ -22,6 +22,7 @@ import { Diagnostics } from './commands/Diagnostics';
import { PagesListener } from './listeners/dashboard';
import { Backers } from './commands/Backers';
import { DataListener, SettingsListener } from './listeners/panel';
import { NavigationType } from './dashboardWebView/models';
let frontMatterStatusBar: vscode.StatusBarItem;
let statusDebouncer: { (fnc: any, time: number): void; };
@@ -57,7 +58,7 @@ export async function activate(context: vscode.ExtensionContext) {
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboard, (data?: DashboardData) => {
Telemetry.send(TelemetryEvent.openContentDashboard);
if (!data) {
Dashboard.open({ type: "contents" });
Dashboard.open({ type: NavigationType.Contents });
} else {
Dashboard.open(data);
}
@@ -65,12 +66,17 @@ export async function activate(context: vscode.ExtensionContext) {
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardMedia, (data?: DashboardData) => {
Telemetry.send(TelemetryEvent.openMediaDashboard);
Dashboard.open({ type: "media" });
Dashboard.open({ type: NavigationType.Media });
}));
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardSnippets, (data?: DashboardData) => {
Telemetry.send(TelemetryEvent.openSnippetsDashboard);
Dashboard.open({ type: NavigationType.Snippets });
}));
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardData, (data?: DashboardData) => {
Telemetry.send(TelemetryEvent.openDataDashboard);
Dashboard.open({ type: "data" });
Dashboard.open({ type: NavigationType.Data });
}));
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.dashboardClose, (data?: DashboardData) => {
@@ -206,6 +212,9 @@ export async function activate(context: vscode.ExtensionContext) {
// Inserting an image in Markdown
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.insertImage, Article.insertImage));
// Inserting a snippet in Markdown
subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.insertSnippet, Article.insertSnippet));
// Create the editor experience for bulk scripts
subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(ContentProvider.scheme, new ContentProvider()));
+5 -5
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, SETTINGS_FILE_PRESERVE_CASING, 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 { DefaultFields, SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_CONTENT_PLACEHOLDERS, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_FILE_PRESERVE_CASING, 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 '.';
@@ -143,7 +143,7 @@ export class ArticleHelper {
*/
public static isMarkdownFile(document: vscode.TextDocument | undefined | null = null) {
const supportedLanguages = ["markdown", "mdx"];
const fileTypes = Settings.get<string[]>(SETTINGS_CONTENT_SUPPORTED_FILETYPES);
const fileTypes = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES);
const supportedFileExtensions = fileTypes ? fileTypes.map(f => f.startsWith(`.`) ? f : `.${f}`) : DEFAULT_FILE_TYPES;
const languageId = document?.languageId?.toLowerCase();
const isSupportedLanguage = languageId && supportedLanguages.includes(languageId);
@@ -227,7 +227,7 @@ export class ArticleHelper {
* @returns
*/
public static sanitize(value: string): string {
const preserveCasing = Settings.get(SETTINGS_FILE_PRESERVE_CASING) as boolean;
const preserveCasing = Settings.get(SETTING_FILE_PRESERVE_CASING) as boolean;
return sanitize((preserveCasing ? value : value.toLowerCase()).replace(/ /g, "-"));
}
@@ -240,7 +240,7 @@ export class ArticleHelper {
*/
public static createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string, fileExtension?: string): string | undefined {
const prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
const fileType = Settings.get<string>(SETTING_CONTENT_DEFAULT_FILETYPE);
// Name of the file or folder to create
const sanitizedName = ArticleHelper.sanitize(titleValue);
@@ -336,7 +336,7 @@ export class ArticleHelper {
*/
public static processCustomPlaceholders(value: string, title: string) {
if (value && typeof value === "string") {
const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS);
const placeholders = Settings.get<{id: string, value: string}[]>(SETTING_CONTENT_PLACEHOLDERS);
if (placeholders && placeholders.length > 0) {
for (const placeholder of placeholders) {
if (value.includes(`{{${placeholder.id}}}`)) {
+2 -2
View File
@@ -1,6 +1,6 @@
import { PagesListener } from './../listeners/dashboard';
import { ArticleHelper, Settings } from ".";
import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
import { SETTING_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants";
import { ContentType as IContentType, DraftField, Field } from '../models';
import { Uri, commands } from 'vscode';
import { Folders } from "../commands/Folders";
@@ -18,7 +18,7 @@ export class ContentType {
* @returns
*/
public static getDraftField() {
const draftField = Settings.get<DraftField | null | undefined>(SETTINGS_CONTENT_DRAFT_FIELD);
const draftField = Settings.get<DraftField | null | undefined>(SETTING_CONTENT_DRAFT_FIELD);
if (draftField) {
return draftField;
}
+13 -12
View File
@@ -2,7 +2,7 @@ import { basename, join } from "path";
import { workspace } from "vscode";
import { Folders } from "../commands/Folders";
import { Template } from "../commands/Template";
import { CONTEXT, ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_DATA_FOLDERS, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants";
import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS } from "../constants";
import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models";
import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models";
import { DataFile } from "../models/DataFile";
@@ -23,35 +23,36 @@ export class DashboardSettings {
return {
beta: ext.isBetaVersion(),
wsFolder: wsFolder ? wsFolder.fsPath : '',
staticFolder: Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER),
staticFolder: Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER),
folders: Folders.get(),
initialized: isInitialized,
tags: Settings.getTaxonomy(TaxonomyType.Tag),
categories: Settings.getTaxonomy(TaxonomyType.Category),
openOnStart: Settings.get(SETTINGS_DASHBOARD_OPENONSTART),
openOnStart: Settings.get(SETTING_DASHBOARD_OPENONSTART),
versionInfo: ext.getVersion(),
pageViewType: await ext.getState<DashboardViewType | undefined>(ExtensionState.PagesView, "workspace"),
mediaSnippet: Settings.get<string[]>(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [],
mediaSnippet: Settings.get<string[]>(SETTING_DASHBOARD_MEDIA_SNIPPET) || [],
contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [],
draftField: Settings.get<DraftField>(SETTINGS_CONTENT_DRAFT_FIELD),
customSorting: Settings.get<SortingSetting[]>(SETTINGS_CONTENT_SORTING),
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
customSorting: Settings.get<SortingSetting[]>(SETTING_CONTENT_SORTING),
contentFolders: Folders.get(),
crntFramework: Settings.get<string>(SETTINGS_FRAMEWORK_ID),
crntFramework: Settings.get<string>(SETTING_FRAMEWORK_ID),
framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null,
scripts: (Settings.get<CustomScript[]>(SETTING_CUSTOM_SCRIPTS) || []),
dashboardState: {
contents: {
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Contents.Sorting, "workspace"),
defaultSorting: Settings.get<string>(SETTINGS_CONTENT_SORTING_DEFAULT)
defaultSorting: Settings.get<string>(SETTING_CONTENT_SORTING_DEFAULT)
},
media: {
sorting: await ext.getState<SortingOption | undefined>(ExtensionState.Dashboard.Media.Sorting, "workspace"),
defaultSorting: Settings.get<string>(SETTINGS_MEDIA_SORTING_DEFAULT),
defaultSorting: Settings.get<string>(SETTING_MEDIA_SORTING_DEFAULT),
selectedFolder: await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace")
}
},
dataFiles: await this.getDataFiles(),
dataTypes: Settings.get<DataType[]>(SETTINGS_DATA_TYPES),
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
snippets: Settings.get<DataType[]>(SETTING_CONTENT_SNIPPETS),
isBacker: await ext.getState<boolean | undefined>(CONTEXT.backer, 'global')
} as ISettings
}
@@ -62,8 +63,8 @@ export class DashboardSettings {
*/
private static async getDataFiles(): Promise<DataFile[]> {
const wsPath = Folders.getWorkspaceFolder()?.fsPath;
const files = Settings.get<DataFile[]>(SETTINGS_DATA_FILES);
const folders = Settings.get<DataFolder[]>(SETTINGS_DATA_FOLDERS);
const files = Settings.get<DataFile[]>(SETTING_DATA_FILES);
const folders = Settings.get<DataFolder[]>(SETTING_DATA_FOLDERS);
let clonedFiles = Object.assign([], files);
if (folders) {
+3 -3
View File
@@ -2,7 +2,7 @@ import { existsSync, renameSync } from "fs";
import { basename, join } from "path";
import { extensions, Uri, ExtensionContext, window, workspace, commands, ExtensionMode, DiagnosticCollection, languages } from "vscode";
import { Folders, WORKSPACE_PLACEHOLDER } from "../commands/Folders";
import { EXTENSION_NAME, GITHUB_LINK, SETTINGS_CONTENT_FOLDERS, SETTINGS_CONTENT_PAGE_FOLDERS, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, DEFAULT_CONTENT_TYPE_NAME, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, DefaultFields, LocalStore, SETTING_TEMPLATES_FOLDER } from "../constants";
import { EXTENSION_NAME, GITHUB_LINK, SETTING_CONTENT_FOLDERS, SETTING_CONTENT_PAGE_FOLDERS, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, DEFAULT_CONTENT_TYPE_NAME, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, DefaultFields, LocalStore, SETTING_TEMPLATES_FOLDER } from "../constants";
import { ContentType } from "../models";
import { Notifications } from "./Notifications";
import { parseWinPath } from "./parseWinPath";
@@ -137,7 +137,7 @@ export class Extension {
// Migration to version 3.1.0
if (major < 3 || (major === 3 && minor < 1)) {
const folders = Settings.get<any>(SETTINGS_CONTENT_FOLDERS);
const folders = Settings.get<any>(SETTING_CONTENT_FOLDERS);
if (folders && folders.length > 0) {
const workspace = Folders.getWorkspaceFolder();
const projectFolder = basename(workspace?.fsPath || "");
@@ -147,7 +147,7 @@ export class Extension {
path: `${WORKSPACE_PLACEHOLDER}${folder.fsPath.split(projectFolder).slice(1).join('')}`.split('\\').join('/')
}));
await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, paths);
await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, paths);
}
}
+3 -3
View File
@@ -5,7 +5,7 @@ import { Field } from '../models';
import { existsSync } from 'fs';
import { Folders } from '../commands/Folders';
import { Settings } from './SettingsHelper';
import { SETTINGS_CONTENT_STATIC_FOLDER } from '../constants';
import { SETTING_CONTENT_STATIC_FOLDER } from '../constants';
import { parseWinPath } from './parseWinPath';
export class ImageHelper {
@@ -51,7 +51,7 @@ export class ImageHelper {
*/
public static relToAbs(filePath: string, value: string) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || "", value);
const contentFolderPath = filePath ? join(dirname(filePath), value) : null;
@@ -73,7 +73,7 @@ export class ImageHelper {
*/
public static absToRel(imgValue: string) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
let relPath = imgValue || "";
if (imgValue) {
+3 -3
View File
@@ -1,7 +1,7 @@
import { decodeBase64Image, Extension, MediaLibrary, Notifications, parseWinPath, Settings, Sorting } from ".";
import { Dashboard } from "../commands/Dashboard";
import { Folders } from "../commands/Folders";
import { ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTINGS_CONTENT_STATIC_FOLDER } from "../constants";
import { ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_CONTENT_STATIC_FOLDER } from "../constants";
import { SortingOption } from "../dashboardWebView/models";
import { MediaInfo, MediaPaths, SortOrder, SortType } from "../models";
import { basename, extname, join, parse, dirname } from "path";
@@ -26,7 +26,7 @@ export class MediaHelpers {
*/
public static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
const contentFolders = Folders.get();
const viewData = Dashboard.viewData;
let selectedFolder = requestedFolder;
@@ -199,7 +199,7 @@ export class MediaHelpers {
public static async saveFile({fileName, contents, folder}: { fileName: string; contents: string; folder: string | null }) {
if (fileName && contents) {
const wsFolder = Folders.getWorkspaceFolder();
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
const wsPath = wsFolder ? wsFolder.fsPath : "";
let absFolderPath = join(wsPath, staticFolder || "");
+6 -6
View File
@@ -3,7 +3,7 @@ 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_FIELD_GROUPS, SETTING_TAXONOMY_TAGS } from "../constants"
import { CONTEXT, DefaultFields, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_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_FIELD_GROUPS, SETTING_TAXONOMY_TAGS } from "../constants"
import { CustomScript, DataType, DraftField, FieldGroup, PanelSettings as IPanelSettings, ScriptType } from "../models"
export class PanelSettings {
@@ -33,18 +33,18 @@ export class PanelSettings {
isInitialized: await Template.isInitialized(),
modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false,
writingSettingsEnabled: this.isWritingSettingsEnabled() || false,
fmHighlighting: Settings.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT),
fmHighlighting: Settings.get(SETTING_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),
draftField: Settings.get<DraftField>(SETTING_CONTENT_DRAFT_FIELD),
isBacker: await Extension.getInstance().getState<boolean | undefined>(CONTEXT.backer, 'global'),
framework: Settings.get<string>(SETTINGS_FRAMEWORK_ID),
framework: Settings.get<string>(SETTING_FRAMEWORK_ID),
commands: {
start: Settings.get<string>(SETTINGS_FRAMEWORK_START)
start: Settings.get<string>(SETTING_FRAMEWORK_START)
},
dataTypes: Settings.get<DataType[]>(SETTINGS_DATA_TYPES),
dataTypes: Settings.get<DataType[]>(SETTING_DATA_TYPES),
fieldGroups: Settings.get<FieldGroup[]>(SETTING_TAXONOMY_FIELD_GROUPS),
}
}
+977
View File
@@ -0,0 +1,977 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// SOURCE: https://github.com/microsoft/vscode/tree/main/src/vs/editor/contrib/snippet/browser
import { CharCode } from '../constants/charCode';
export const enum TokenType {
Dollar,
Colon,
Comma,
CurlyOpen,
CurlyClose,
Backslash,
Forwardslash,
Pipe,
Int,
VariableName,
Format,
Plus,
Dash,
QuestionMark,
EOF
}
export interface Token {
type: TokenType;
pos: number;
len: number;
}
export class Scanner {
private static _table: { [ch: number]: TokenType } = {
[CharCode.DollarSign]: TokenType.Dollar,
[CharCode.Colon]: TokenType.Colon,
[CharCode.Comma]: TokenType.Comma,
[CharCode.OpenCurlyBrace]: TokenType.CurlyOpen,
[CharCode.CloseCurlyBrace]: TokenType.CurlyClose,
[CharCode.Backslash]: TokenType.Backslash,
[CharCode.Slash]: TokenType.Forwardslash,
[CharCode.Pipe]: TokenType.Pipe,
[CharCode.Plus]: TokenType.Plus,
[CharCode.Dash]: TokenType.Dash,
[CharCode.QuestionMark]: TokenType.QuestionMark,
};
static isDigitCharacter(ch: number): boolean {
return ch >= CharCode.Digit0 && ch <= CharCode.Digit9;
}
static isVariableCharacter(ch: number): boolean {
return ch === CharCode.Underline
|| (ch >= CharCode.a && ch <= CharCode.z)
|| (ch >= CharCode.A && ch <= CharCode.Z);
}
value: string = '';
pos: number = 0;
text(value: string) {
this.value = value;
this.pos = 0;
}
tokenText(token: Token): string {
return this.value.substr(token.pos, token.len);
}
next(): Token {
if (this.pos >= this.value.length) {
return { type: TokenType.EOF, pos: this.pos, len: 0 };
}
let pos = this.pos;
let len = 0;
let ch = this.value.charCodeAt(pos);
let type: TokenType;
// static types
type = Scanner._table[ch];
if (typeof type === 'number') {
this.pos += 1;
return { type, pos, len: 1 };
}
// number
if (Scanner.isDigitCharacter(ch)) {
type = TokenType.Int;
do {
len += 1;
ch = this.value.charCodeAt(pos + len);
} while (Scanner.isDigitCharacter(ch));
this.pos += len;
return { type, pos, len };
}
// variable name
if (Scanner.isVariableCharacter(ch)) {
type = TokenType.VariableName;
do {
ch = this.value.charCodeAt(pos + (++len));
} while (Scanner.isVariableCharacter(ch) || Scanner.isDigitCharacter(ch));
this.pos += len;
return { type, pos, len };
}
// format
type = TokenType.Format;
do {
len += 1;
ch = this.value.charCodeAt(pos + len);
} while (
!isNaN(ch)
&& typeof Scanner._table[ch] === 'undefined' // not static token
&& !Scanner.isDigitCharacter(ch) // not number
&& !Scanner.isVariableCharacter(ch) // not variable
);
this.pos += len;
return { type, pos, len };
}
}
export abstract class Marker {
readonly _markerBrand: any;
public parent!: Marker;
protected _children: Marker[] = [];
appendChild(child: Marker): this {
if (child instanceof Text && this._children[this._children.length - 1] instanceof Text) {
// this and previous child are text -> merge them
(<Text>this._children[this._children.length - 1]).value += child.value;
} else {
// normal adoption of child
child.parent = this;
this._children.push(child);
}
return this;
}
replace(child: Marker, others: Marker[]): void {
const { parent } = child;
const idx = parent.children.indexOf(child);
const newChildren = parent.children.slice(0);
newChildren.splice(idx, 1, ...others);
parent._children = newChildren;
(function _fixParent(children: Marker[], parent: Marker) {
for (const child of children) {
child.parent = parent;
_fixParent(child.children, child);
}
})(others, parent);
}
get children(): Marker[] {
return this._children;
}
get snippet(): TextmateSnippet | undefined {
let candidate: Marker = this;
while (true) {
if (!candidate) {
return undefined;
}
if (candidate instanceof TextmateSnippet) {
return candidate;
}
candidate = candidate.parent;
}
}
toString(): string {
return this.children.reduce((prev, cur) => prev + cur.toString(), '');
}
abstract toTextmateString(): string;
len(): number {
return 0;
}
abstract clone(): Marker;
}
export class Text extends Marker {
static escape(value: string): string {
return value.replace(/\$|}|\\/g, '\\$&');
}
constructor(public value: string) {
super();
}
override toString() {
return this.value;
}
toTextmateString(): string {
return Text.escape(this.value);
}
override len(): number {
return this.value.length;
}
clone(): Text {
return new Text(this.value);
}
}
export abstract class TransformableMarker extends Marker {
public transform?: Transform;
}
export class Placeholder extends TransformableMarker {
constructor(public index: number | string) {
super();
}
get isFinalTabstop() {
return this.index === 0;
}
get choice(): Choice | undefined {
return this._children.length === 1 && this._children[0] instanceof Choice
? this._children[0] as Choice
: undefined;
}
toTextmateString(): string {
let transformString = '';
if (this.transform) {
transformString = this.transform.toTextmateString();
}
if (this.children.length === 0 && !this.transform) {
return `\${${this.index}}`;
} else if (this.children.length === 0) {
return `\${${this.index}${transformString}}`;
} else if (this.choice) {
return `\${${this.index}|${this.choice.toTextmateString()}|${transformString}}`;
} else {
return `\${${this.index}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`;
}
}
clone(): Placeholder {
let ret = new Placeholder(this.index);
if (this.transform) {
ret.transform = this.transform.clone();
}
ret._children = this.children.map(child => child.clone());
return ret;
}
}
export class Choice extends Marker {
readonly options: Text[] = [];
override appendChild(marker: Marker): this {
if (marker instanceof Text) {
marker.parent = this;
this.options.push(marker);
}
return this;
}
override toString() {
return this.options[0].value;
}
toTextmateString(): string {
return this.options
.map(option => option.value.replace(/\||,/g, '\\$&'))
.join(',');
}
override len(): number {
return this.options[0].len();
}
clone(): Choice {
let ret = new Choice();
this.options.forEach(ret.appendChild, ret);
return ret;
}
}
export class Transform extends Marker {
regexp: RegExp = new RegExp('');
resolve(value: string): string {
const _this = this;
let didMatch = false;
let ret = value.replace(this.regexp, function () {
didMatch = true;
return _this._replace(Array.prototype.slice.call(arguments, 0, -2));
});
// when the regex didn't match and when the transform has
// else branches, then run those
if (!didMatch && this._children.some(child => child instanceof FormatString && Boolean(child.elseValue))) {
ret = this._replace([]);
}
return ret;
}
private _replace(groups: string[]): string {
let ret = '';
for (const marker of this._children) {
if (marker instanceof FormatString) {
let value = groups[marker.index] || '';
value = marker.resolve(value);
ret += value;
} else {
ret += marker.toString();
}
}
return ret;
}
override toString(): string {
return '';
}
toTextmateString(): string {
return `/${this.regexp.source}/${this.children.map(c => c.toTextmateString())}/${(this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : '')}`;
}
clone(): Transform {
let ret = new Transform();
ret.regexp = new RegExp(this.regexp.source, '' + (this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : ''));
ret._children = this.children.map(child => child.clone());
return ret;
}
}
export class FormatString extends Marker {
constructor(
readonly index: number,
readonly shorthandName?: string,
readonly ifValue?: string,
readonly elseValue?: string,
) {
super();
}
resolve(value?: string): string {
if (this.shorthandName === 'upcase') {
return !value ? '' : value.toLocaleUpperCase();
} else if (this.shorthandName === 'downcase') {
return !value ? '' : value.toLocaleLowerCase();
} else if (this.shorthandName === 'capitalize') {
return !value ? '' : (value[0].toLocaleUpperCase() + value.substr(1));
} else if (this.shorthandName === 'pascalcase') {
return !value ? '' : this._toPascalCase(value);
} else if (this.shorthandName === 'camelcase') {
return !value ? '' : this._toCamelCase(value);
} else if (Boolean(value) && typeof this.ifValue === 'string') {
return this.ifValue;
} else if (!Boolean(value) && typeof this.elseValue === 'string') {
return this.elseValue;
} else {
return value || '';
}
}
private _toPascalCase(value: string): string {
const match = value.match(/[a-z0-9]+/gi);
if (!match) {
return value;
}
return match.map(word => {
return word.charAt(0).toUpperCase()
+ word.substr(1).toLowerCase();
})
.join('');
}
private _toCamelCase(value: string): string {
const match = value.match(/[a-z0-9]+/gi);
if (!match) {
return value;
}
return match.map((word, index) => {
if (index === 0) {
return word.toLowerCase();
} else {
return word.charAt(0).toUpperCase()
+ word.substr(1).toLowerCase();
}
})
.join('');
}
toTextmateString(): string {
let value = '${';
value += this.index;
if (this.shorthandName) {
value += `:/${this.shorthandName}`;
} else if (this.ifValue && this.elseValue) {
value += `:?${this.ifValue}:${this.elseValue}`;
} else if (this.ifValue) {
value += `:+${this.ifValue}`;
} else if (this.elseValue) {
value += `:-${this.elseValue}`;
}
value += '}';
return value;
}
clone(): FormatString {
let ret = new FormatString(this.index, this.shorthandName, this.ifValue, this.elseValue);
return ret;
}
}
export class Variable extends TransformableMarker {
constructor(public name: string) {
super();
}
resolve(resolver: VariableResolver): boolean {
let value = resolver.resolve(this);
if (this.transform) {
value = this.transform.resolve(value || '');
}
if (value !== undefined) {
this._children = [new Text(value)];
return true;
}
return false;
}
toTextmateString(): string {
let transformString = '';
if (this.transform) {
transformString = this.transform.toTextmateString();
}
if (this.children.length === 0) {
return `\${${this.name}${transformString}}`;
} else {
return `\${${this.name}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`;
}
}
clone(): Variable {
const ret = new Variable(this.name);
if (this.transform) {
ret.transform = this.transform.clone();
}
ret._children = this.children.map(child => child.clone());
return ret;
}
}
export interface VariableResolver {
resolve(variable: Variable): string | undefined;
}
function walk(marker: Marker[], visitor: (marker: Marker) => boolean): void {
const stack = [...marker];
while (stack.length > 0) {
const marker = stack.shift()!;
const recurse = visitor(marker);
if (!recurse) {
break;
}
stack.unshift(...marker.children);
}
}
export class TextmateSnippet extends Marker {
private _placeholders?: { all: Placeholder[]; last?: Placeholder };
get placeholderInfo() {
if (!this._placeholders) {
// fill in placeholders
let all: Placeholder[] = [];
let last: Placeholder | undefined;
this.walk(function (candidate) {
if (candidate instanceof Placeholder) {
all.push(candidate);
last = !last || last.index < candidate.index ? candidate : last;
}
return true;
});
this._placeholders = { all, last };
}
return this._placeholders;
}
get placeholders(): Placeholder[] {
const { all } = this.placeholderInfo;
return all;
}
offset(marker: Marker): number {
let pos = 0;
let found = false;
this.walk(candidate => {
if (candidate === marker) {
found = true;
return false;
}
pos += candidate.len();
return true;
});
if (!found) {
return -1;
}
return pos;
}
fullLen(marker: Marker): number {
let ret = 0;
walk([marker], marker => {
ret += marker.len();
return true;
});
return ret;
}
enclosingPlaceholders(placeholder: Placeholder): Placeholder[] {
let ret: Placeholder[] = [];
let { parent } = placeholder;
while (parent) {
if (parent instanceof Placeholder) {
ret.push(parent);
}
parent = parent.parent;
}
return ret;
}
resolveVariables(resolver: VariableResolver): this {
this.walk(candidate => {
if (candidate instanceof Variable) {
if (candidate.resolve(resolver)) {
this._placeholders = undefined;
}
}
return true;
});
return this;
}
override appendChild(child: Marker) {
this._placeholders = undefined;
return super.appendChild(child);
}
override replace(child: Marker, others: Marker[]): void {
this._placeholders = undefined;
return super.replace(child, others);
}
toTextmateString(): string {
return this.children.reduce((prev, cur) => prev + cur.toTextmateString(), '');
}
clone(): TextmateSnippet {
let ret = new TextmateSnippet();
this._children = this.children.map(child => child.clone());
return ret;
}
walk(visitor: (marker: Marker) => boolean): void {
walk(this.children, visitor);
}
}
export class SnippetParser {
static escape(value: string): string {
return value.replace(/\$|}|\\/g, '\\$&');
}
static guessNeedsClipboard(template: string): boolean {
return /\${?CLIPBOARD/.test(template);
}
private _scanner: Scanner = new Scanner();
private _token: Token = { type: TokenType.EOF, pos: 0, len: 0 };
text(value: string): string {
return this.parse(value).toString();
}
parse(value: string, insertFinalTabstop?: boolean, enforceFinalTabstop?: boolean): TextmateSnippet {
this._scanner.text(value);
this._token = this._scanner.next();
const snippet = new TextmateSnippet();
while (this._parse(snippet)) {
// nothing
}
// fill in values for placeholders. the first placeholder of an index
// that has a value defines the value for all placeholders with that index
const placeholderDefaultValues = new Map<number | string, Marker[] | undefined>();
const incompletePlaceholders: Placeholder[] = [];
let placeholderCount = 0;
snippet.walk(marker => {
if (marker instanceof Placeholder) {
placeholderCount += 1;
if (marker.isFinalTabstop) {
placeholderDefaultValues.set(0, undefined);
} else if (!placeholderDefaultValues.has(marker.index) && marker.children.length > 0) {
placeholderDefaultValues.set(marker.index, marker.children);
} else {
incompletePlaceholders.push(marker);
}
}
return true;
});
for (const placeholder of incompletePlaceholders) {
const defaultValues = placeholderDefaultValues.get(placeholder.index);
if (defaultValues) {
const clone = new Placeholder(placeholder.index);
clone.transform = placeholder.transform;
for (const child of defaultValues) {
clone.appendChild(child.clone());
}
snippet.replace(placeholder, [clone]);
}
}
if (!enforceFinalTabstop) {
enforceFinalTabstop = placeholderCount > 0 && insertFinalTabstop;
}
if (!placeholderDefaultValues.has(0) && enforceFinalTabstop) {
// the snippet uses placeholders but has no
// final tabstop defined -> insert at the end
snippet.appendChild(new Placeholder(0));
}
return snippet;
}
private _accept(type?: TokenType): boolean;
private _accept(type: TokenType | undefined, value: true): string;
private _accept(type: TokenType, value?: boolean): boolean | string {
if (type === undefined || this._token.type === type) {
let ret = !value ? true : this._scanner.tokenText(this._token);
this._token = this._scanner.next();
return ret;
}
return false;
}
private _backTo(token: Token): false {
this._scanner.pos = token.pos + token.len;
this._token = token;
return false;
}
private _until(type: TokenType): false | string {
const start = this._token;
while (this._token.type !== type) {
if (this._token.type === TokenType.EOF) {
return false;
} else if (this._token.type === TokenType.Backslash) {
const nextToken = this._scanner.next();
if (nextToken.type !== TokenType.Dollar
&& nextToken.type !== TokenType.CurlyClose
&& nextToken.type !== TokenType.Backslash) {
return false;
}
}
this._token = this._scanner.next();
}
const value = this._scanner.value.substring(start.pos, this._token.pos).replace(/\\(\$|}|\\)/g, '$1');
this._token = this._scanner.next();
return value;
}
private _parse(marker: Marker): boolean {
return this._parseComplexVariable(marker) || this._parseAnything(marker);
}
private _parseChoiceElement(parent: Choice): boolean {
const token = this._token;
const values: string[] = [];
while (true) {
if (this._token.type === TokenType.Comma || this._token.type === TokenType.Pipe) {
break;
}
let value: string;
if (value = this._accept(TokenType.Backslash, true)) {
// \, \|, or \\
value = this._accept(TokenType.Comma, true)
|| this._accept(TokenType.Pipe, true)
|| this._accept(TokenType.Backslash, true)
|| value;
} else {
value = this._accept(undefined, true);
}
if (!value) {
// EOF
this._backTo(token);
return false;
}
values.push(value);
}
if (values.length === 0) {
this._backTo(token);
return false;
}
parent.appendChild(new Text(values.join('')));
return true;
}
// ${foo:<children>}, ${foo} -> variable
private _parseComplexVariable(parent: Marker): boolean {
let name: string;
const token = this._token;
const match = this._accept(TokenType.Dollar)
&& this._accept(TokenType.CurlyOpen)
&& (name = this._accept(TokenType.VariableName, true));
if (!match) {
return this._backTo(token);
}
const placeholder = new Placeholder(String(name!));
if (this._accept(TokenType.Colon)) {
// ${foo:<children>}
while (true) {
// ...} -> done
if (this._accept(TokenType.CurlyClose)) {
parent.appendChild(placeholder);
return true;
}
if (this._parse(placeholder)) {
continue;
}
// fallback
parent.appendChild(new Text('${' + name! + ':'));
placeholder.children.forEach(parent.appendChild, parent);
return true;
}
} else if (this._accept(TokenType.Pipe)) {
const choice = new Choice();
while (true) {
if (this._parseChoiceElement(choice)) {
if (this._accept(TokenType.Comma)) {
// opt, -> more
continue;
}
if (this._accept(TokenType.Pipe)) {
placeholder.appendChild(choice);
if (this._accept(TokenType.CurlyClose)) {
// ..|} -> done
parent.appendChild(placeholder);
return true;
}
}
}
this._backTo(token);
return false;
}
} else if (this._accept(TokenType.Forwardslash)) {
// ${foo/<regex>/<format>/<options>}
if (this._parseTransform(placeholder)) {
parent.appendChild(placeholder);
return true;
}
this._backTo(token);
return false;
} else if (this._accept(TokenType.CurlyClose)) {
// ${foo}
parent.appendChild(placeholder);
return true;
} else {
// ${foo <- missing curly or colon
return this._backTo(token);
}
}
private _parseTransform(parent: TransformableMarker): boolean {
// ...<regex>/<format>/<options>}
let transform = new Transform();
let regexValue = '';
let regexOptions = '';
// (1) /regex
while (true) {
if (this._accept(TokenType.Forwardslash)) {
break;
}
let escaped: string;
if (escaped = this._accept(TokenType.Backslash, true)) {
escaped = this._accept(TokenType.Forwardslash, true) || escaped;
regexValue += escaped;
continue;
}
if (this._token.type !== TokenType.EOF) {
regexValue += this._accept(undefined, true);
continue;
}
return false;
}
// (2) /format
while (true) {
if (this._accept(TokenType.Forwardslash)) {
break;
}
let escaped: string;
if (escaped = this._accept(TokenType.Backslash, true)) {
escaped = this._accept(TokenType.Backslash, true) || this._accept(TokenType.Forwardslash, true) || escaped;
transform.appendChild(new Text(escaped));
continue;
}
if (this._parseFormatString(transform) || this._parseAnything(transform)) {
continue;
}
return false;
}
// (3) /option
while (true) {
if (this._accept(TokenType.CurlyClose)) {
break;
}
if (this._token.type !== TokenType.EOF) {
regexOptions += this._accept(undefined, true);
continue;
}
return false;
}
try {
transform.regexp = new RegExp(regexValue, regexOptions);
} catch (e) {
// invalid regexp
return false;
}
parent.transform = transform;
return true;
}
private _parseFormatString(parent: Transform): boolean {
const token = this._token;
if (!this._accept(TokenType.Dollar)) {
return false;
}
let complex = false;
if (this._accept(TokenType.CurlyOpen)) {
complex = true;
}
let index = this._accept(TokenType.Int, true);
if (!index) {
this._backTo(token);
return false;
} else if (!complex) {
// $1
parent.appendChild(new FormatString(Number(index)));
return true;
} else if (this._accept(TokenType.CurlyClose)) {
// ${1}
parent.appendChild(new FormatString(Number(index)));
return true;
} else if (!this._accept(TokenType.Colon)) {
this._backTo(token);
return false;
}
if (this._accept(TokenType.Forwardslash)) {
// ${1:/upcase}
let shorthand = this._accept(TokenType.VariableName, true);
if (!shorthand || !this._accept(TokenType.CurlyClose)) {
this._backTo(token);
return false;
} else {
parent.appendChild(new FormatString(Number(index), shorthand));
return true;
}
} else if (this._accept(TokenType.Plus)) {
// ${1:+<if>}
let ifValue = this._until(TokenType.CurlyClose);
if (ifValue) {
parent.appendChild(new FormatString(Number(index), undefined, ifValue, undefined));
return true;
}
} else if (this._accept(TokenType.Dash)) {
// ${2:-<else>}
let elseValue = this._until(TokenType.CurlyClose);
if (elseValue) {
parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue));
return true;
}
} else if (this._accept(TokenType.QuestionMark)) {
// ${2:?<if>:<else>}
let ifValue = this._until(TokenType.Colon);
if (ifValue) {
let elseValue = this._until(TokenType.CurlyClose);
if (elseValue) {
parent.appendChild(new FormatString(Number(index), undefined, ifValue, elseValue));
return true;
}
}
} else {
// ${1:<else>}
let elseValue = this._until(TokenType.CurlyClose);
if (elseValue) {
parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue));
return true;
}
}
this._backTo(token);
return false;
}
private _parseAnything(marker: Marker): boolean {
if (this._token.type !== TokenType.EOF) {
marker.appendChild(new Text(this._scanner.tokenText(this._token)));
this._accept(undefined);
return true;
}
return false;
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
import { DEFAULT_FILE_TYPES } from './../constants/DefaultFileTypes';
import { Settings } from ".";
import { SETTINGS_CONTENT_SUPPORTED_FILETYPES } from "../constants";
import { SETTING_CONTENT_SUPPORTED_FILETYPES } from "../constants";
import { extname } from 'path';
export const isValidFile = (fileName: string) => {
let supportedFiles = Settings.get<string[]>(SETTINGS_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
let supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES;
supportedFiles = supportedFiles.map(f => f.startsWith(`.`) ? f : `.${f}`);
// Get the extension of the file path
+2 -2
View File
@@ -4,7 +4,7 @@ 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 { COMMAND_NAME, DefaultFields, SETTING_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";
@@ -140,7 +140,7 @@ export class PagesListener extends BaseListener {
const wsFolder = Folders.getWorkspaceFolder();
const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description;
const dateField = Settings.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate;
const staticFolder = Settings.get<string>(SETTINGS_CONTENT_STATIC_FOLDER);
const staticFolder = Settings.get<string>(SETTING_CONTENT_STATIC_FOLDER);
const page: Page = {
...article.data,
+4 -4
View File
@@ -1,4 +1,4 @@
import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../../constants";
import { SETTING_CONTENT_STATIC_FOLDER, SETTING_FRAMEWORK_ID } from "../../constants";
import { DashboardCommand } from "../../dashboardWebView/DashboardCommand";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { DashboardSettings, Settings } from "../../helpers";
@@ -54,15 +54,15 @@ export class SettingsListener extends BaseListener {
* @param frameworkId
*/
private static setFramework(frameworkId: string | null) {
Settings.update(SETTINGS_FRAMEWORK_ID, frameworkId, true);
Settings.update(SETTING_FRAMEWORK_ID, frameworkId, true);
if (frameworkId) {
const allFrameworks = FrameworkDetector.getAll();
const framework = allFrameworks.find((f: Framework) => f.name === frameworkId);
if (framework) {
Settings.update(SETTINGS_CONTENT_STATIC_FOLDER, framework.static, true);
Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true);
} else {
Settings.update(SETTINGS_CONTENT_STATIC_FOLDER, "", true);
Settings.update(SETTING_CONTENT_STATIC_FOLDER, "", true);
}
}
}
@@ -0,0 +1,88 @@
import { EditorHelper } from "@estruyf/vscode";
import { Position, window } from "vscode";
import { Dashboard } from "../../commands/Dashboard";
import { SETTING_CONTENT_SNIPPETS } from "../../constants";
import { DashboardMessage } from "../../dashboardWebView/DashboardMessage";
import { Notifications, Settings } from "../../helpers";
import { BaseListener } from "./BaseListener";
export class SnippetListener extends BaseListener {
public static process(msg: { command: DashboardMessage, data: any }) {
super.process(msg);
switch(msg.command) {
case DashboardMessage.addSnippet:
this.addSnippet(msg.data);
break;
case DashboardMessage.updateSnippet:
this.updateSnippet(msg.data);
break;
case DashboardMessage.insertSnippet:
this.insertSnippet(msg.data);
break;
}
}
private static async addSnippet(data: any) {
const { title, description, body } = data;
if (!title || !body) {
Notifications.warning("Snippet missing title or body");
return;
}
const snippets = Settings.get<any>(SETTING_CONTENT_SNIPPETS);
if (snippets && snippets[title]) {
Notifications.warning("Snippet with the same title already exists");
return;
}
const snippetLines = body.split("\n");
snippets[title] = {
description,
body: snippetLines.length === 1 ? snippetLines[0] : snippetLines
};
Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
}
private static async updateSnippet(data: any) {
const { snippets } = data;
if (!snippets) {
Notifications.warning("No snippets to update");
return;
}
Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true);
}
private static async insertSnippet(data: any) {
const { file, snippet } = data;
if (!file || !snippet) {
return;
}
await EditorHelper.showFile(data.file);
Dashboard.resetViewData();
const editor = window.activeTextEditor;
const position = editor?.selection?.active;
if (!position) {
return;
}
const selection = editor?.selection;
await editor?.edit(builder => {
if (selection !== undefined) {
builder.replace(selection, snippet);
} else {
builder.insert(position, snippet);
}
});
}
}
+2
View File
@@ -1,7 +1,9 @@
export * from './BaseListener';
export * from './DashboardListener';
export * from './DataListener';
export * from './ExtensionListener';
export * from './MediaListener';
export * from './PagesListener';
export * from './SettingsListener';
export * from './SnippetListener';
export * from './TelemetryListener';
+3 -3
View File
@@ -1,5 +1,5 @@
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_BETA_ID, EXTENSION_ID, SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_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";
@@ -30,13 +30,13 @@ export class SettingsListener extends BaseListener {
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);
this.updateSetting(SETTING_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 || "");
this.updateSetting(SETTING_FRAMEWORK_START, msg.data || "");
break;
}
}
+3 -1
View File
@@ -1,4 +1,6 @@
import { NavigationType } from '../dashboardWebView/models';
export interface DashboardData {
type: "contents" | "media" | "data";
type: NavigationType;
data?: any;
}
+7
View File
@@ -0,0 +1,7 @@
export interface SnippetField {
name: string;
value: string;
type: 'text' | 'textarea' | 'select';
tmString: string;
options?: string[];
}
+1
View File
@@ -10,6 +10,7 @@ export * from './DraftField';
export * from './Framework';
export * from './MediaPaths';
export * from './PanelSettings';
export * from './SnippetField';
export * from './SortOrder';
export * from './SortType';
export * from './SortingSetting';
+3 -3
View File
@@ -1,7 +1,7 @@
import { ArticleHelper } from '../helpers';
import { languages, TextEditorDecorationType } from 'vscode';
import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, Range, TextDocument, window, Position } from 'vscode';
import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTING_FRONTMATTER_TYPE } from '../constants';
import { SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_FRONTMATTER_TYPE } from '../constants';
import { Settings } from '../helpers';
import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider';
@@ -12,7 +12,7 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider {
private static decType: TextEditorDecorationType | null = null;
public static register() {
const supportedFiles = Settings.get<string[]>(SETTINGS_CONTENT_SUPPORTED_FILETYPES);
const supportedFiles = Settings.get<string[]>(SETTING_CONTENT_SUPPORTED_FILETYPES);
languages.registerFoldingRangeProvider({ language: 'markdown', scheme: 'file' }, new MarkdownFoldingProvider());
@@ -41,7 +41,7 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider {
const isSupported = ArticleHelper.isMarkdownFile(activeDoc);
if (isSupported) {
const fmHighlight = Settings.get<boolean>(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT);
const fmHighlight = Settings.get<boolean>(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT);
const range = this.getFrontMatterRange();