Merge branch 'issue/175' into dev

This commit is contained in:
Elio Struyf
2022-03-04 15:05:31 +01:00
60 changed files with 2521 additions and 136 deletions
+6 -1
View File
@@ -1,12 +1,17 @@
# Change Log
## [6.2.0] - 2022-03-xx
## [7.0.0] - 2022-03-xx
### ✨ Features
- [#175](https://github.com/estruyf/vscode-front-matter/issues/175): New snippet support + dashboard
### 🎨 Enhancements
- Light color theme enhancements to media cards
- Light color theme enhancements to folder cards
- [#272](https://github.com/estruyf/vscode-front-matter/issues/272): New slide over panel for showing details of media files
- [#276](https://github.com/estruyf/vscode-front-matter/issues/276): Add a Front Matter walkthrough for VS Code
## [6.1.1] - 2022-03-02
+2
View File
@@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1">
</svg>

After

Width:  |  Height:  |  Size: 68 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#C5C5C5" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
</svg>

After

Width:  |  Height:  |  Size: 357 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#424242" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.121 14.121L19 19m-7-7l7-7m-7 7l-2.879 2.879M12 12L9.121 9.121m0 5.758a3 3 0 10-4.243 4.243 3 3 0 004.243-4.243zm0-5.758a3 3 0 10-4.243-4.243 3 3 0 004.243 4.243z" />
</svg>

After

Width:  |  Height:  |  Size: 357 B

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