forked from iarv/vscode-front-matter
Code snippets changes: add, update, delete, and more
This commit is contained in:
@@ -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.0] - 2022-02-28 - [Release notes](https://beta.frontmatter.codes/updates/v6.1.0)
|
||||
|
||||
|
||||
2
assets/empty.svg
Normal file
2
assets/empty.svg
Normal 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
assets/icons/scissors-dark.svg
Normal file
3
assets/icons/scissors-dark.svg
Normal 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
assets/icons/scissors-light.svg
Normal file
3
assets/icons/scissors-light.svg
Normal 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
assets/walkthrough/documentation.md
Normal file
3
assets/walkthrough/documentation.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Documentation
|
||||
|
||||
Our documentation can be found at: [https://frontmatter.codes/docs](https://frontmatter.codes/docs)
|
||||
11
assets/walkthrough/get-started.md
Normal file
11
assets/walkthrough/get-started.md
Normal 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
|
||||
8
assets/walkthrough/support-the-project.md
Normal file
8
assets/walkthrough/support-the-project.md
Normal file
@@ -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).
|
||||
77
package.json
77
package.json
@@ -1089,7 +1089,7 @@
|
||||
},
|
||||
{
|
||||
"command": "frontMatter.markup.blockquote",
|
||||
"title": "Codeblock",
|
||||
"title": "Blockquote",
|
||||
"category": "Front matter",
|
||||
"icon": {
|
||||
"light": "assets/icons/blockquote-light.svg",
|
||||
@@ -1180,6 +1180,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",
|
||||
@@ -1212,6 +1221,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",
|
||||
@@ -1287,32 +1305,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"
|
||||
},
|
||||
{
|
||||
@@ -1345,6 +1363,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",
|
||||
@@ -1466,6 +1489,42 @@
|
||||
"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": {
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Template } from "./Template";
|
||||
import { Folders } from "./Folders";
|
||||
import { Settings } from "../helpers";
|
||||
import { SETTING_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants";
|
||||
import { SettingsListener } from '../listeners/dashboard';
|
||||
|
||||
export class Project {
|
||||
|
||||
@@ -50,6 +51,8 @@ categories: []
|
||||
}
|
||||
|
||||
Telemetry.send(TelemetryEvent.initialization)
|
||||
|
||||
SettingsListener.getSettings();
|
||||
} catch (err: any) {
|
||||
Notifications.error(`Sorry, something went wrong - ${err?.message || err}`);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,7 @@ export class Wysiwyg {
|
||||
{ label: "$(tasklist) Task list", detail: "Add a task list", alwaysShow: true },
|
||||
{ label: "$(code) Code", detail: "Add inline code snippet", alwaysShow: true },
|
||||
{ label: "$(symbol-namespace) Code block", detail: "Add a code block", alwaysShow: true },
|
||||
{ label: "$(quote) Blockquote", detail: "Add a blockquote", alwaysShow: true },
|
||||
]
|
||||
|
||||
const option = await window.showQuickPick([ ...qpItems ], {
|
||||
@@ -73,6 +74,8 @@ export class Wysiwyg {
|
||||
await this.addMarkup(MarkupType.code);
|
||||
} else if (option.label === qpItems[4].label) {
|
||||
await this.addMarkup(MarkupType.codeblock);
|
||||
} else if (option.label === qpItems[5].label) {
|
||||
await this.addMarkup(MarkupType.blockquote);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -29,13 +29,17 @@ export const COMMAND_NAME = {
|
||||
preview: getCommandName("preview"),
|
||||
dashboard: getCommandName("dashboard"),
|
||||
dashboardMedia: getCommandName("dashboard.media"),
|
||||
dashboardSnippets: getCommandName("dashboard.snippets"),
|
||||
dashboardData: getCommandName("dashboard.data"),
|
||||
dashboardClose: getCommandName("dashboard.close"),
|
||||
promote: getCommandName("promoteSettings"),
|
||||
insertImage: getCommandName("insertImage"),
|
||||
createFolder: getCommandName("createFolder"),
|
||||
diagnostics: getCommandName("diagnostics"),
|
||||
|
||||
// Insert dashboards
|
||||
insertImage: getCommandName("insertImage"),
|
||||
insertSnippet: getCommandName("insertSnippet"),
|
||||
|
||||
// WYSIWYG
|
||||
bold: getCommandName("markup.bold"),
|
||||
italic: getCommandName("markup.italic"),
|
||||
|
||||
@@ -4,6 +4,7 @@ export const TelemetryEvent = {
|
||||
openContentDashboard: 'openContentDashboard',
|
||||
openMediaDashboard: 'openMediaDashboard',
|
||||
openDataDashboard: 'openDataDashboard',
|
||||
openSnippetsDashboard: 'openSnippetsDashboard',
|
||||
closeDashboard: 'closeDashboard',
|
||||
generateSlug: 'generateSlug',
|
||||
createContentFromTemplate: 'createContentFromTemplate',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const CONTEXT = {
|
||||
canInit: "frontMatterCanInit",
|
||||
initialized: "frontMatterInitialized",
|
||||
canOpenPreview: "frontMatterCanOpenPreview",
|
||||
canOpenDashboard: "frontMatterCanOpenDashboard",
|
||||
isEnabled: "frontMatter:enabled",
|
||||
|
||||
@@ -26,4 +26,6 @@ export enum DashboardMessage {
|
||||
putDataEntries = 'putDataEntries',
|
||||
sendTelemetry = 'sendTelemetry',
|
||||
insertSnippet = 'insertSnippet',
|
||||
addSnippet = 'addSnippet',
|
||||
updateSnippet = 'updateSnippet',
|
||||
}
|
||||
@@ -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">
|
||||
{
|
||||
|
||||
@@ -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 { CodeIcon, 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';
|
||||
@@ -33,7 +33,7 @@ export const Tabs: React.FunctionComponent<ITabsProps> = ({ onNavigate }: React.
|
||||
<Tab
|
||||
navigationType={NavigationType.Snippets}
|
||||
onNavigate={onNavigate}>
|
||||
<CodeIcon className={`h-6 w-auto mr-2`} /><span>Snippets</span>
|
||||
<ScissorsIcon className={`h-6 w-auto mr-2`} /><span>Snippets</span>
|
||||
</Tab>
|
||||
</li>
|
||||
{
|
||||
|
||||
@@ -4,16 +4,18 @@ 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> = ({ folders, totalPages, children }: React.PropsWithChildren<IPageLayoutProps>) => {
|
||||
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} />
|
||||
|
||||
@@ -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`} />
|
||||
|
||||
@@ -16,8 +16,7 @@ export interface IFormDialogProps {
|
||||
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 FormDialog: React.FunctionComponent<IFormDialogProps> = ({title, de
|
||||
<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 FormDialog: React.FunctionComponent<IFormDialogProps> = ({title, de
|
||||
</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}
|
||||
>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DotsHorizontalIcon, PlusIcon } from '@heroicons/react/outline';
|
||||
import { CodeIcon, DotsHorizontalIcon, PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Choice, Scanner, SnippetParser, TokenType, Variable, VariableResolver } from '../../../helpers/SnippetParser';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ViewDataSelector } from '../../state';
|
||||
import { SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { Alert } from '../Modals/Alert';
|
||||
import { FormDialog } from '../Modals/FormDialog';
|
||||
import { SnippetForm } from './SnippetForm';
|
||||
import { NewForm } from './NewForm';
|
||||
import SnippetForm, { SnippetFormHandle } from './SnippetForm';
|
||||
|
||||
export interface IItemProps {
|
||||
title: string;
|
||||
@@ -16,21 +17,77 @@ export interface IItemProps {
|
||||
|
||||
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);
|
||||
|
||||
// Todo: On add, show dialog to insert the placeholders and content
|
||||
const [ snippetTitle, setSnippetTitle ] = useState<string>('');
|
||||
const [ snippetDescription, setSnippetDescription ] = useState<string>('');
|
||||
const [ snippetOriginalBody, setSnippetOriginalBody ] = useState<string>('');
|
||||
|
||||
const formRef = useRef<SnippetFormHandle>(null);
|
||||
|
||||
const insertToArticle = () => {
|
||||
Messenger.send(DashboardMessage.insertSnippet, {
|
||||
file: viewData?.data?.filePath,
|
||||
snippet: snippet.body.join(`\n`)
|
||||
});
|
||||
formRef.current?.onSave();
|
||||
setShowInsertDialog(false);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setShowEditDialog(false);
|
||||
setSnippetTitle('');
|
||||
setSnippetDescription('');
|
||||
setSnippetOriginalBody('');
|
||||
};
|
||||
|
||||
const onOpenEdit = useCallback(() => {
|
||||
setSnippetTitle(title);
|
||||
setSnippetDescription(snippet.description);
|
||||
setSnippetOriginalBody(snippet.body.join(`\n`));
|
||||
setShowEditDialog(true);
|
||||
}, [snippet]);
|
||||
|
||||
const onSnippetUpdate = useCallback(() => {
|
||||
if (!snippetTitle || !snippetOriginalBody) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const snippets = Object.assign({}, settings?.snippets || {});
|
||||
const snippetContents = {
|
||||
description: snippetDescription || '',
|
||||
body: snippetOriginalBody.split("\n")
|
||||
};
|
||||
|
||||
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 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 p-4 space-y-2">
|
||||
<div className="font-bold text-xl">{title}</div>
|
||||
<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">
|
||||
@@ -40,22 +97,30 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
|
||||
<div className='hidden group-hover:flex space-x-2'>
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
viewData?.data?.filePath && (
|
||||
<>
|
||||
<button onClick={() => setShowInsertDialog(true)}>
|
||||
<PlusIcon className='w-4 h-4' />
|
||||
<span className='sr-only'>Insert snippet</span>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div>Edit</div>
|
||||
)
|
||||
}
|
||||
|
||||
<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-whisper-900 text-base">{snippet.description}</p>
|
||||
<p className="text-xs text-vulcan-200 dark:text-whisper-800">{snippet.description}</p>
|
||||
</li>
|
||||
|
||||
{
|
||||
@@ -70,11 +135,48 @@ export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: Re
|
||||
cancelBtnText='Cancel'>
|
||||
|
||||
<SnippetForm
|
||||
snippet={snippet} />
|
||||
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} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
108
src/dashboardWebView/components/SnippetsView/NewForm.tsx
Normal file
108
src/dashboardWebView/components/SnippetsView/NewForm.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
|
||||
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">{`\${selection}`}</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>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,68 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { ChevronDownIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Choice, SnippetParser, Variable, VariableResolver } from '../../../helpers/SnippetParser';
|
||||
import { DashboardMessage } from '../../DashboardMessage';
|
||||
import { ViewDataSelector } from '../../state';
|
||||
|
||||
|
||||
export interface ISnippetFormProps {
|
||||
snippet: any;
|
||||
selection: string | undefined;
|
||||
}
|
||||
|
||||
export const SnippetForm: React.FunctionComponent<ISnippetFormProps> = ({ snippet }: React.PropsWithChildren<ISnippetFormProps>) => {
|
||||
const [ fields, setFields ] = useState<any>([]);
|
||||
export interface SnippetFormHandle {
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
interface SnippetField {
|
||||
name: string;
|
||||
value: string;
|
||||
type: 'text' | 'select';
|
||||
tmString: string;
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
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((fieldName: string) => {
|
||||
if (selection && fieldName === 'selection') {
|
||||
return selection;
|
||||
}
|
||||
|
||||
return;
|
||||
}, [selection]);
|
||||
|
||||
const snippetBody = useMemo(() => {
|
||||
let body = snippet.body.join(`\n`);
|
||||
|
||||
for (const field of fields) {
|
||||
body = body.replace(field.tmString, field.value);
|
||||
}
|
||||
|
||||
return body;
|
||||
}, [fields, selection]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onSave() {
|
||||
if (!snippetBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
Messenger.send(DashboardMessage.insertSnippet, {
|
||||
file: viewData?.data?.filePath,
|
||||
snippet: snippetBody
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
const snippetParser = new SnippetParser();
|
||||
@@ -20,30 +75,31 @@ export const SnippetForm: React.FunctionComponent<ISnippetFormProps> = ({ snippe
|
||||
|
||||
for (const placeholder of placeholders) {
|
||||
const tmString = placeholder.toTextmateString();
|
||||
console.log(`tmString`, placeholder);
|
||||
|
||||
if (placeholder.children.length === 0) {
|
||||
allFields.push({
|
||||
type: 'text',
|
||||
name: placeholder.index,
|
||||
value: '',
|
||||
value: insertSelectionValue(placeholder.index as string) || '',
|
||||
tmString
|
||||
});
|
||||
} else {
|
||||
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: child.options,
|
||||
value: (child as any).value || options[0] || "",
|
||||
options,
|
||||
tmString
|
||||
});
|
||||
} else {
|
||||
allFields.push({
|
||||
type: 'text',
|
||||
name: placeholder.index,
|
||||
value: (child as any).value,
|
||||
value: insertSelectionValue(placeholder.index as string) || (child as any).value || "",
|
||||
tmString
|
||||
});
|
||||
}
|
||||
@@ -56,17 +112,51 @@ export const SnippetForm: React.FunctionComponent<ISnippetFormProps> = ({ snippe
|
||||
|
||||
return (
|
||||
<div>
|
||||
<pre className='border border-opacity-40 p-2 whitespace-normal break-words'>{snippet.body.join(`\n`)}</pre>
|
||||
<pre className='border border-opacity-40 p-2 whitespace-pre-wrap break-words'>
|
||||
{snippetBody}
|
||||
</pre>
|
||||
|
||||
<ul className='mt-4'>
|
||||
<div className='space-y-4 mt-4'>
|
||||
{
|
||||
fields.map((field: any, index: number) => (
|
||||
<li key={index}>
|
||||
<p>{field.name} - {field.value} - {(field.options || []).join(',')}</p>
|
||||
</li>
|
||||
fields.map((field: SnippetField, index: number) => (
|
||||
<div key={index}>
|
||||
<label htmlFor={field.name} className="block text-sm font-medium capitalize">
|
||||
{field.name}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
{
|
||||
field.type === 'select' ? (
|
||||
<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 => onTextChange(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>
|
||||
) : (
|
||||
<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) => onTextChange(field, e.currentTarget.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default React.forwardRef(SnippetForm);
|
||||
@@ -1,22 +1,69 @@
|
||||
import { CodeIcon } from '@heroicons/react/outline';
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { CodeIcon, PlusSmIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useMemo } 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>
|
||||
<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`}>
|
||||
@@ -46,6 +93,29 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
|
||||
</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>
|
||||
);
|
||||
};
|
||||
@@ -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:
|
||||
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -238,11 +238,13 @@ export class Placeholder extends TransformableMarker {
|
||||
|
||||
toTextmateString(): string {
|
||||
let transformString = '';
|
||||
|
||||
if (this.transform) {
|
||||
transformString = this.transform.toTextmateString();
|
||||
}
|
||||
|
||||
if (this.children.length === 0 && !this.transform) {
|
||||
return `\$${this.index}`;
|
||||
return `\${${this.index}}`;
|
||||
} else if (this.children.length === 0) {
|
||||
return `\${${this.index}${transformString}}`;
|
||||
} else if (this.choice) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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";
|
||||
|
||||
|
||||
@@ -11,12 +13,51 @@ export class SnippetListener extends BaseListener {
|
||||
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;
|
||||
}
|
||||
|
||||
snippets[title] = {
|
||||
description,
|
||||
body: body.split("\n")
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { NavigationType } from '../dashboardWebView/models';
|
||||
|
||||
export interface DashboardData {
|
||||
type: "contents" | "media" | "data";
|
||||
type: NavigationType;
|
||||
data?: any;
|
||||
}
|
||||
Reference in New Issue
Block a user