@kedataindo/docflow-plugins 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,9 +39,14 @@ __export(index_exports, {
39
39
  CiteEngine: () => CiteEngine,
40
40
  CommentMarkExtension: () => CommentMark,
41
41
  DEFAULT_CSL_STYLE: () => DEFAULT_CSL_STYLE,
42
+ DateChipNode: () => DateChipNode,
43
+ DropdownChipNode: () => DropdownChipNode,
44
+ FileChipNode: () => FileChipNode,
42
45
  FontSizeExtension: () => import_docflow_core14.FontSizeExtension,
43
46
  FootnoteNode: () => FootnoteNode,
47
+ LocationChipNode: () => LocationChipNode,
44
48
  PageBreak: () => PageBreak,
49
+ PeopleChipNode: () => PeopleChipNode,
45
50
  SlashMenuExtension: () => SlashMenuExtension,
46
51
  TocEntryNode: () => TocEntryNode,
47
52
  TocNode: () => TocNode,
@@ -76,6 +81,7 @@ __export(index_exports, {
76
81
  sanitizeCiteprocHtml: () => sanitizeCiteprocHtml,
77
82
  slashMenuPlugin: () => slashMenuPlugin,
78
83
  slashState: () => slashState,
84
+ smartElementsPlugin: () => smartElementsPlugin,
79
85
  tablePlugin: () => tablePlugin,
80
86
  textColorPlugin: () => textColorPlugin,
81
87
  tocPlugin: () => tocPlugin
@@ -2723,10 +2729,440 @@ var commentPlugin = {
2723
2729
  commands: {}
2724
2730
  };
2725
2731
 
2726
- // src/slashMenu.ts
2732
+ // src/smartElements.ts
2727
2733
  var import_core10 = require("@tiptap/core");
2728
- var import_state2 = require("@tiptap/pm/state");
2729
2734
  var import_docflow_core19 = require("@kedataindo/docflow-core");
2735
+ function todayISO() {
2736
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2737
+ }
2738
+ function formatDate(iso) {
2739
+ if (!iso) return "Pick a date";
2740
+ const d = /* @__PURE__ */ new Date(iso + "T00:00:00");
2741
+ if (isNaN(d.getTime())) return "Pick a date";
2742
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
2743
+ }
2744
+ function initialOf(name) {
2745
+ return (name || "?").trim().charAt(0).toUpperCase() || "?";
2746
+ }
2747
+ var DEFAULT_STATUSES = ["To Do", "In Progress", "Review", "Approved"];
2748
+ function slugify(s) {
2749
+ return s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2750
+ }
2751
+ var DateChipNode = import_core10.Node.create({
2752
+ name: "dateChip",
2753
+ group: "inline",
2754
+ inline: true,
2755
+ selectable: true,
2756
+ draggable: false,
2757
+ atom: true,
2758
+ addAttributes() {
2759
+ return {
2760
+ date: {
2761
+ default: todayISO(),
2762
+ parseHTML: (el) => el.getAttribute("data-date") ?? todayISO(),
2763
+ renderHTML: (attrs) => ({ "data-date": attrs.date ?? "" })
2764
+ }
2765
+ };
2766
+ },
2767
+ parseHTML() {
2768
+ return [{ tag: 'span[data-node-type="date-chip"]' }];
2769
+ },
2770
+ renderHTML({ node, HTMLAttributes }) {
2771
+ return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
2772
+ "data-node-type": "date-chip",
2773
+ class: "docs-chip docs-chip--date"
2774
+ }), formatDate(node.attrs.date)];
2775
+ },
2776
+ addNodeView() {
2777
+ return (props) => {
2778
+ let currentNode = props.node;
2779
+ const getPos = props.getPos;
2780
+ const view = props.view;
2781
+ const dom = document.createElement("span");
2782
+ dom.className = "docs-chip docs-chip--date";
2783
+ dom.setAttribute("data-node-type", "date-chip");
2784
+ dom.setAttribute("contenteditable", "false");
2785
+ const render = () => {
2786
+ if (dom.querySelector("input")) return;
2787
+ dom.textContent = formatDate(currentNode.attrs.date);
2788
+ dom.setAttribute("data-date", currentNode.attrs.date);
2789
+ };
2790
+ render();
2791
+ dom.addEventListener("click", (e) => {
2792
+ e.preventDefault();
2793
+ e.stopPropagation();
2794
+ if (dom.querySelector("input")) return;
2795
+ const input = document.createElement("input");
2796
+ input.type = "date";
2797
+ input.value = currentNode.attrs.date;
2798
+ input.className = "docs-chip-date-input";
2799
+ dom.replaceChildren(input);
2800
+ input.focus();
2801
+ const commit = () => {
2802
+ const newVal = input.value;
2803
+ const pos = getPos();
2804
+ if (typeof pos === "number") {
2805
+ view.dispatch(view.state.tr.setNodeMarkup(pos, void 0, { date: newVal }));
2806
+ }
2807
+ setTimeout(render, 30);
2808
+ };
2809
+ input.addEventListener("change", commit);
2810
+ input.addEventListener("blur", () => setTimeout(render, 30));
2811
+ });
2812
+ return {
2813
+ dom,
2814
+ update(updatedNode) {
2815
+ if (updatedNode.type.name !== "dateChip") return false;
2816
+ currentNode = updatedNode;
2817
+ render();
2818
+ return true;
2819
+ }
2820
+ };
2821
+ };
2822
+ }
2823
+ });
2824
+ var PeopleChipNode = import_core10.Node.create({
2825
+ name: "peopleChip",
2826
+ group: "inline",
2827
+ inline: true,
2828
+ selectable: true,
2829
+ draggable: false,
2830
+ atom: true,
2831
+ addAttributes() {
2832
+ return {
2833
+ userId: {
2834
+ default: "",
2835
+ parseHTML: (el) => el.getAttribute("data-user-id") ?? "",
2836
+ renderHTML: (attrs) => attrs.userId ? { "data-user-id": attrs.userId } : {}
2837
+ },
2838
+ name: {
2839
+ default: "",
2840
+ parseHTML: (el) => el.getAttribute("data-name") ?? "",
2841
+ renderHTML: (attrs) => ({ "data-name": attrs.name ?? "" })
2842
+ }
2843
+ };
2844
+ },
2845
+ parseHTML() {
2846
+ return [{ tag: 'span[data-node-type="people-chip"]' }];
2847
+ },
2848
+ renderHTML({ node, HTMLAttributes }) {
2849
+ return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
2850
+ "data-node-type": "people-chip",
2851
+ class: "docs-chip docs-chip--people"
2852
+ }), `@${node.attrs.name || "user"}`];
2853
+ },
2854
+ addNodeView() {
2855
+ return (props) => {
2856
+ let currentNode = props.node;
2857
+ const dom = document.createElement("span");
2858
+ dom.className = "docs-chip docs-chip--people";
2859
+ dom.setAttribute("data-node-type", "people-chip");
2860
+ dom.setAttribute("contenteditable", "false");
2861
+ const render = () => {
2862
+ const name = currentNode.attrs.name || "user";
2863
+ dom.innerHTML = "";
2864
+ const avatar = document.createElement("span");
2865
+ avatar.className = "docs-chip-avatar";
2866
+ avatar.textContent = initialOf(name);
2867
+ dom.appendChild(avatar);
2868
+ const label = document.createElement("span");
2869
+ label.className = "docs-chip-label";
2870
+ label.textContent = "@" + name;
2871
+ dom.appendChild(label);
2872
+ };
2873
+ render();
2874
+ return {
2875
+ dom,
2876
+ update(updatedNode) {
2877
+ if (updatedNode.type.name !== "peopleChip") return false;
2878
+ currentNode = updatedNode;
2879
+ render();
2880
+ return true;
2881
+ }
2882
+ };
2883
+ };
2884
+ }
2885
+ });
2886
+ var FileChipNode = import_core10.Node.create({
2887
+ name: "fileChip",
2888
+ group: "inline",
2889
+ inline: true,
2890
+ selectable: true,
2891
+ draggable: false,
2892
+ atom: true,
2893
+ addAttributes() {
2894
+ return {
2895
+ fileId: {
2896
+ default: "",
2897
+ parseHTML: (el) => el.getAttribute("data-file-id") ?? "",
2898
+ renderHTML: (attrs) => attrs.fileId ? { "data-file-id": attrs.fileId } : {}
2899
+ },
2900
+ name: {
2901
+ default: "",
2902
+ parseHTML: (el) => el.getAttribute("data-name") ?? "",
2903
+ renderHTML: (attrs) => ({ "data-name": attrs.name ?? "" })
2904
+ }
2905
+ };
2906
+ },
2907
+ parseHTML() {
2908
+ return [{ tag: 'span[data-node-type="file-chip"]' }];
2909
+ },
2910
+ renderHTML({ node, HTMLAttributes }) {
2911
+ return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
2912
+ "data-node-type": "file-chip",
2913
+ class: "docs-chip docs-chip--file"
2914
+ }), node.attrs.name || "file"];
2915
+ },
2916
+ addNodeView() {
2917
+ return (props) => {
2918
+ let currentNode = props.node;
2919
+ const dom = document.createElement("span");
2920
+ dom.className = "docs-chip docs-chip--file";
2921
+ dom.setAttribute("data-node-type", "file-chip");
2922
+ dom.setAttribute("contenteditable", "false");
2923
+ const render = () => {
2924
+ const name = currentNode.attrs.name || "Untitled file";
2925
+ dom.innerHTML = "";
2926
+ const icon = document.createElement("span");
2927
+ icon.className = "docs-chip-icon";
2928
+ icon.textContent = "\u{1F5CE}";
2929
+ dom.appendChild(icon);
2930
+ const label = document.createElement("span");
2931
+ label.className = "docs-chip-label";
2932
+ label.textContent = name;
2933
+ dom.appendChild(label);
2934
+ };
2935
+ render();
2936
+ return {
2937
+ dom,
2938
+ update(updatedNode) {
2939
+ if (updatedNode.type.name !== "fileChip") return false;
2940
+ currentNode = updatedNode;
2941
+ render();
2942
+ return true;
2943
+ }
2944
+ };
2945
+ };
2946
+ }
2947
+ });
2948
+ var DropdownChipNode = import_core10.Node.create({
2949
+ name: "dropdownChip",
2950
+ group: "inline",
2951
+ inline: true,
2952
+ selectable: true,
2953
+ draggable: false,
2954
+ atom: true,
2955
+ addAttributes() {
2956
+ return {
2957
+ options: {
2958
+ default: DEFAULT_STATUSES,
2959
+ parseHTML: (el) => {
2960
+ const raw = el.getAttribute("data-options") ?? "";
2961
+ const list = raw ? raw.split("|") : [];
2962
+ return list.length ? list : DEFAULT_STATUSES;
2963
+ },
2964
+ renderHTML: (attrs) => ({
2965
+ "data-options": (attrs.options ?? []).join("|")
2966
+ })
2967
+ },
2968
+ selected: {
2969
+ default: DEFAULT_STATUSES[0],
2970
+ parseHTML: (el) => el.getAttribute("data-selected") ?? DEFAULT_STATUSES[0],
2971
+ renderHTML: (attrs) => ({ "data-selected": attrs.selected ?? "" })
2972
+ }
2973
+ };
2974
+ },
2975
+ parseHTML() {
2976
+ return [{ tag: 'span[data-node-type="dropdown-chip"]' }];
2977
+ },
2978
+ renderHTML({ node, HTMLAttributes }) {
2979
+ return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
2980
+ "data-node-type": "dropdown-chip",
2981
+ class: "docs-chip docs-chip--dropdown"
2982
+ }), node.attrs.selected || ""];
2983
+ },
2984
+ addNodeView() {
2985
+ return (props) => {
2986
+ let currentNode = props.node;
2987
+ const getPos = props.getPos;
2988
+ const view = props.view;
2989
+ const dom = document.createElement("span");
2990
+ dom.className = "docs-chip docs-chip--dropdown";
2991
+ dom.setAttribute("data-node-type", "dropdown-chip");
2992
+ dom.setAttribute("contenteditable", "false");
2993
+ const render = () => {
2994
+ const opts = currentNode.attrs.options ?? DEFAULT_STATUSES;
2995
+ const sel = currentNode.attrs.selected ?? (opts[0] ?? "");
2996
+ dom.innerHTML = "";
2997
+ const select = document.createElement("select");
2998
+ select.className = "docs-chip-select";
2999
+ select.addEventListener("mousedown", (e) => e.stopPropagation());
3000
+ for (const opt of opts) {
3001
+ const o = document.createElement("option");
3002
+ o.value = opt;
3003
+ o.textContent = opt;
3004
+ if (opt === sel) o.selected = true;
3005
+ select.appendChild(o);
3006
+ }
3007
+ select.addEventListener("change", () => {
3008
+ const pos = getPos();
3009
+ if (typeof pos === "number") {
3010
+ view.dispatch(view.state.tr.setNodeMarkup(pos, void 0, {
3011
+ ...currentNode.attrs,
3012
+ selected: select.value
3013
+ }));
3014
+ }
3015
+ });
3016
+ dom.appendChild(select);
3017
+ };
3018
+ render();
3019
+ return {
3020
+ dom,
3021
+ update(updatedNode) {
3022
+ if (updatedNode.type.name !== "dropdownChip") return false;
3023
+ currentNode = updatedNode;
3024
+ render();
3025
+ return true;
3026
+ }
3027
+ };
3028
+ };
3029
+ }
3030
+ });
3031
+ var LocationChipNode = import_core10.Node.create({
3032
+ name: "locationChip",
3033
+ group: "inline",
3034
+ inline: true,
3035
+ selectable: true,
3036
+ draggable: false,
3037
+ atom: true,
3038
+ addAttributes() {
3039
+ return {
3040
+ label: {
3041
+ default: "",
3042
+ parseHTML: (el) => el.getAttribute("data-label") ?? "",
3043
+ renderHTML: (attrs) => ({ "data-label": attrs.label ?? "" })
3044
+ },
3045
+ lat: {
3046
+ default: null,
3047
+ parseHTML: (el) => {
3048
+ const v = el.getAttribute("data-lat");
3049
+ return v === null ? null : Number(v);
3050
+ },
3051
+ renderHTML: (attrs) => attrs.lat != null ? { "data-lat": attrs.lat } : {}
3052
+ },
3053
+ lng: {
3054
+ default: null,
3055
+ parseHTML: (el) => {
3056
+ const v = el.getAttribute("data-lng");
3057
+ return v === null ? null : Number(v);
3058
+ },
3059
+ renderHTML: (attrs) => attrs.lng != null ? { "data-lng": attrs.lng } : {}
3060
+ }
3061
+ };
3062
+ },
3063
+ parseHTML() {
3064
+ return [{ tag: 'span[data-node-type="location-chip"]' }];
3065
+ },
3066
+ renderHTML({ node, HTMLAttributes }) {
3067
+ return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
3068
+ "data-node-type": "location-chip",
3069
+ class: "docs-chip docs-chip--location"
3070
+ }), node.attrs.label || ""];
3071
+ },
3072
+ addNodeView() {
3073
+ return (props) => {
3074
+ let currentNode = props.node;
3075
+ const dom = document.createElement("span");
3076
+ dom.className = "docs-chip docs-chip--location";
3077
+ dom.setAttribute("data-node-type", "location-chip");
3078
+ dom.setAttribute("contenteditable", "false");
3079
+ const render = () => {
3080
+ const label = currentNode.attrs.label || "Add location";
3081
+ dom.innerHTML = "";
3082
+ const icon = document.createElement("span");
3083
+ icon.className = "docs-chip-icon";
3084
+ icon.textContent = "\u{1F4CD}";
3085
+ dom.appendChild(icon);
3086
+ const lbl = document.createElement("span");
3087
+ lbl.className = "docs-chip-label";
3088
+ lbl.textContent = label;
3089
+ dom.appendChild(lbl);
3090
+ };
3091
+ render();
3092
+ return {
3093
+ dom,
3094
+ update(updatedNode) {
3095
+ if (updatedNode.type.name !== "locationChip") return false;
3096
+ currentNode = updatedNode;
3097
+ render();
3098
+ return true;
3099
+ }
3100
+ };
3101
+ };
3102
+ }
3103
+ });
3104
+ var smartElementsPlugin = (0, import_docflow_core19.definePlugin)({
3105
+ id: "smart-elements",
3106
+ tiptapExtensions: [
3107
+ DateChipNode,
3108
+ PeopleChipNode,
3109
+ FileChipNode,
3110
+ DropdownChipNode,
3111
+ LocationChipNode
3112
+ ],
3113
+ slashCommands: [
3114
+ { name: "Date", command: "insertDateChip" },
3115
+ { name: "People", command: "insertPeopleChip" },
3116
+ { name: "File", command: "insertFileChip" },
3117
+ { name: "Dropdown", command: "insertDropdownChip" },
3118
+ { name: "Location", command: "insertLocationChip" }
3119
+ ],
3120
+ commands: {
3121
+ insertDateChip: (editor) => {
3122
+ return editor.chain().focus().insertContent({
3123
+ type: "dateChip",
3124
+ attrs: { date: todayISO() }
3125
+ }).run();
3126
+ },
3127
+ insertPeopleChip: (editor) => {
3128
+ const name = typeof window !== "undefined" && typeof window.prompt === "function" ? window.prompt("Enter user name:", "") ?? "" : "";
3129
+ if (!name.trim()) return false;
3130
+ const trimmed = name.trim();
3131
+ return editor.chain().focus().insertContent({
3132
+ type: "peopleChip",
3133
+ attrs: { userId: slugify(trimmed), name: trimmed }
3134
+ }).run();
3135
+ },
3136
+ insertFileChip: (editor) => {
3137
+ const name = typeof window !== "undefined" && typeof window.prompt === "function" ? window.prompt("Enter file/document name:", "") ?? "" : "";
3138
+ if (!name.trim()) return false;
3139
+ const trimmed = name.trim();
3140
+ return editor.chain().focus().insertContent({
3141
+ type: "fileChip",
3142
+ attrs: { fileId: slugify(trimmed), name: trimmed }
3143
+ }).run();
3144
+ },
3145
+ insertDropdownChip: (editor) => {
3146
+ return editor.chain().focus().insertContent({
3147
+ type: "dropdownChip",
3148
+ attrs: { options: DEFAULT_STATUSES, selected: DEFAULT_STATUSES[0] }
3149
+ }).run();
3150
+ },
3151
+ insertLocationChip: (editor) => {
3152
+ const label = typeof window !== "undefined" && typeof window.prompt === "function" ? window.prompt("Enter location name:", "") ?? "" : "";
3153
+ if (!label.trim()) return false;
3154
+ return editor.chain().focus().insertContent({
3155
+ type: "locationChip",
3156
+ attrs: { label: label.trim() }
3157
+ }).run();
3158
+ }
3159
+ }
3160
+ });
3161
+
3162
+ // src/slashMenu.ts
3163
+ var import_core11 = require("@tiptap/core");
3164
+ var import_state2 = require("@tiptap/pm/state");
3165
+ var import_docflow_core20 = require("@kedataindo/docflow-core");
2730
3166
  var slashState = {
2731
3167
  open: false,
2732
3168
  query: "",
@@ -2780,7 +3216,7 @@ function getRegisteredCommands() {
2780
3216
  return true;
2781
3217
  });
