Skip to content

Commit

Permalink
Merge branch 'issue/865' into beta
Browse files Browse the repository at this point in the history
  • Loading branch information
estruyf committed Oct 10, 2024
2 parents 8660f5f + 3b7671a commit 305c95f
Show file tree
Hide file tree
Showing 10 changed files with 9,086 additions and 3,543 deletions.
12,315 changes: 8,829 additions & 3,486 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 17 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1366,7 +1366,14 @@
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description%"
},
"wysiwyg": {
"type": "boolean",
"type": [
"boolean",
"string"
],
"enum": [
"html",
"markdown"
],
"default": false,
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description%"
},
Expand Down Expand Up @@ -2887,7 +2894,14 @@
"react-router-dom": "^6.8.0",
"react-sortable-hoc": "^2.0.0",
"recoil": "^0.7.7",
"remark-gfm": "^3.0.1",
"rehype-parse": "^9.0.1",
"rehype-remark": "^10.0.0",
"rehype-stringify": "^10.0.1",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"remark-stringify": "^11.0.0",
"rimraf": "^3.0.2",
"semver": "^7.3.8",
"simple-git": "^3.16.0",
Expand All @@ -2897,6 +2911,7 @@
"tailwindcss-animate": "^1.0.7",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"unified": "^11.0.5",
"uniforms": "^3.10.2",
"uniforms-antd": "^3.10.2",
"uniforms-bridge-json-schema": "^3.10.2",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.id.description": "The choice ID",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.choices.items.properties.title.description": "The choice title",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.single.description": "Is a single line field",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field (HTML output)",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.wysiwyg.description": "Is a WYSIWYG field. You can set it to markdown or HTML.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.multiple.description": "Do you allow to select multiple values?",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.isPreviewImage.description": "Specify if the image field can be used as preview. Be aware, you can only have one preview image per content type.",
"setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.hidden.description": "Do you want to hide the field from the metadata section?",
Expand Down
2 changes: 1 addition & 1 deletion src/models/PanelSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export interface Field {
type: FieldType;
choices?: string[] | Choice[];
single?: boolean;
wysiwyg?: boolean;
wysiwyg?: boolean | string;
multiple?: boolean;
isPreviewImage?: boolean;
hidden?: boolean;
Expand Down
12 changes: 1 addition & 11 deletions src/panelWebView/components/Fields/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const DEBOUNCE_TIME = 300;

export interface ITextFieldProps extends BaseFieldProps<string> {
singleLine: boolean | undefined;
wysiwyg: boolean | undefined;
limit: number | undefined;
rows?: number;
name: string;
Expand All @@ -26,12 +25,9 @@ export interface ITextFieldProps extends BaseFieldProps<string> {
onChange: (txtValue: string) => void;
}

const WysiwygField = React.lazy(() => import('./WysiwygField'));

export const TextField: React.FunctionComponent<ITextFieldProps> = ({
placeholder,
singleLine,
wysiwyg,
limit,
label,
description,
Expand Down Expand Up @@ -199,13 +195,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
</div>
)}

{wysiwyg ? (
<React.Suspense
fallback={<div>{localize(LocalizationKey.panelFieldsTextFieldLoading)}</div>}
>
<WysiwygField text={text || ''} onChange={onTextChange} />
</React.Suspense>
) : singleLine ? (
{singleLine ? (
<input
className={`metadata_field__input`}
value={text || ''}
Expand Down
68 changes: 46 additions & 22 deletions src/panelWebView/components/Fields/WrapperField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import {
NumberField,
CustomField,
FieldCollection,
TagPicker
TagPicker,
WYSIWYGType
} from '.';
import { fieldWhenClause } from '../../../utils/fieldWhenClause';
import { ContentTypeRelationshipField } from './ContentTypeRelationshipField';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { LocalizationKey, localize } from '../../../localization';
import { GeneralCommands } from '../../../constants';
const WysiwygField = React.lazy(() => import('./WysiwygField'));

export interface IWrapperFieldProps {
field: Field;
Expand Down Expand Up @@ -210,24 +212,43 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
limit = settings?.seo.description;
}

return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TextField
name={field.name}
label={field.title || field.name}
description={field.description}
singleLine={field.single}
limit={limit}
wysiwyg={field.wysiwyg}
rows={4}
onChange={onFieldChange}
value={(fieldValue as string) || null}
required={!!field.required}
settings={settings}
actions={field.actions}
/>
</FieldBoundary>
);
if (field.wysiwyg) {
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<React.Suspense
fallback={<div>{localize(LocalizationKey.panelFieldsTextFieldLoading)}</div>}
>
<WysiwygField
name={field.name}
label={field.title || field.name}
description={field.description}
limit={limit}
type={typeof field.wysiwyg === 'boolean' ? 'html' : field.wysiwyg.toLowerCase() as WYSIWYGType}
onChange={onFieldChange}
value={(fieldValue as string) || null}
required={!!field.required} />
</React.Suspense>
</FieldBoundary>
);
} else {
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<TextField
name={field.name}
label={field.title || field.name}
description={field.description}
singleLine={field.single}
limit={limit}
rows={4}
onChange={onFieldChange}
value={(fieldValue as string) || null}
required={!!field.required}
settings={settings}
actions={field.actions}
/>
</FieldBoundary>
);
}
} else if (field.type === 'number') {
let nrValue: number | null = field.numberOptions?.isDecimal ? parseFloat(fieldValue as string) : parseInt(fieldValue as string);
if (isNaN(nrValue)) {
Expand Down Expand Up @@ -556,7 +577,10 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
</FieldBoundary>
);
} else {
console.warn(l10n.t(LocalizationKey.panelFieldsWrapperFieldUnknown, field.type));
messageHandler.send(GeneralCommands.toVSCode.logging.verbose, {
message: localize(LocalizationKey.panelFieldsWrapperFieldUnknown, field.type),
location: 'PANEL'
});
return null;
}
};
189 changes: 175 additions & 14 deletions src/panelWebView/components/Fields/WysiwygField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,186 @@ import * as React from 'react';
const ReactQuill = require('react-quill');
import 'react-quill/dist/quill.snow.css';

export interface IWysiwygFieldProps {
text: string;
import { PencilIcon } from '@heroicons/react/24/outline';

import { unified } from "unified";
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import remarkRehype from 'remark-rehype'
import remarkStringify from "remark-stringify";

import rehypeParse from "rehype-parse";
import rehypeRemark from "rehype-remark";
import rehypeStringify from 'rehype-stringify'

import { differenceInSeconds } from "date-fns";
import { BaseFieldProps } from '../../../models';
import { FieldMessage, FieldTitle } from '.';
import { LocalizationKey, localize } from '../../../localization';
import { useRecoilState } from 'recoil';
import { RequiredFieldsAtom } from '../../state';
import { useDebounce } from '../../../hooks/useDebounce';

const DEBOUNCE_TIME = 500;

function markdownToHtml(markdownText: string) {
const file = unified()
.use(remarkParse)
.use(remarkRehype)
.use(remarkGfm)
.use(rehypeStringify)
.processSync(markdownText);

const htmlContents = String(file);
return htmlContents.replace(/<del>/g, '<s>').replace(/<\/del>/g, '</s>');
}

function htmlToMarkdown(htmlText: string) {
const file = unified()
.use(rehypeParse, { emitParseErrors: true, duplicateAttribute: false })
.use(rehypeRemark)
.use(remarkGfm)
.use(remarkStringify)
.processSync(htmlText);
return String(file);
}

export type WYSIWYGType = "html" | "markdown";

export interface IWysiwygFieldProps extends BaseFieldProps<string> {
name: string;
limit?: number;
type: WYSIWYGType;
onChange: (txtValue: string) => void;
}

const WysiwygField: React.FunctionComponent<IWysiwygFieldProps> = ({
text,
onChange
label,
description,
value,
type = "html",
onChange,
limit,
required
}: React.PropsWithChildren<IWysiwygFieldProps>) => {
const modules = {
toolbar: [
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'ordered' }, { list: 'bullet' }],
['clean']
]
};

return <ReactQuill modules={modules} value={text || ''} onChange={onChange} />;
const [, setRequiredFields] = useRecoilState(RequiredFieldsAtom);
const [lastUpdated, setLastUpdated] = React.useState<number | null>(null);
const [text, setText] = React.useState<string | null | undefined>(type === "html" ? value : markdownToHtml(value || ""));
const debouncedText = useDebounce<string | null | undefined>(text, DEBOUNCE_TIME);

const onTextChange = (newValue: string) => {
setText(newValue);
setLastUpdated(Date.now());
}

const modules = React.useMemo(() => {
const styles = ['bold', 'italic', 'strike'];

if (type === "html") {
styles.push('underline');
}

return {
toolbar: [
[{ header: [1, 2, 3, false] }],
styles,
[{ list: 'ordered' }, { list: 'bullet' }],
['clean']
]
};
}, [type]);

const isValid = React.useMemo(() => {
let temp = true;
if (limit && limit !== -1) {
temp = (text || '').length <= limit;
}
return temp;
}, [limit, text]);

const updateRequired = React.useCallback(
(isValid: boolean) => {
setRequiredFields((prev) => {
let clone = Object.assign([], prev);

if (isValid) {
clone = clone.filter((item) => item !== label);
} else {
clone.push(label);
}

return clone;
});
},
[setRequiredFields]
);

const showRequiredState = React.useMemo(() => {
return required && !text;
}, [required, text]);

const border = React.useMemo(() => {
if (showRequiredState) {
updateRequired(false);
return '1px solid var(--vscode-inputValidation-errorBorder)';
} else if (!isValid) {
updateRequired(true);
return '1px solid var(--vscode-inputValidation-warningBorder)';
} else {
updateRequired(true);
return '1px solid var(--vscode-inputValidation-infoBorder)';
}
}, [showRequiredState, isValid]);

/**
* Update the text value when the value changes
*/
React.useEffect(() => {
if (text !== value && (lastUpdated === null || differenceInSeconds(Date.now(), lastUpdated) > 2)) {
setText(type === "html" ? value : markdownToHtml(value || ""));
}
setLastUpdated(null);
}, [value]);

/**
* Update the value when the debounced text changes
*/
React.useEffect(() => {
if (debouncedText !== undefined && value !== debouncedText && lastUpdated !== null) {
const valueToUpdate = type === "html" ? debouncedText : htmlToMarkdown(debouncedText || "");
onChange(valueToUpdate || "");
}
}, [debouncedText]);

return (
<div className={`metadata_field`}>
<FieldTitle
label={label}
icon={<PencilIcon />}
required={required}
/>

<ReactQuill
modules={modules}
value={text || ''}
onChange={onTextChange}
style={{ border }}
/>

{limit && limit > 0 && (text || '').length > limit && (
<div className={`metadata_field__limit`}>
{localize(LocalizationKey.panelFieldsTextFieldLimit, `${(text || '').length}/${limit}`)}
</div>
)}

<FieldMessage
description={description}
name={label.toLowerCase()}
showRequired={showRequiredState}
/>

</div>
);
};

export default WysiwygField;
Loading

0 comments on commit 305c95f

Please sign in to comment.