mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-12 03:43:01 +02:00
Snippet dialog
This commit is contained in:
+2
-2
@@ -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,7 +13,7 @@ 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);
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { Messenger } from '@estruyf/vscode/dist/client';
|
||||
import { DotsHorizontalIcon, PlusIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, 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 { FormDialog } from '../Modals/FormDialog';
|
||||
import { SnippetForm } from './SnippetForm';
|
||||
|
||||
export interface IItemProps {
|
||||
title: string;
|
||||
snippet: any;
|
||||
}
|
||||
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ snippet }: React.PropsWithChildren<IItemProps>) => {
|
||||
export const Item: React.FunctionComponent<IItemProps> = ({ title, snippet }: React.PropsWithChildren<IItemProps>) => {
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
const [ showInsertDialog, setShowInsertDialog ] = useState(false);
|
||||
|
||||
// Todo: On add, show dialog to insert the placeholders and content
|
||||
|
||||
@@ -21,49 +26,55 @@ export const Item: React.FunctionComponent<IItemProps> = ({ snippet }: React.Pro
|
||||
snippet: snippet.body.join(`\n`)
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const snippetParser = new SnippetParser();
|
||||
const body = snippet.body.join(`\n`);
|
||||
|
||||
const parsed = snippetParser.parse(body);
|
||||
const placeholders = parsed.placeholderInfo.all;
|
||||
|
||||
for (const placeholder of placeholders) {
|
||||
const tmString = placeholder.toTextmateString();
|
||||
|
||||
for (const child of placeholder.children as any[]) {
|
||||
if (child instanceof Choice) {
|
||||
console.log(child.options)
|
||||
} else {
|
||||
console.log(tmString, child.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resolver: VariableResolver = {
|
||||
resolve: (variable: Variable): string | undefined => {
|
||||
console.log(`variable`, variable);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
parsed.resolveVariables(resolver);
|
||||
}, []);
|
||||
|
||||
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">
|
||||
<div className="font-bold text-xl mb-2">{snippet.title}</div>
|
||||
<p className="text-whisper-900 text-base">{snippet.description}</p>
|
||||
<div>
|
||||
{
|
||||
viewData?.data?.filePath ? (
|
||||
<button onClick={insertToArticle}>Add</button>
|
||||
) : (
|
||||
<div>Edit</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
<>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</>
|
||||
) : (
|
||||
<div>Edit</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-whisper-900 text-base">{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
|
||||
snippet={snippet} />
|
||||
|
||||
</FormDialog>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Choice, SnippetParser, Variable, VariableResolver } from '../../../helpers/SnippetParser';
|
||||
|
||||
export interface ISnippetFormProps {
|
||||
snippet: any;
|
||||
}
|
||||
|
||||
export const SnippetForm: React.FunctionComponent<ISnippetFormProps> = ({ snippet }: React.PropsWithChildren<ISnippetFormProps>) => {
|
||||
const [ fields, setFields ] = useState<any>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const snippetParser = new SnippetParser();
|
||||
const 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();
|
||||
console.log(`tmString`, placeholder);
|
||||
|
||||
if (placeholder.children.length === 0) {
|
||||
allFields.push({
|
||||
type: 'text',
|
||||
name: placeholder.index,
|
||||
value: '',
|
||||
tmString
|
||||
});
|
||||
} else {
|
||||
for (const child of placeholder.children as any[]) {
|
||||
if (child instanceof Choice) {
|
||||
allFields.push({
|
||||
type: 'select',
|
||||
name: placeholder.index,
|
||||
value: (child as any).value,
|
||||
options: child.options,
|
||||
tmString
|
||||
});
|
||||
} else {
|
||||
allFields.push({
|
||||
type: 'text',
|
||||
name: placeholder.index,
|
||||
value: (child as any).value,
|
||||
tmString
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFields(allFields);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<pre className='border border-opacity-40 p-2 whitespace-normal break-words'>{snippet.body.join(`\n`)}</pre>
|
||||
|
||||
<ul className='mt-4'>
|
||||
{
|
||||
fields.map((field: any, index: number) => (
|
||||
<li key={index}>
|
||||
<p>{field.name} - {field.value} - {(field.options || []).join(',')}</p>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CodeIcon } from '@heroicons/react/outline';
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { AddIcon } from '../../../panelWebView/components/Icons/AddIcon';
|
||||
import { SettingsSelector, ViewDataSelector } from '../../state';
|
||||
import { PageLayout } from '../Layout/PageLayout';
|
||||
import { Item } from './Item';
|
||||
@@ -12,7 +12,8 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
|
||||
const settings = useRecoilValue(SettingsSelector);
|
||||
const viewData = useRecoilValue(ViewDataSelector);
|
||||
|
||||
const snippets = settings?.snippets || [];
|
||||
const snippetKeys = useMemo(() => Object.keys(settings?.snippets) || [], [settings?.snippets]);
|
||||
const snippets = settings?.snippets || {};
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
@@ -25,11 +26,14 @@ export const Snippets: React.FunctionComponent<ISnippetsProps> = (props: React.P
|
||||
}
|
||||
|
||||
{
|
||||
snippets && snippets.length > 0 ? (
|
||||
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`}>
|
||||
{
|
||||
snippets.map((snippet: any, index: number) => (
|
||||
<Item snippet={snippet} key={index} />
|
||||
snippetKeys.map((snippetKey: any, index: number) => (
|
||||
<Item
|
||||
key={index}
|
||||
title={snippetKey}
|
||||
snippet={snippets[snippetKey]} />
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
@@ -221,23 +221,8 @@ export abstract class TransformableMarker extends Marker {
|
||||
}
|
||||
|
||||
export class Placeholder extends TransformableMarker {
|
||||
static compareByIndex(a: Placeholder, b: Placeholder): number {
|
||||
if (a.index === b.index) {
|
||||
return 0;
|
||||
} else if (a.isFinalTabstop) {
|
||||
return 1;
|
||||
} else if (b.isFinalTabstop) {
|
||||
return -1;
|
||||
} else if (a.index < b.index) {
|
||||
return -1;
|
||||
} else if (a.index > b.index) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(public index: number) {
|
||||
constructor(public index: number | string) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -629,7 +614,7 @@ export class SnippetParser {
|
||||
|
||||
// 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, Marker[] | undefined>();
|
||||
const placeholderDefaultValues = new Map<number | string, Marker[] | undefined>();
|
||||
const incompletePlaceholders: Placeholder[] = [];
|
||||
let placeholderCount = 0;
|
||||
snippet.walk(marker => {
|
||||
@@ -876,7 +861,7 @@ export class SnippetParser {
|
||||
return this._backTo(token);
|
||||
}
|
||||
|
||||
const variable = new Variable(name!);
|
||||
const placeholder = new Placeholder(String(name!));
|
||||
|
||||
if (this._accept(TokenType.Colon)) {
|
||||
// ${foo:<children>}
|
||||
@@ -884,24 +869,49 @@ export class SnippetParser {
|
||||
|
||||
// ...} -> done
|
||||
if (this._accept(TokenType.CurlyClose)) {
|
||||
parent.appendChild(variable);
|
||||
parent.appendChild(placeholder);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._parse(variable)) {
|
||||
if (this._parse(placeholder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// fallback
|
||||
parent.appendChild(new Text('${' + name! + ':'));
|
||||
variable.children.forEach(parent.appendChild, parent);
|
||||
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(variable)) {
|
||||
parent.appendChild(variable);
|
||||
if (this._parseTransform(placeholder)) {
|
||||
parent.appendChild(placeholder);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -910,7 +920,7 @@ export class SnippetParser {
|
||||
|
||||
} else if (this._accept(TokenType.CurlyClose)) {
|
||||
// ${foo}
|
||||
parent.appendChild(variable);
|
||||
parent.appendChild(placeholder);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user