mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-06 09:51:29 +02:00
Merge branch 'issue/175' into dev
This commit is contained in:
+26
-2
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export const SnippetVariables = {
|
||||
FM_SELECTED_TEXT: 'FM_SELECTED_TEXT',
|
||||
FM_TEXT: 'FM_TEXT_',
|
||||
FM_MULTILINE: 'FM_MULTILINE_',
|
||||
};
|
||||
@@ -4,6 +4,7 @@ export const TelemetryEvent = {
|
||||
openContentDashboard: 'openContentDashboard',
|
||||
openMediaDashboard: 'openMediaDashboard',
|
||||
openDataDashboard: 'openDataDashboard',
|
||||
openSnippetsDashboard: 'openSnippetsDashboard',
|
||||
closeDashboard: 'closeDashboard',
|
||||
generateSlug: 'generateSlug',
|
||||
createContentFromTemplate: 'createContentFromTemplate',
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
export const CONTEXT = {
|
||||
canInit: "frontMatterCanInit",
|
||||
initialized: "frontMatterInitialized",
|
||||
canOpenPreview: "frontMatterCanOpenPreview",
|
||||
canOpenDashboard: "frontMatterCanOpenDashboard",
|
||||
isEnabled: "frontMatter:enabled",
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
+5
-6
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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",
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export interface Settings {
|
||||
dataFiles: DataFile[] | undefined;
|
||||
dataTypes: DataType[] | undefined;
|
||||
isBacker: boolean | undefined;
|
||||
snippets: any | undefined;
|
||||
}
|
||||
|
||||
export interface DashboardState {
|
||||
|
||||
+12
-3
@@ -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()));
|
||||
|
||||
|
||||
@@ -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}}}`)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 || "");
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
|
||||
export interface DashboardData {
|
||||
type: "contents" | "media" | "data";
|
||||
type: NavigationType;
|
||||
data?: any;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface SnippetField {
|
||||
name: string;
|
||||
value: string;
|
||||
type: 'text' | 'textarea' | 'select';
|
||||
tmString: string;
|
||||
options?: string[];
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user