Skip to content

Commit

Permalink
feat: add options for placeholders (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
rusandorx authored Oct 27, 2023
1 parent 11ae392 commit ef6711e
Show file tree
Hide file tree
Showing 19 changed files with 129 additions and 34 deletions.
7 changes: 3 additions & 4 deletions src/extensions/base/BaseSchema/BaseSchemaSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ export enum BaseNode {
export const pType = nodeTypeFactory(BaseNode.Paragraph);

export type BaseSchemaSpecsOptions = {
// This cannot be passed through placeholder option of BehaviorPreset because BasePreset initializes first
paragraphPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
};

export const BaseSchemaSpecs: ExtensionAuto<BaseSchemaSpecsOptions> = (builder, opts) => {
const {paragraphPlaceholder} = opts;

builder
.addNode(BaseNode.Doc, () => ({
spec: {
Expand Down Expand Up @@ -45,9 +44,9 @@ export const BaseSchemaSpecs: ExtensionAuto<BaseSchemaSpecsOptions> = (builder,
toDOM() {
return ['p', 0];
},
placeholder: paragraphPlaceholder
placeholder: opts.paragraphPlaceholder
? {
content: paragraphPlaceholder,
content: opts.paragraphPlaceholder,
alwaysVisible: false,
}
: undefined,
Expand Down
14 changes: 12 additions & 2 deletions src/extensions/behavior/Placeholder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {cn} from '../../../classname';
import type {ExtensionAuto} from '../../../core';
import {isNodeEmpty} from '../../../utils/nodes';
import {isTextSelection} from '../../../utils/selection';
import {getPlaceholderContent} from '../../../utils/placeholder';
import {getPlaceholderContent, PlaceholderOptions} from '../../../utils/placeholder';

import './index.scss';

Expand Down Expand Up @@ -97,7 +97,8 @@ type WidgetsMap = Record<number, WidgetSpec | PluginKey>;

const pluginKey = new PluginKey<PlaceholderPluginState>('placeholder_plugin');

export const Placeholder: ExtensionAuto = (builder) => {
export const Placeholder: ExtensionAuto<PlaceholderOptions> = (builder, opts) => {
builder.context.set('placeholder', opts);
builder.addPlugin(
() =>
new Plugin<PlaceholderPluginState>({
Expand All @@ -120,6 +121,7 @@ export const Placeholder: ExtensionAuto = (builder) => {
apply: applyState,
},
}),
builder.Priority.VeryHigh,
);
};

Expand Down Expand Up @@ -231,3 +233,11 @@ declare module 'prosemirror-model' {
};
}
}

declare global {
namespace YfmEditor {
interface Context {
placeholder: PlaceholderOptions;
}
}
}
4 changes: 3 additions & 1 deletion src/extensions/behavior/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {Cursor, CursorOptions} from './Cursor';
import {History, HistoryOptions} from './History';
import {Clipboard, ClipboardOptions} from './Clipboard';
import {ReactRendererExtension, ReactRenderer} from './ReactRenderer';
import {PlaceholderOptions} from '../../utils/placeholder';

export * from './Cursor';
export * from './History';
Expand All @@ -18,13 +19,14 @@ export type BehaviorPresetOptions = {
cursor?: CursorOptions;
history?: HistoryOptions;
clipboard?: ClipboardOptions;
placeholder?: PlaceholderOptions;
reactRenderer: ReactRenderer;
};

export const BehaviorPreset: ExtensionAuto<BehaviorPresetOptions> = (builder, opts) => {
builder
.use(Selection)
.use(Placeholder)
.use(Placeholder, opts.placeholder ?? {})
.use(Cursor, opts.cursor ?? {})
.use(History, opts.history ?? {})
.use(Clipboard, opts.clipboard ?? {})
Expand Down
8 changes: 7 additions & 1 deletion src/extensions/markdown/Deflist/DeflistSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ export const defTermType = nodeTypeFactory(DeflistNode.Term);
export const defDescType = nodeTypeFactory(DeflistNode.Desc);

export type DeflistSpecsOptions = {
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
deflistTermPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
deflistDescPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
};

export const DeflistSpecs: ExtensionAuto<DeflistSpecsOptions> = (builder, opts) => {
const spec = getSpec(opts);
const spec = getSpec(opts, builder.context.get('placeholder'));

builder.configureMd((md) => md.use(deflistPlugin));
builder
Expand Down
16 changes: 13 additions & 3 deletions src/extensions/markdown/Deflist/DeflistSpecs/spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import type {NodeSpec} from 'prosemirror-model';
import type {DeflistSpecsOptions} from './index';
import {DeflistNode} from './const';
import {PlaceholderOptions} from '../../../../utils/placeholder';

const DEFAULT_PLACEHOLDERS = {
Term: 'Definition term',
Desc: 'Definition description',
};

export const getSpec = (opts?: DeflistSpecsOptions): Record<DeflistNode, NodeSpec> => ({
export const getSpec = (
opts?: DeflistSpecsOptions,
placeholder?: PlaceholderOptions,
): Record<DeflistNode, NodeSpec> => ({
[DeflistNode.List]: {
group: 'block',
content: `(${DeflistNode.Term} ${DeflistNode.Desc})+`,
Expand All @@ -29,7 +33,10 @@ export const getSpec = (opts?: DeflistSpecsOptions): Record<DeflistNode, NodeSpe
return ['dt', 0];
},
placeholder: {
content: opts?.deflistTermPlaceholder ?? DEFAULT_PLACEHOLDERS.Term,
content:
placeholder?.[DeflistNode.Term] ??
opts?.deflistTermPlaceholder ??
DEFAULT_PLACEHOLDERS.Term,
alwaysVisible: true,
},
selectable: false,
Expand All @@ -46,7 +53,10 @@ export const getSpec = (opts?: DeflistSpecsOptions): Record<DeflistNode, NodeSpe
return ['dd', 0];
},
placeholder: {
content: opts?.deflistDescPlaceholder ?? DEFAULT_PLACEHOLDERS.Desc,
content:
placeholder?.[DeflistNode.Desc] ??
opts?.deflistDescPlaceholder ??
DEFAULT_PLACEHOLDERS.Desc,
alwaysVisible: true,
},
selectable: false,
Expand Down
10 changes: 7 additions & 3 deletions src/extensions/markdown/Heading/HeadingSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ export const headingType = nodeTypeFactory(headingNodeName);
const DEFAULT_PLACEHOLDER = (node: Node) => 'Heading ' + node.attrs[headingLevelAttr];

export type HeadingSpecsOptions = {
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
headingPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
};

export const HeadingSpecs: ExtensionAuto<HeadingSpecsOptions> = (builder, opts) => {
const {headingPlaceholder} = opts ?? {};

builder.addNode(headingNodeName, () => ({
spec: {
attrs: {[headingLevelAttr]: {default: 1}},
Expand All @@ -33,7 +34,10 @@ export const HeadingSpecs: ExtensionAuto<HeadingSpecsOptions> = (builder, opts)
return ['h' + node.attrs[headingLevelAttr], 0];
},
placeholder: {
content: headingPlaceholder ?? DEFAULT_PLACEHOLDER,
content:
builder.context.get('placeholder')?.heading ??
opts.headingPlaceholder ??
DEFAULT_PLACEHOLDER,
alwaysVisible: true,
},
},
Expand Down
5 changes: 4 additions & 1 deletion src/extensions/yfm/Checkbox/CheckboxSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ export const checkboxLabelType = nodeTypeFactory(CheckboxNode.Label);
export const checkboxInputType = nodeTypeFactory(CheckboxNode.Input);

export type CheckboxSpecsOptions = {
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
checkboxLabelPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
inputView?: YENodeSpec['view'];
labelView?: YENodeSpec['view'];
checkboxView?: YENodeSpec['view'];
};

export const CheckboxSpecs: ExtensionAuto<CheckboxSpecsOptions> = (builder, opts) => {
const spec = getSpec(opts);
const spec = getSpec(opts, builder.context.get('placeholder'));

builder
.configureMd((md) => checkboxPlugin(md, {idPrefix, divClass: b()}))
Expand Down
7 changes: 6 additions & 1 deletion src/extensions/yfm/Checkbox/CheckboxSpecs/spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type {NodeSpec} from 'prosemirror-model';
import type {CheckboxSpecsOptions} from './index';
import {b, CheckboxNode} from '../const';
import {PlaceholderOptions} from '../../../../utils/placeholder';

const DEFAULT_LABEL_PLACEHOLDER = 'Checkbox';

export const getSpec = (
opts?: Pick<CheckboxSpecsOptions, 'checkboxLabelPlaceholder'>,
placeholder?: PlaceholderOptions,
): Record<CheckboxNode, NodeSpec> => ({
[CheckboxNode.Checkbox]: {
group: 'block',
Expand Down Expand Up @@ -54,7 +56,10 @@ export const getSpec = (
},
escapeText: false,
placeholder: {
content: opts?.checkboxLabelPlaceholder ?? DEFAULT_LABEL_PLACEHOLDER,
content:
placeholder?.[CheckboxNode.Label] ??
opts?.checkboxLabelPlaceholder ??
DEFAULT_LABEL_PLACEHOLDER,
alwaysVisible: true,
},
toDOM(node) {
Expand Down
7 changes: 6 additions & 1 deletion src/extensions/yfm/ImgSize/ImgSizeSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ type ImsizeTypedAttributes = {
export {ImgSizeAttr};

export type ImgSizeSpecsOptions = {
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
placeholder?: NodeSpec['placeholder'];
};

export const ImgSizeSpecs: ExtensionAuto<ImgSizeSpecsOptions> = (builder, opts) => {
const placeholderContent = builder.context.get('placeholder')?.imgSize;

builder.configureMd((md) => md.use(imsize, {log}));
builder.addNode(imageNodeName, () => ({
spec: {
Expand All @@ -33,7 +38,7 @@ export const ImgSizeSpecs: ExtensionAuto<ImgSizeSpecsOptions> = (builder, opts)
[ImgSizeAttr.Height]: {default: null},
[ImgSizeAttr.Width]: {default: null},
},
placeholder: opts.placeholder,
placeholder: placeholderContent ? {content: placeholderContent} : opts.placeholder,
group: 'inline',
draggable: true,
parseDOM: [
Expand Down
8 changes: 7 additions & 1 deletion src/extensions/yfm/YfmCut/YfmCutSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ export type YfmCutSpecsOptions = {
cutView?: YENodeSpec['view'];
cutTitleView?: YENodeSpec['view'];
cutContentView?: YENodeSpec['view'];
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
yfmCutTitlePlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
yfmCutContentPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
};

export const YfmCutSpecs: ExtensionAuto<YfmCutSpecsOptions> = (builder, opts) => {
const spec = getSpec(opts);
const spec = getSpec(opts, builder.context.get('placeholder'));

builder
.configureMd((md) => md.use(yfmPlugin, {log}))
Expand Down
16 changes: 13 additions & 3 deletions src/extensions/yfm/YfmCut/YfmCutSpecs/spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import type {NodeSpec} from 'prosemirror-model';
import type {YfmCutSpecsOptions} from './index';
import {CutNode} from '../const';
import {PlaceholderOptions} from '../../../../utils/placeholder';

const DEFAULT_PLACEHOLDERS = {
Title: 'Cut title',
Content: 'Cut content',
};

export const getSpec = (opts?: YfmCutSpecsOptions): Record<CutNode, NodeSpec> => ({
export const getSpec = (
opts?: YfmCutSpecsOptions,
placeholder?: PlaceholderOptions,
): Record<CutNode, NodeSpec> => ({
[CutNode.Cut]: {
attrs: {class: {default: 'yfm-cut'}},
content: `${CutNode.CutTitle} ${CutNode.CutContent}`,
Expand All @@ -31,7 +35,10 @@ export const getSpec = (opts?: YfmCutSpecsOptions): Record<CutNode, NodeSpec> =>
return ['div', node.attrs, 0];
},
placeholder: {
content: opts?.yfmCutTitlePlaceholder ?? DEFAULT_PLACEHOLDERS.Title,
content:
placeholder?.[CutNode.CutTitle] ??
opts?.yfmCutTitlePlaceholder ??
DEFAULT_PLACEHOLDERS.Title,
alwaysVisible: true,
},
selectable: false,
Expand All @@ -48,7 +55,10 @@ export const getSpec = (opts?: YfmCutSpecsOptions): Record<CutNode, NodeSpec> =>
return ['div', node.attrs, 0];
},
placeholder: {
content: opts?.yfmCutContentPlaceholder ?? DEFAULT_PLACEHOLDERS.Content,
content:
placeholder?.[CutNode.CutContent] ??
opts?.yfmCutContentPlaceholder ??
DEFAULT_PLACEHOLDERS.Content,
alwaysVisible: true,
},
selectable: false,
Expand Down
10 changes: 7 additions & 3 deletions src/extensions/yfm/YfmHeading/YfmHeadingSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ const DEFAULT_PLACEHOLDER = (node: Node) => 'Heading ' + node.attrs[YfmHeadingAt
export {YfmHeadingAttr} from './const';

export type YfmHeadingSpecsOptions = {
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
headingPlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
};

/** YfmHeading extension needs markdown-it-attrs plugin */
export const YfmHeadingSpecs: ExtensionAuto<YfmHeadingSpecsOptions> = (builder, opts) => {
const {headingPlaceholder} = opts ?? {};

builder.addNode(headingNodeName, () => ({
spec: {
attrs: {
Expand Down Expand Up @@ -52,7 +53,10 @@ export const YfmHeadingSpecs: ExtensionAuto<YfmHeadingSpecsOptions> = (builder,
];
},
placeholder: {
content: headingPlaceholder ?? DEFAULT_PLACEHOLDER,
content:
builder.context.get('placeholder')?.heading ??
opts.headingPlaceholder ??
DEFAULT_PLACEHOLDER,
alwaysVisible: true,
},
},
Expand Down
5 changes: 4 additions & 1 deletion src/extensions/yfm/YfmNote/YfmNoteSpecs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ export {NoteNode as YfmNoteNode} from './const';
export {noteType, noteTitleType} from './utils';

export type YfmNoteSpecsOptions = {
/**
* @deprecated: use placeholder option in BehaviorPreset instead.
*/
yfmNoteTitlePlaceholder?: NonNullable<NodeSpec['placeholder']>['content'];
};

export const YfmNoteSpecs: ExtensionAuto<YfmNoteSpecsOptions> = (builder, opts) => {
const spec = getSpec(opts);
const spec = getSpec(opts, builder.context.get('placeholder'));

builder
.configureMd((md) => md.use(yfmPlugin, {log}))
Expand Down
11 changes: 9 additions & 2 deletions src/extensions/yfm/YfmNote/YfmNoteSpecs/spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type {NodeSpec} from 'prosemirror-model';
import {YfmNoteSpecsOptions} from './index';
import {NoteAttrs, NoteNode} from './const';
import {PlaceholderOptions} from '../../../../utils/placeholder';

const DEFAULT_TITLE_PLACEHOLDER = 'Note';

export const getSpec = (opts?: YfmNoteSpecsOptions): Record<NoteNode, NodeSpec> => ({
export const getSpec = (
opts?: YfmNoteSpecsOptions,
placeholder?: PlaceholderOptions,
): Record<NoteNode, NodeSpec> => ({
[NoteNode.Note]: {
attrs: {
[NoteAttrs.Class]: {default: 'yfm-note yfm-accent-info'},
Expand Down Expand Up @@ -45,7 +49,10 @@ export const getSpec = (opts?: YfmNoteSpecsOptions): Record<NoteNode, NodeSpec>
selectable: false,
allowSelection: false,
placeholder: {
content: opts?.yfmNoteTitlePlaceholder ?? DEFAULT_TITLE_PLACEHOLDER,
content:
placeholder?.[NoteNode.NoteTitle] ??
opts?.yfmNoteTitlePlaceholder ??
DEFAULT_TITLE_PLACEHOLDER,
alwaysVisible: true,
},
complex: 'leaf',
Expand Down
Loading

0 comments on commit ef6711e

Please sign in to comment.