2782
3218
  }
2783
- var SlashMenuExtension = import_core10.Extension.create({
3219
+ var SlashMenuExtension = import_core11.Extension.create({
2784
3220
  name: "slashMenu",
2785
3221
  addProseMirrorPlugins() {
2786
3222
  return [
@@ -2871,7 +3307,7 @@ var SlashMenuExtension = import_core10.Extension.create({
2871
3307
  ];
2872
3308
  }
2873
3309
  });
2874
- var slashMenuPlugin = (0, import_docflow_core19.definePlugin)({
3310
+ var slashMenuPlugin = (0, import_docflow_core20.definePlugin)({
2875
3311
  id: "slash-menu",
2876
3312
  tiptapExtensions: [SlashMenuExtension],
2877
3313
  hooks: {
@@ -2902,7 +3338,8 @@ var defaultPlugins = [
2902
3338
  highlightPlugin,
2903
3339
  citationPlugin,
2904
3340
  aiPlugin,
2905
- commentPlugin
3341
+ commentPlugin,
3342
+ smartElementsPlugin
2906
3343
  ];
2907
3344
  // Annotate the CommonJS export names for ESM import in node:
2908
3345
  0 && (module.exports = {
@@ -2915,9 +3352,14 @@ var defaultPlugins = [
2915
3352
  CiteEngine,
2916
3353
  CommentMarkExtension,
2917
3354
  DEFAULT_CSL_STYLE,
3355
+ DateChipNode,
3356
+ DropdownChipNode,
3357
+ FileChipNode,
2918
3358
  FontSizeExtension,
2919
3359
  FootnoteNode,
3360
+ LocationChipNode,
2920
3361
  PageBreak,
3362
+ PeopleChipNode,
2921
3363
  SlashMenuExtension,
2922
3364
  TocEntryNode,
2923
3365
  TocNode,
@@ -2952,6 +3394,7 @@ var defaultPlugins = [
2952
3394
  sanitizeCiteprocHtml,
2953
3395
  slashMenuPlugin,
2954
3396
  slashState,
3397
+ smartElementsPlugin,
2955
3398
  tablePlugin,
2956
3399
  textColorPlugin,
2957
3400
  tocPlugin
package/dist/index.d.cts CHANGED
@@ -225,6 +225,13 @@ declare const CommentMark: Mark<any, any>;
225
225
  */
226
226
  declare const commentPlugin: DocsEditorPlugin;
227
227
 
228
+ declare const DateChipNode: Node<any, any>;
229
+ declare const PeopleChipNode: Node<any, any>;
230
+ declare const FileChipNode: Node<any, any>;
231
+ declare const DropdownChipNode: Node<any, any>;
232
+ declare const LocationChipNode: Node<any, any>;
233
+ declare const smartElementsPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
234
+
228
235
  /**
229
236
  * Bibliography — a fully derived block. It stores NOTHING (no attrs): the
230
237
  * node view renders the engine's citeproc-sorted entries and repaints on
@@ -257,4 +264,4 @@ declare const slashMenuPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
257
264
 
258
265
  declare const defaultPlugins: _kedata_indonesia_docflow_core.DocsEditorPlugin[];
259
266
 
260
- export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, FootnoteNode, PageBreak, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, linkPlugin, listsPlugin, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, tablePlugin, textColorPlugin, tocPlugin };
267
+ export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, DateChipNode, DropdownChipNode, FileChipNode, FootnoteNode, LocationChipNode, PageBreak, PeopleChipNode, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, linkPlugin, listsPlugin, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, smartElementsPlugin, tablePlugin, textColorPlugin, tocPlugin };
package/dist/index.d.ts CHANGED
@@ -225,6 +225,13 @@ declare const CommentMark: Mark<any, any>;
225
225
  */
226
226
  declare const commentPlugin: DocsEditorPlugin;
227
227
 
228
+ declare const DateChipNode: Node<any, any>;
229
+ declare const PeopleChipNode: Node<any, any>;
230
+ declare const FileChipNode: Node<any, any>;
231
+ declare const DropdownChipNode: Node<any, any>;
232
+ declare const LocationChipNode: Node<any, any>;
233
+ declare const smartElementsPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
234
+
228
235
  /**
229
236
  * Bibliography — a fully derived block. It stores NOTHING (no attrs): the
230
237
  * node view renders the engine's citeproc-sorted entries and repaints on
@@ -257,4 +264,4 @@ declare const slashMenuPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
257
264
 
258
265
  declare const defaultPlugins: _kedata_indonesia_docflow_core.DocsEditorPlugin[];
259
266
 
260
- export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, FootnoteNode, PageBreak, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, linkPlugin, listsPlugin, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, tablePlugin, textColorPlugin, tocPlugin };
267
+ export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, DateChipNode, DropdownChipNode, FileChipNode, FootnoteNode, LocationChipNode, PageBreak, PeopleChipNode, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, linkPlugin, listsPlugin, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, smartElementsPlugin, tablePlugin, textColorPlugin, tocPlugin };
package/dist/index.js CHANGED
@@ -1247,10 +1247,440 @@ var commentPlugin = {
1247
1247
  commands: {}
1248
1248
  };
1249
1249
 
1250
+ // src/smartElements.ts
1251
+ import { Node as Node5, mergeAttributes as mergeAttributes6 } from "@tiptap/core";
1252
+ import { definePlugin as definePlugin18 } from "@kedataindo/docflow-core";
1253
+ function todayISO() {
1254
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1255
+ }
1256
+ function formatDate(iso) {
1257
+ if (!iso) return "Pick a date";
1258
+ const d = /* @__PURE__ */ new Date(iso + "T00:00:00");
1259
+ if (isNaN(d.getTime())) return "Pick a date";
1260
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
1261
+ }
1262
+ function initialOf(name) {
1263
+ return (name || "?").trim().charAt(0).toUpperCase() || "?";
1264
+ }
1265
+ var DEFAULT_STATUSES = ["To Do", "In Progress", "Review", "Approved"];
1266
+ function slugify(s) {
1267
+ return s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1268
+ }
1269
+ var DateChipNode = Node5.create({
1270
+ name: "dateChip",
1271
+ group: "inline",
1272
+ inline: true,
1273
+ selectable: true,
1274
+ draggable: false,
1275
+ atom: true,
1276
+ addAttributes() {
1277
+ return {
1278
+ date: {
1279
+ default: todayISO(),
1280
+ parseHTML: (el) => el.getAttribute("data-date") ?? todayISO(),
1281
+ renderHTML: (attrs) => ({ "data-date": attrs.date ?? "" })
1282
+ }
1283
+ };
1284
+ },
1285
+ parseHTML() {
1286
+ return [{ tag: 'span[data-node-type="date-chip"]' }];
1287
+ },
1288
+ renderHTML({ node, HTMLAttributes }) {
1289
+ return ["span", mergeAttributes6(HTMLAttributes, {
1290
+ "data-node-type": "date-chip",
1291
+ class: "docs-chip docs-chip--date"
1292
+ }), formatDate(node.attrs.date)];
1293
+ },
1294
+ addNodeView() {
1295
+ return (props) => {
1296
+ let currentNode = props.node;
1297
+ const getPos = props.getPos;
1298
+ const view = props.view;
1299
+ const dom = document.createElement("span");
1300
+ dom.className = "docs-chip docs-chip--date";
1301
+ dom.setAttribute("data-node-type", "date-chip");
1302
+ dom.setAttribute("contenteditable", "false");
1303
+ const render = () => {
1304
+ if (dom.querySelector("input")) return;
1305
+ dom.textContent = formatDate(currentNode.attrs.date);
1306
+ dom.setAttribute("data-date", currentNode.attrs.date);
1307
+ };
1308
+ render();
1309
+ dom.addEventListener("click", (e) => {
1310
+ e.preventDefault();
1311
+ e.stopPropagation();
1312
+ if (dom.querySelector("input")) return;
1313
+ const input = document.createElement("input");
1314
+ input.type = "date";
1315
+ input.value = currentNode.attrs.date;
1316
+ input.className = "docs-chip-date-input";
1317
+ dom.replaceChildren(input);
1318
+ input.focus();
1319
+ const commit = () => {
1320
+ const newVal = input.value;
1321
+ const pos = getPos();
1322
+ if (typeof pos === "number") {
1323
+ view.dispatch(view.state.tr.setNodeMarkup(pos, void 0, { date: newVal }));
1324
+ }
1325
+ setTimeout(render, 30);
1326
+ };
1327
+ input.addEventListener("change", commit);
1328
+ input.addEventListener("blur", () => setTimeout(render, 30));
1329
+ });
1330
+ return {
1331
+ dom,
1332
+ update(updatedNode) {
1333
+ if (updatedNode.type.name !== "dateChip") return false;
1334
+ currentNode = updatedNode;
1335
+ render();
1336
+ return true;
1337
+ }
1338
+ };
1339
+ };
1340
+ }
1341
+ });
1342
+ var PeopleChipNode = Node5.create({
1343
+ name: "peopleChip",
1344
+ group: "inline",
1345
+ inline: true,
1346
+ selectable: true,
1347
+ draggable: false,
1348
+ atom: true,
1349
+ addAttributes() {
1350
+ return {
1351
+ userId: {
1352
+ default: "",
1353
+ parseHTML: (el) => el.getAttribute("data-user-id") ?? "",
1354
+ renderHTML: (attrs) => attrs.userId ? { "data-user-id": attrs.userId } : {}
1355
+ },
1356
+ name: {
1357
+ default: "",
1358
+ parseHTML: (el) => el.getAttribute("data-name") ?? "",
1359
+ renderHTML: (attrs) => ({ "data-name": attrs.name ?? "" })
1360
+ }
1361
+ };
1362
+ },
1363
+ parseHTML() {
1364
+ return [{ tag: 'span[data-node-type="people-chip"]' }];
1365
+ },
1366
+ renderHTML({ node, HTMLAttributes }) {
1367
+ return ["span", mergeAttributes6(HTMLAttributes, {
1368
+ "data-node-type": "people-chip",
1369
+ class: "docs-chip docs-chip--people"
1370
+ }), `@${node.attrs.name || "user"}`];
1371
+ },
1372
+ addNodeView() {
1373
+ return (props) => {
1374
+ let currentNode = props.node;
1375
+ const dom = document.createElement("span");
1376
+ dom.className = "docs-chip docs-chip--people";
1377
+ dom.setAttribute("data-node-type", "people-chip");
1378
+ dom.setAttribute("contenteditable", "false");
1379
+ const render = () => {
1380
+ const name = currentNode.attrs.name || "user";
1381
+ dom.innerHTML = "";
1382
+ const avatar = document.createElement("span");
1383
+ avatar.className = "docs-chip-avatar";
1384
+ avatar.textContent = initialOf(name);
1385
+ dom.appendChild(avatar);
1386
+ const label = document.createElement("span");
1387
+ label.className = "docs-chip-label";
1388
+ label.textContent = "@" + name;
1389
+ dom.appendChild(label);
1390
+ };
1391
+ render();
1392
+ return {
1393
+ dom,
1394
+ update(updatedNode) {
1395
+ if (updatedNode.type.name !== "peopleChip") return false;
1396
+ currentNode = updatedNode;
1397
+ render();
1398
+ return true;
1399
+ }
1400
+ };
1401
+ };
1402
+ }
1403
+ });
1404
+ var FileChipNode = Node5.create({
1405
+ name: "fileChip",
1406
+ group: "inline",
1407
+ inline: true,
1408
+ selectable: true,
1409
+ draggable: false,
1410
+ atom: true,
1411
+ addAttributes() {
1412
+ return {
1413
+ fileId: {
1414
+ default: "",
1415
+ parseHTML: (el) => el.getAttribute("data-file-id") ?? "",
1416
+ renderHTML: (attrs) => attrs.fileId ? { "data-file-id": attrs.fileId } : {}
1417
+ },
1418
+ name: {
1419
+ default: "",
1420
+ parseHTML: (el) => el.getAttribute("data-name") ?? "",
1421
+ renderHTML: (attrs) => ({ "data-name": attrs.name ?? "" })
1422
+ }
1423
+ };
1424
+ },
1425
+ parseHTML() {
1426
+ return [{ tag: 'span[data-node-type="file-chip"]' }];
1427
+ },
1428
+ renderHTML({ node, HTMLAttributes }) {
1429
+ return ["span", mergeAttributes6(HTMLAttributes, {
1430
+ "data-node-type": "file-chip",
1431
+ class: "docs-chip docs-chip--file"
1432
+ }), node.attrs.name || "file"];
1433
+ },
1434
+ addNodeView() {
1435
+ return (props) => {
1436
+ let currentNode = props.node;
1437
+ const dom = document.createElement("span");
1438
+ dom.className = "docs-chip docs-chip--file";
1439
+ dom.setAttribute("data-node-type", "file-chip");
1440
+ dom.setAttribute("contenteditable", "false");
1441
+ const render = () => {
1442
+ const name = currentNode.attrs.name || "Untitled file";
1443
+ dom.innerHTML = "";
1444
+ const icon = document.createElement("span");
1445
+ icon.className = "docs-chip-icon";
1446
+ icon.textContent = "\u{1F5CE}";
1447
+ dom.appendChild(icon);
1448
+ const label = document.createElement("span");
1449
+ label.className = "docs-chip-label";
1450
+ label.textContent = name;
1451
+ dom.appendChild(label);
1452
+ };
1453
+ render();
1454
+ return {
1455
+ dom,
1456
+ update(updatedNode) {
1457
+ if (updatedNode.type.name !== "fileChip") return false;
1458
+ currentNode = updatedNode;
1459
+ render();
1460
+ return true;
1461
+ }
1462
+ };
1463
+ };
1464
+ }
1465
+ });
1466
+ var DropdownChipNode = Node5.create({
1467
+ name: "dropdownChip",
1468
+ group: "inline",
1469
+ inline: true,
1470
+ selectable: true,
1471
+ draggable: false,
1472
+ atom: true,
1473
+ addAttributes() {
1474
+ return {
1475
+ options: {
1476
+ default: DEFAULT_STATUSES,
1477
+ parseHTML: (el) => {
1478
+ const raw = el.getAttribute("data-options") ?? "";
1479
+ const list = raw ? raw.split("|") : [];
1480
+ return list.length ? list : DEFAULT_STATUSES;
1481
+ },
1482
+ renderHTML: (attrs) => ({
1483
+ "data-options": (attrs.options ?? []).join("|")
1484
+ })
1485
+ },
1486
+ selected: {
1487
+ default: DEFAULT_STATUSES[0],
1488
+ parseHTML: (el) => el.getAttribute("data-selected") ?? DEFAULT_STATUSES[0],
1489
+ renderHTML: (attrs) => ({ "data-selected": attrs.selected ?? "" })
1490
+ }
1491
+ };
1492
+ },
1493
+ parseHTML() {
1494
+ return [{ tag: 'span[data-node-type="dropdown-chip"]' }];
1495
+ },
1496
+ renderHTML({ node, HTMLAttributes }) {
1497
+ return ["span", mergeAttributes6(HTMLAttributes, {
1498
+ "data-node-type": "dropdown-chip",
1499
+ class: "docs-chip docs-chip--dropdown"
1500
+ }), node.attrs.selected || ""];
1501
+ },
1502
+ addNodeView() {
1503
+ return (props) => {
1504
+ let currentNode = props.node;
1505
+ const getPos = props.getPos;
1506
+ const view = props.view;
1507
+ const dom = document.createElement("span");
1508
+ dom.className = "docs-chip docs-chip--dropdown";
1509
+ dom.setAttribute("data-node-type", "dropdown-chip");
1510
+ dom.setAttribute("contenteditable", "false");
1511
+ const render = () => {
1512
+ const opts = currentNode.attrs.options ?? DEFAULT_STATUSES;
1513
+ const sel = currentNode.attrs.selected ?? (opts[0] ?? "");
1514
+ dom.innerHTML = "";
1515
+ const select = document.createElement("select");
1516
+ select.className = "docs-chip-select";
1517
+ select.addEventListener("mousedown", (e) => e.stopPropagation());
1518
+ for (const opt of opts) {
1519
+ const o = document.createElement("option");
1520
+ o.value = opt;
1521
+ o.textContent = opt;
1522
+ if (opt === sel) o.selected = true;
1523
+ select.appendChild(o);
1524
+ }
1525
+ select.addEventListener("change", () => {
1526
+ const pos = getPos();
1527
+ if (typeof pos === "number") {
1528
+ view.dispatch(view.state.tr.setNodeMarkup(pos, void 0, {
1529
+ ...currentNode.attrs,
1530
+ selected: select.value
1531
+ }));
1532
+ }
1533
+ });
1534
+ dom.appendChild(select);
1535
+ };
1536
+ render();
1537
+ return {
1538
+ dom,
1539
+ update(updatedNode) {
1540
+ if (updatedNode.type.name !== "dropdownChip") return false;
1541
+ currentNode = updatedNode;
1542
+ render();
1543
+ return true;
1544
+ }
1545
+ };
1546
+ };
1547
+ }
1548
+ });
1549
+ var LocationChipNode = Node5.create({
1550
+ name: "locationChip",
1551
+ group: "inline",
1552
+ inline: true,
1553
+ selectable: true,
1554
+ draggable: false,
1555
+ atom: true,
1556
+ addAttributes() {
1557
+ return {
1558
+ label: {
1559
+ default: "",
1560
+ parseHTML: (el) => el.getAttribute("data-label") ?? "",
1561
+ renderHTML: (attrs) => ({ "data-label": attrs.label ?? "" })
1562
+ },
1563
+ lat: {
1564
+ default: null,
1565
+ parseHTML: (el) => {
1566
+ const v = el.getAttribute("data-lat");
1567
+ return v === null ? null : Number(v);
1568
+ },
1569
+ renderHTML: (attrs) => attrs.lat != null ? { "data-lat": attrs.lat } : {}
1570
+ },
1571
+ lng: {
1572
+ default: null,
1573
+ parseHTML: (el) => {
1574
+ const v = el.getAttribute("data-lng");
1575
+ return v === null ? null : Number(v);
1576
+ },
1577
+ renderHTML: (attrs) => attrs.lng != null ? { "data-lng": attrs.lng } : {}
1578
+ }
1579
+ };
1580
+ },
1581
+ parseHTML() {
1582
+ return [{ tag: 'span[data-node-type="location-chip"]' }];
1583
+ },
1584
+ renderHTML({ node, HTMLAttributes }) {
1585
+ return ["span", mergeAttributes6(HTMLAttributes, {
1586
+ "data-node-type": "location-chip",
1587
+ class: "docs-chip docs-chip--location"
1588
+ }), node.attrs.label || ""];
1589
+ },
1590
+ addNodeView() {
1591
+ return (props) => {
1592
+ let currentNode = props.node;
1593
+ const dom = document.createElement("span");
1594
+ dom.className = "docs-chip docs-chip--location";
1595
+ dom.setAttribute("data-node-type", "location-chip");
1596
+ dom.setAttribute("contenteditable", "false");
1597
+ const render = () => {
1598
+ const label = currentNode.attrs.label || "Add location";
1599
+ dom.innerHTML = "";
1600
+ const icon = document.createElement("span");
1601
+ icon.className = "docs-chip-icon";
1602
+ icon.textContent = "\u{1F4CD}";
1603
+ dom.appendChild(icon);
1604
+ const lbl = document.createElement("span");
1605
+ lbl.className = "docs-chip-label";
1606
+ lbl.textContent = label;
1607
+ dom.appendChild(lbl);
1608
+ };
1609
+ render();
1610
+ return {
1611
+ dom,
1612
+ update(updatedNode) {
1613
+ if (updatedNode.type.name !== "locationChip") return false;
1614
+ currentNode = updatedNode;
1615
+ render();
1616
+ return true;
1617
+ }
1618
+ };
1619
+ };
1620
+ }
1621
+ });
1622
+ var smartElementsPlugin = definePlugin18({
1623
+ id: "smart-elements",
1624
+ tiptapExtensions: [
1625
+ DateChipNode,
1626
+ PeopleChipNode,
1627
+ FileChipNode,
1628
+ DropdownChipNode,
1629
+ LocationChipNode
1630
+ ],
1631
+ slashCommands: [
1632
+ { name: "Date", command: "insertDateChip" },
1633
+ { name: "People", command: "insertPeopleChip" },
1634
+ { name: "File", command: "insertFileChip" },
1635
+ { name: "Dropdown", command: "insertDropdownChip" },
1636
+ { name: "Location", command: "insertLocationChip" }
1637
+ ],
1638
+ commands: {
1639
+ insertDateChip: (editor) => {
1640
+ return editor.chain().focus().insertContent({
1641
+ type: "dateChip",
1642
+ attrs: { date: todayISO() }
1643
+ }).run();
1644
+ },
1645
+ insertPeopleChip: (editor) => {
1646
+ const name = typeof window !== "undefined" && typeof window.prompt === "function" ? window.prompt("Enter user name:", "") ?? "" : "";
1647
+ if (!name.trim()) return false;
1648
+ const trimmed = name.trim();
1649
+ return editor.chain().focus().insertContent({
1650
+ type: "peopleChip",
1651
+ attrs: { userId: slugify(trimmed), name: trimmed }
1652
+ }).run();
1653
+ },
1654
+ insertFileChip: (editor) => {
1655
+ const name = typeof window !== "undefined" && typeof window.prompt === "function" ? window.prompt("Enter file/document name:", "") ?? "" : "";
1656
+ if (!name.trim()) return false;
1657
+ const trimmed = name.trim();
1658
+ return editor.chain().focus().insertContent({
1659
+ type: "fileChip",
1660
+ attrs: { fileId: slugify(trimmed), name: trimmed }
1661
+ }).run();
1662
+ },
1663
+ insertDropdownChip: (editor) => {
1664
+ return editor.chain().focus().insertContent({
1665
+ type: "dropdownChip",
1666
+ attrs: { options: DEFAULT_STATUSES, selected: DEFAULT_STATUSES[0] }
1667
+ }).run();
1668
+ },
1669
+ insertLocationChip: (editor) => {
1670
+ const label = typeof window !== "undefined" && typeof window.prompt === "function" ? window.prompt("Enter location name:", "") ?? "" : "";
1671
+ if (!label.trim()) return false;
1672
+ return editor.chain().focus().insertContent({
1673
+ type: "locationChip",
1674
+ attrs: { label: label.trim() }
1675
+ }).run();
1676
+ }
1677
+ }
1678
+ });
1679
+
1250
1680
  // src/slashMenu.ts
1251
1681
  import { Extension as Extension3 } from "@tiptap/core";
1252
1682
  import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
1253
- import { definePlugin as definePlugin18 } from "@kedataindo/docflow-core";
1683
+ import { definePlugin as definePlugin19 } from "@kedataindo/docflow-core";
1254
1684
  var slashState = {
1255
1685
  open: false,
1256
1686
  query: "",
@@ -1395,7 +1825,7 @@ var SlashMenuExtension = Extension3.create({
1395
1825
  ];
1396
1826
  }
1397
1827
  });
1398
- var slashMenuPlugin = definePlugin18({
1828
+ var slashMenuPlugin = definePlugin19({
1399
1829
  id: "slash-menu",
1400
1830
  tiptapExtensions: [SlashMenuExtension],
1401
1831
  hooks: {
@@ -1426,7 +1856,8 @@ var defaultPlugins = [
1426
1856
  highlightPlugin,
1427
1857
  citationPlugin,
1428
1858
  aiPlugin,
1429
- commentPlugin
1859
+ commentPlugin,
1860
+ smartElementsPlugin
1430
1861
  ];
1431
1862
  export {
1432
1863
  AIExtension,
@@ -1438,9 +1869,14 @@ export {
1438
1869
  CiteEngine,
1439
1870
  CommentMark as CommentMarkExtension,
1440
1871
  DEFAULT_CSL_STYLE,
1872
+ DateChipNode,
1873
+ DropdownChipNode,
1874
+ FileChipNode,
1441
1875
  FontSizeExtension,
1442
1876
  FootnoteNode,
1877
+ LocationChipNode,
1443
1878
  PageBreak,
1879
+ PeopleChipNode,
1444
1880
  SlashMenuExtension,
1445
1881
  TocEntryNode,
1446
1882
  TocNode,
@@ -1475,6 +1911,7 @@ export {
1475
1911
  sanitizeCiteprocHtml,
1476
1912
  slashMenuPlugin,
1477
1913
  slashState,
1914
+ smartElementsPlugin,
1478
1915
  tablePlugin,
1479
1916
  textColorPlugin,
1480
1917
  tocPlugin
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kedataindo/docflow-plugins",
3
3
  "license": "UNLICENSED",
4
- "version": "0.0.30",
4
+ "version": "0.0.32",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -30,7 +30,7 @@
30
30
  "@tiptap/extension-highlight": "^2.27.2",
31
31
  "@tiptap/pm": "^2.11.0",
32
32
  "citeproc": "^2.4.63",
33
- "@kedataindo/docflow-core": "0.0.28"
33
+ "@kedataindo/docflow-core": "0.0.30"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@tiptap/core": "^2.11.0",