@fastkit/vui 0.7.57 → 0.7.64

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.
Files changed (36) hide show
  1. package/dist/tool/index.js +1 -0
  2. package/dist/tool/vite-plugin.d.ts.map +1 -1
  3. package/dist/vui.cjs.js +615 -44
  4. package/dist/vui.cjs.prod.js +615 -44
  5. package/dist/vui.css +221 -0
  6. package/dist/vui.d.ts +139 -4
  7. package/dist/vui.min.css +1 -1
  8. package/dist/vui.min.css.map +1 -1
  9. package/dist/vui.mjs +605 -44
  10. package/package.json +7 -6
  11. package/src/components/VButton/VButtonGroup.scss +4 -0
  12. package/src/components/VButton/VButtonGroup.tsx +12 -1
  13. package/src/components/VWysiwygEditor/.DS_Store +0 -0
  14. package/src/components/VWysiwygEditor/VWysiwygEditor.scss +6 -0
  15. package/src/components/VWysiwygEditor/VWysiwygEditor.tsx +97 -56
  16. package/src/components/VWysiwygEditor/extensions/.DS_Store +0 -0
  17. package/src/components/VWysiwygEditor/extensions/color.ts +72 -0
  18. package/src/components/VWysiwygEditor/extensions/index.ts +2 -0
  19. package/src/components/VWysiwygEditor/extensions/linter/.DS_Store +0 -0
  20. package/src/components/VWysiwygEditor/extensions/linter/Linter.scss +140 -0
  21. package/src/components/VWysiwygEditor/extensions/linter/Linter.tsx +210 -0
  22. package/src/components/VWysiwygEditor/extensions/linter/LinterPlugin.ts +62 -0
  23. package/src/components/VWysiwygEditor/extensions/linter/index.ts +5 -0
  24. package/src/components/VWysiwygEditor/extensions/linter/plugins/BadWords.tsx +50 -0
  25. package/src/components/VWysiwygEditor/extensions/linter/plugins/HeadingLevel.ts +39 -0
  26. package/src/components/VWysiwygEditor/extensions/linter/plugins/Punctuation.ts +49 -0
  27. package/src/components/VWysiwygEditor/extensions/linter/plugins/index.ts +3 -0
  28. package/src/components/VWysiwygEditor/extensions/linter/utils.ts +28 -0
  29. package/src/components/VWysiwygEditor/index.ts +1 -0
  30. package/src/components/VWysiwygEditor/schemes.ts +190 -4
  31. package/src/components/VWysiwygEditor/tools/color.scss +140 -0
  32. package/src/components/VWysiwygEditor/tools/color.tsx +90 -0
  33. package/src/components/VWysiwygEditor/tools/index.ts +1 -1
  34. package/src/service.tsx +5 -0
  35. package/src/tool/vite-plugin.ts +1 -0
  36. package/src/components/VWysiwygEditor/tools/text-color.ts +0 -14
package/dist/vui.mjs CHANGED
@@ -14,6 +14,9 @@ import { VAppContainer, VAppLayoutControl } from '@fastkit/vue-app-layout';
14
14
  import { createTextableProps, createTextableEmits, TextableControl } from '@fastkit/vue-form-control';
15
15
  import { useEditor, EditorContent, BubbleMenu } from '@tiptap/vue-3';
16
16
  import StarterKit from '@tiptap/starter-kit';
17
+ import { Extension } from '@tiptap/core';
18
+ import { Decoration, DecorationSet } from 'prosemirror-view';
19
+ import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
17
20
  import { BulletList } from '@tiptap/extension-bullet-list';
18
21
  import { Bold } from '@tiptap/extension-bold';
19
22
  import { Italic } from '@tiptap/extension-italic';
@@ -22,6 +25,7 @@ import { History } from '@tiptap/extension-history';
22
25
  import { Link } from '@tiptap/extension-link';
23
26
  import { validateIf, url } from '@fastkit/rules';
24
27
  import { OrderedList } from '@tiptap/extension-ordered-list';
28
+ import TextStyle from '@tiptap/extension-text-style';
25
29
 
26
30
  const CONTROL_SIZES = ['sm', 'md', 'lg'];
27
31
  const CONTROL_FIELD_VARIANTS = ['outlined', 'filled', 'flat'];
@@ -1224,6 +1228,7 @@ const VButtonGroup = defineComponent({
1224
1228
 
1225
1229
  setup(props, ctx) {
1226
1230
  return () => {
1231
+ let hasIcon = false;
1227
1232
  let buttonLength = 0;
1228
1233
  let tmp = renderSlotOrEmpty$1(ctx.slots) || [];
1229
1234
 
@@ -1240,6 +1245,11 @@ const VButtonGroup = defineComponent({
1240
1245
  }
1241
1246
 
1242
1247
  buttonLength++;
1248
+
1249
+ if (!hasIcon && node.props && !!node.props.icon) {
1250
+ hasIcon = true;
1251
+ }
1252
+
1243
1253
  return {
1244
1254
  isButton: true,
1245
1255
  node
@@ -1256,6 +1266,7 @@ const VButtonGroup = defineComponent({
1256
1266
  ...childProps,
1257
1267
  rouded: false,
1258
1268
  class: ['v-button-group__item', {
1269
+ 'v-button--icon': hasIcon,
1259
1270
  'v-button-group__item--has-left': hasLeft,
1260
1271
  'v-button-group__item--has-right': hasRight
1261
1272
  }]
@@ -1265,7 +1276,9 @@ const VButtonGroup = defineComponent({
1265
1276
  return child.node;
1266
1277
  });
1267
1278
  return createVNode("div", {
1268
- "class": "v-button-group"
1279
+ "class": ['v-button-group', {
1280
+ 'v-button-group--has-icon': hasIcon
1281
+ }]
1269
1282
  }, [$children]);
1270
1283
  };
1271
1284
  }
@@ -2869,13 +2882,105 @@ const VTextarea = defineComponent({
2869
2882
 
2870
2883
  });
2871
2884
 
2885
+ const EDITOR_EVENTS = [
2886
+ 'beforeCreate',
2887
+ 'create',
2888
+ 'update',
2889
+ 'selectionUpdate',
2890
+ 'transaction',
2891
+ 'focus',
2892
+ 'blur',
2893
+ 'destroy',
2894
+ ];
2895
+ const prefixedEventName = (source) => {
2896
+ return `on${source.charAt(0).toUpperCase()}${source.slice(1)}`;
2897
+ };
2898
+ class WysiwygEditorInitializeContext {
2899
+ listeners = {};
2900
+ _vui;
2901
+ get vui() {
2902
+ return this._vui();
2903
+ }
2904
+ constructor(vuiGetter, opts = {}) {
2905
+ this._vui = vuiGetter;
2906
+ EDITOR_EVENTS.forEach((event) => {
2907
+ this.listeners[event] = [];
2908
+ const prefixed = prefixedEventName(event);
2909
+ const fn = opts[prefixed];
2910
+ fn && this.listeners[event].push(fn);
2911
+ });
2912
+ }
2913
+ on(ev, handler) {
2914
+ this.listeners[ev].push(handler);
2915
+ return () => this.off(ev, handler);
2916
+ }
2917
+ off(ev, handler) {
2918
+ this.listeners[ev] = this.listeners[ev].filter((_handler) => _handler !== handler);
2919
+ }
2920
+ editorOptions() {
2921
+ const opts = {};
2922
+ EDITOR_EVENTS.forEach((event) => {
2923
+ const prefixed = prefixedEventName(event);
2924
+ opts[prefixed] = (props) => {
2925
+ const handlers = this.listeners[event];
2926
+ handlers.forEach((handler) => {
2927
+ handler(props);
2928
+ });
2929
+ };
2930
+ });
2931
+ return opts;
2932
+ }
2933
+ }
2934
+ // export function createWysiwygExtension<Options = any, Storage = any>(
2935
+ // extension: Extension<Options, Storage>,
2936
+ // ): Extension<Options, Storage>;
2937
+ // export function createWysiwygExtension<Options = any, Storage = any>(
2938
+ // node: Node<Options, Storage>,
2939
+ // ): Node<Options, Storage>;
2940
+ // export function createWysiwygExtension<Options = any, Storage = any>(
2941
+ // mark: Mark<Options, Storage>,
2942
+ // ): Mark<Options, Storage>;
2943
+ // export function createWysiwygExtension<Options = any, Storage = any>(
2944
+ // factory: WysiwygExtensionFactory<Options, Storage>,
2945
+ // ): WysiwygExtensionFactory<Options, Storage>;
2946
+ function isCreatedWysiwygExtension(source) {
2947
+ return (!!source &&
2948
+ typeof source === 'object' &&
2949
+ source.__isCreatedWysiwygExtension === true);
2950
+ }
2951
+ function createWysiwygExtension(extension) {
2952
+ const ext = {
2953
+ __isCreatedWysiwygExtension: true,
2954
+ _configs: [],
2955
+ configure: (opts) => {
2956
+ opts && ext._configs.push(opts);
2957
+ return ext;
2958
+ },
2959
+ raw: extension,
2960
+ };
2961
+ return ext;
2962
+ }
2963
+ function resolveRawWysiwygExtension(raw, ctx) {
2964
+ if (isCreatedWysiwygExtension(raw)) {
2965
+ const { raw: _raw, _configs } = raw;
2966
+ let ext = typeof _raw === 'function' ? _raw(ctx) : _raw;
2967
+ _configs.forEach((config) => {
2968
+ ext = ext.configure(config);
2969
+ });
2970
+ return ext;
2971
+ }
2972
+ return typeof raw === 'function' ? raw(ctx) : raw;
2973
+ }
2974
+ function resolveRawWysiwygExtensions(raws, ctx) {
2975
+ return raws.map((raw) => resolveRawWysiwygExtension(raw, ctx));
2976
+ }
2872
2977
  function resolveRawWysiwygEditorTool(raw, vui) {
2873
2978
  return typeof raw === 'function' ? raw(vui) : raw;
2874
2979
  }
2875
- function resolveRawWysiwygEditorTools(raws, vui) {
2980
+ function resolveRawWysiwygEditorTools(rawTools, vui) {
2876
2981
  const tools = [];
2877
2982
  const extensions = [];
2878
- raws.forEach((raw) => {
2983
+ rawTools.forEach((raw) => {
2879
2984
  let resolved = resolveRawWysiwygEditorTool(raw, vui);
2880
2985
  if (!Array.isArray(resolved)) {
2881
2986
  resolved = [resolved];
@@ -2906,6 +3011,10 @@ const VWysiwygEditor = defineComponent({
2906
3011
  ...createControlFieldProviderProps(),
2907
3012
  ...createControlProps(),
2908
3013
  ...defineSlotsProps(),
3014
+ extensions: {
3015
+ type: Array,
3016
+ default: () => []
3017
+ },
2909
3018
  tools: {
2910
3019
  type: Array,
2911
3020
  default: () => []
@@ -2928,13 +3037,40 @@ const VWysiwygEditor = defineComponent({
2928
3037
  nodeType: VUI_WYSIWYG_EDITOR_SYMBOL,
2929
3038
  validationValue: () => textRef.value
2930
3039
  });
3040
+ const initializeCtx = new WysiwygEditorInitializeContext(() => vui, {
3041
+ onCreate: ({
3042
+ editor
3043
+ }) => {
3044
+ textRef.value = editor.getText();
3045
+ updateEditable();
3046
+ },
3047
+ onFocus: ({
3048
+ event
3049
+ }) => {
3050
+ inputControl.focusHandler(event);
3051
+ },
3052
+ onBlur: ({
3053
+ event
3054
+ }) => {
3055
+ inputControl.blurHandler(event);
3056
+ },
3057
+ onUpdate: ({
3058
+ editor
3059
+ }) => {
3060
+ inputControl.value = editor.getHTML();
3061
+ textRef.value = editor.getText();
3062
+ }
3063
+ });
2931
3064
  const extensions = [StarterKit.configure({
2932
3065
  bold: false,
2933
3066
  bulletList: false,
2934
3067
  orderedList: false,
2935
3068
  history: false,
2936
3069
  italic: false
2937
- }), ...wysiwygSettings.value.extensions];
3070
+ }), // Linter.configure({
3071
+ // plugins: [BadWords(['abc', 'evidently']), Punctuation, HeadingLevel],
3072
+ // }),
3073
+ ...resolveRawWysiwygExtensions(props.extensions, initializeCtx), ...wysiwygSettings.value.extensions];
2938
3074
 
2939
3075
  const updateEditable = () => {
2940
3076
  if (!editor.value) return;
@@ -2948,23 +3084,13 @@ const VWysiwygEditor = defineComponent({
2948
3084
  $editor.commands.setContent(modelValue == null ? '' : modelValue, false);
2949
3085
  textRef.value = $editor.getText();
2950
3086
  });
2951
- const editor = useEditor({
2952
- onCreate: ({
2953
- editor
2954
- }) => {
2955
- textRef.value = editor.getText();
2956
- updateEditable();
2957
- },
2958
- onFocus: ctx => {
2959
- inputControl.focusHandler(ctx.event);
2960
- },
2961
- onBlur: ctx => {
2962
- inputControl.blurHandler(ctx.event);
2963
- },
2964
- onUpdate: ev => {
2965
- inputControl.value = ev.editor.getHTML();
2966
- textRef.value = ev.editor.getText();
2967
- },
3087
+ initializeCtx.on('create', ({
3088
+ editor
3089
+ }) => {
3090
+ textRef.value = editor.getText();
3091
+ updateEditable();
3092
+ });
3093
+ const editor = useEditor({ ...initializeCtx.editorOptions(),
2968
3094
  autofocus: props.autofocus,
2969
3095
  content: props.modelValue,
2970
3096
  extensions,
@@ -3010,9 +3136,9 @@ const VWysiwygEditor = defineComponent({
3010
3136
  const _editor = editor.value;
3011
3137
  const variant = vui.setting(bubbleMenu ? 'containedVariant' : 'plainVariant');
3012
3138
  return createVNode(VButtonGroup, {
3013
- "class": {
3139
+ "class": ['v-wysiwyg-editor__toolbar', {
3014
3140
  'v-wysiwyg-editor__floating-toolbar': props.floatingToolbar
3015
- },
3141
+ }],
3016
3142
  "variant": variant
3017
3143
  }, _isSlot$7(_slot = tools.map(({
3018
3144
  key,
@@ -3024,16 +3150,23 @@ const VWysiwygEditor = defineComponent({
3024
3150
  const isActive = !!_editor && resolveContextableValue(context, active);
3025
3151
  const isDisabled = !inputControl.canOperation || !!_editor && resolveContextableValue(context, disabled);
3026
3152
  let iconName;
3153
+ let child;
3027
3154
 
3028
3155
  if (typeof icon === 'function') {
3029
3156
  if (_editor) {
3030
- iconName = icon(context);
3157
+ const _child = icon(context);
3158
+
3159
+ if (typeof _child === 'function') {
3160
+ child = _child();
3161
+ } else {
3162
+ iconName = child;
3163
+ }
3031
3164
  }
3032
3165
  } else {
3033
3166
  iconName = icon;
3034
3167
  }
3035
3168
 
3036
- if (!iconName) return;
3169
+ if (!iconName && child == null) return;
3037
3170
  return createVNode(VButton, {
3038
3171
  "tabindex": -1,
3039
3172
  "key": key,
@@ -3041,12 +3174,17 @@ const VWysiwygEditor = defineComponent({
3041
3174
  "onClick": ev => onClick(context, ev),
3042
3175
  "color": isActive ? toolButtonActiveColor : undefined,
3043
3176
  "disabled": isDisabled
3044
- }, null);
3177
+ }, _isSlot$7(child) ? child : {
3178
+ default: () => [child]
3179
+ });
3045
3180
  })) ? _slot : {
3046
3181
  default: () => [_slot]
3047
3182
  });
3048
3183
  };
3049
3184
 
3185
+ onBeforeUnmount(() => {
3186
+ editor.value && editor.value.destroy();
3187
+ });
3050
3188
  return {
3051
3189
  editor,
3052
3190
  ...inputControl.expose(),
@@ -3082,9 +3220,7 @@ const VWysiwygEditor = defineComponent({
3082
3220
  } = this;
3083
3221
  return createVNode("div", {
3084
3222
  "class": "v-wysiwyg-editor__wrapper"
3085
- }, [!this.isReadonly && this.createTools(), createVNode("div", {
3086
- "class": "v-wysiwyg-editor__body"
3087
- }, [createVNode(VControlField, {
3223
+ }, [!this.isReadonly && this.createTools(), createVNode(VControlField, {
3088
3224
  "class": "v-wysiwyg-editor__input",
3089
3225
  "autoHeight": true,
3090
3226
  "startAdornment": this.startAdornment,
@@ -3094,7 +3230,9 @@ const VWysiwygEditor = defineComponent({
3094
3230
  default: () => {
3095
3231
  let _slot2;
3096
3232
 
3097
- return createVNode(Fragment, null, [createVNode(EditorContent, {
3233
+ return createVNode("div", {
3234
+ "class": "v-wysiwyg-editor__body"
3235
+ }, [createVNode(EditorContent, {
3098
3236
  "class": "v-wysiwyg-editor__input__element wysiwyg",
3099
3237
  "editor": editor
3100
3238
  }, null), !this.floatingToolbar && !!editor && !this.isReadonly && createVNode(BubbleMenu, {
@@ -3104,7 +3242,7 @@ const VWysiwygEditor = defineComponent({
3104
3242
  default: () => [_slot2]
3105
3243
  })]);
3106
3244
  }
3107
- })])]);
3245
+ })]);
3108
3246
  },
3109
3247
  infoAppends: () => {
3110
3248
  const {
@@ -3122,6 +3260,370 @@ function resolveContextableValue(ctx, source) {
3122
3260
  return typeof source === 'function' ? source(ctx) : source;
3123
3261
  }
3124
3262
 
3263
+ const PROBLEM_CLASS_NAME = 'v-linter__problem';
3264
+ const ISSUE_ICON_CLASS_NAME = 'v-linter__issue-icon';
3265
+ const ISSUE_DATASET_NAME = 'data-issue-id';
3266
+
3267
+ function resolveWysiwygLinterFixMessage(message) {
3268
+ return typeof message === 'function' ? message() : message;
3269
+ }
3270
+
3271
+ function renderIcon(view, issue) {
3272
+ const {
3273
+ level = 'warning'
3274
+ } = issue;
3275
+ const icon = document.createElement('div');
3276
+ const message = resolveWysiwygLinterFixMessage(issue.message);
3277
+ icon.className = `${ISSUE_ICON_CLASS_NAME} ${level}-scope`;
3278
+ icon.setAttribute(ISSUE_DATASET_NAME, issue.id);
3279
+
3280
+ if (typeof message === 'string') {
3281
+ icon.title = message;
3282
+ }
3283
+
3284
+ return icon;
3285
+ }
3286
+
3287
+ function runAllLinterPlugins(doc, plugins) {
3288
+ const decorations = [];
3289
+ const issues = plugins.map(RegisteredLinterPlugin => {
3290
+ return new RegisteredLinterPlugin(doc).scan().getResults();
3291
+ }).flat();
3292
+ issues.forEach(issue => {
3293
+ const {
3294
+ level = 'warning'
3295
+ } = issue;
3296
+ const message = resolveWysiwygLinterFixMessage(issue.message);
3297
+ decorations.push(Decoration.inline(issue.from, issue.to, {
3298
+ class: `${PROBLEM_CLASS_NAME} ${level}-scope`,
3299
+ 'data-issue-id': issue.id,
3300
+ title: typeof message === 'string' ? message : undefined
3301
+ }), Decoration.widget(issue.from, view => renderIcon(view, issue)));
3302
+ });
3303
+ const decorationSet = DecorationSet.create(doc, decorations);
3304
+ return {
3305
+ issues,
3306
+ decorationSet
3307
+ };
3308
+ }
3309
+
3310
+ const WysiwygLinter = createWysiwygExtension(ctx => {
3311
+ function showMenu(view, issue, event) {
3312
+ const message = resolveWysiwygLinterFixMessage(issue.message);
3313
+ const variant = ctx.vui.setting('containedVariant');
3314
+ const scope = ctx.vui.setting(`${issue.level}Scope`);
3315
+ return ctx.vui.menu({
3316
+ activator: event,
3317
+ content: stack => createVNode("div", {
3318
+ "class": "v-linter__issue-menu__body"
3319
+ }, [createVNode("div", {
3320
+ "class": ['v-linter__issue-menu__message', `${scope}-scope ${variant}`]
3321
+ }, [message]), issue.fix.length && createVNode("div", {
3322
+ "class": "v-linter__issue-menu__fixers"
3323
+ }, [issue.fix.map((fixer, index) => createVNode("div", {
3324
+ "key": index,
3325
+ "class": "v-linter__issue-menu__fixer"
3326
+ }, [createVNode("button", {
3327
+ "type": "button",
3328
+ "class": "v-linter__issue-menu__fixer__button",
3329
+ "onClick": () => {
3330
+ stack.close();
3331
+ fixer.handler(view, issue);
3332
+ }
3333
+ }, [createVNode("span", {
3334
+ "class": "v-linter__issue-menu__fixer__button__message"
3335
+ }, [resolveWysiwygLinterFixMessage(fixer.message)])])]))])])
3336
+ });
3337
+ }
3338
+
3339
+ return Extension.create({
3340
+ name: 'linter',
3341
+
3342
+ addOptions() {
3343
+ return {
3344
+ plugins: []
3345
+ };
3346
+ },
3347
+
3348
+ addStorage() {
3349
+ return {
3350
+ issues: []
3351
+ };
3352
+ },
3353
+
3354
+ addProseMirrorPlugins() {
3355
+ const {
3356
+ plugins
3357
+ } = this.options;
3358
+ const {
3359
+ storage
3360
+ } = this;
3361
+
3362
+ const runAll = doc => {
3363
+ const {
3364
+ issues,
3365
+ decorationSet
3366
+ } = runAllLinterPlugins(doc, plugins);
3367
+ storage.issues = issues;
3368
+ return decorationSet;
3369
+ };
3370
+
3371
+ const getIssue = id => storage.issues.find(issue => issue.id === id);
3372
+
3373
+ const getIssueElementByEvent = source => {
3374
+ if (!source || !(source instanceof HTMLElement)) {
3375
+ return;
3376
+ }
3377
+
3378
+ const issueId = source.getAttribute(ISSUE_DATASET_NAME);
3379
+ const issue = issueId && getIssue(issueId);
3380
+ if (!issue) return;
3381
+ return {
3382
+ issue
3383
+ };
3384
+ };
3385
+
3386
+ return [new Plugin({
3387
+ key: new PluginKey('linter'),
3388
+ state: {
3389
+ init(_, {
3390
+ doc
3391
+ }) {
3392
+ return runAll(doc);
3393
+ },
3394
+
3395
+ apply(transaction, oldState) {
3396
+ return transaction.docChanged ? runAll(transaction.doc) : oldState;
3397
+ }
3398
+
3399
+ },
3400
+ props: {
3401
+ decorations(state) {
3402
+ return this.getState(state);
3403
+ },
3404
+
3405
+ handleClick(view, _, event) {
3406
+ const info = getIssueElementByEvent(event.target);
3407
+
3408
+ if (!info) {
3409
+ return false;
3410
+ }
3411
+
3412
+ const {
3413
+ issue
3414
+ } = info;
3415
+ const {
3416
+ from,
3417
+ to
3418
+ } = issue;
3419
+
3420
+ const focus = () => view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, from, to)).scrollIntoView());
3421
+
3422
+ focus();
3423
+ showMenu(view, issue, event);
3424
+ return true;
3425
+ }
3426
+
3427
+ }
3428
+ })];
3429
+ }
3430
+
3431
+ });
3432
+ });
3433
+
3434
+ let IDX = 256, BUFFER;
3435
+ const HEX = [];
3436
+ while (IDX--)
3437
+ HEX[IDX] = (IDX + 256).toString(16).substring(1);
3438
+ function cheepUUID() {
3439
+ let i = 0, num, out = '';
3440
+ if (!BUFFER || IDX + 16 > 256) {
3441
+ BUFFER = Array((i = 256));
3442
+ while (i--)
3443
+ BUFFER[i] = (256 * Math.random()) | 0;
3444
+ i = IDX = 0;
3445
+ }
3446
+ for (; i < 16; i++) {
3447
+ num = BUFFER[IDX + i];
3448
+ if (i == 6)
3449
+ out += HEX[(num & 15) | 64];
3450
+ else if (i == 8)
3451
+ out += HEX[(num & 63) | 128];
3452
+ else
3453
+ out += HEX[num];
3454
+ if (i & 1 && i > 1 && i < 11)
3455
+ out += '-';
3456
+ }
3457
+ IDX++;
3458
+ return out;
3459
+ }
3460
+
3461
+ class WysiwygLinterPlugin {
3462
+ doc;
3463
+ results = [];
3464
+ constructor(doc) {
3465
+ this.doc = doc;
3466
+ }
3467
+ record(result) {
3468
+ const { fix = [] } = result;
3469
+ this.results.push({
3470
+ level: 'error',
3471
+ id: cheepUUID(),
3472
+ ...result,
3473
+ fix: Array.isArray(fix) ? fix : [fix],
3474
+ });
3475
+ }
3476
+ scan() {
3477
+ return this;
3478
+ }
3479
+ getResults() {
3480
+ return this.results;
3481
+ }
3482
+ }
3483
+
3484
+ function WysiwygLinterBadWords(words) {
3485
+ const regex = new RegExp(`\\b(${words.join('|')})\\b`);
3486
+ return class WysiwygLinterBadWords extends WysiwygLinterPlugin {
3487
+ scan() {
3488
+ this.doc.descendants((node, position) => {
3489
+ if (!node.isText) {
3490
+ return;
3491
+ }
3492
+
3493
+ const matches = regex.exec(node.text);
3494
+
3495
+ if (matches) {
3496
+ const fixValue = matches[0] + '!!!!!';
3497
+ this.record({
3498
+ level: 'warning',
3499
+ message: `Try not to say '${matches[0]}'`,
3500
+ from: position + matches.index,
3501
+ to: position + matches.index + matches[0].length,
3502
+ fix: [{
3503
+ message: () => createVNode("span", null, [createVNode("code", null, [fixValue]), createTextVNode("\u306B\u4FEE\u6B63\u3059\u308B\u3002")]),
3504
+ handler: () => {
3505
+ console.log('hoge');
3506
+ }
3507
+ }, {
3508
+ message: 'どうにかする',
3509
+ handler: () => {
3510
+ console.log('hoge');
3511
+ }
3512
+ }]
3513
+ });
3514
+ }
3515
+ });
3516
+ return this;
3517
+ }
3518
+
3519
+ };
3520
+ }
3521
+
3522
+ class WysiwygLinterHeadingLevel extends WysiwygLinterPlugin {
3523
+ fixHeader(level) {
3524
+ return function ({ state, dispatch }, issue) {
3525
+ dispatch(state.tr.setNodeMarkup(issue.from - 1, undefined, { level }));
3526
+ };
3527
+ }
3528
+ scan() {
3529
+ let lastHeadLevel = null;
3530
+ this.doc.descendants((node, position) => {
3531
+ if (node.type.name === 'heading') {
3532
+ // Check whether heading levels fit under the current level
3533
+ const { level } = node.attrs;
3534
+ if (lastHeadLevel != null && level > lastHeadLevel + 1) {
3535
+ this.record({
3536
+ message: `Heading too small (${level} under ${lastHeadLevel})`,
3537
+ from: position + 1,
3538
+ to: position + 1 + node.content.size,
3539
+ fix: {
3540
+ message: '修正する',
3541
+ handler: this.fixHeader(lastHeadLevel + 1),
3542
+ },
3543
+ });
3544
+ }
3545
+ lastHeadLevel = level;
3546
+ }
3547
+ });
3548
+ return this;
3549
+ }
3550
+ }
3551
+
3552
+ class WysiwygLinterPunctuation extends WysiwygLinterPlugin {
3553
+ regex = / ([,.!?:]) ?/g;
3554
+ fix(replacement) {
3555
+ return function ({ state, dispatch }, issue) {
3556
+ dispatch(state.tr.replaceWith(issue.from, issue.to, state.schema.text(replacement)));
3557
+ };
3558
+ }
3559
+ scan() {
3560
+ this.doc.descendants((node, position) => {
3561
+ if (!node.isText) {
3562
+ return;
3563
+ }
3564
+ if (!node.text) {
3565
+ return;
3566
+ }
3567
+ const matches = this.regex.exec(node.text);
3568
+ if (matches) {
3569
+ this.record({
3570
+ message: 'Suspicious spacing around punctuation',
3571
+ from: position + matches.index,
3572
+ to: position + matches.index + matches[0].length,
3573
+ fix: {
3574
+ message: 'Fix it!!!',
3575
+ handler: this.fix(`${matches[1]} `),
3576
+ },
3577
+ });
3578
+ }
3579
+ });
3580
+ return this;
3581
+ }
3582
+ }
3583
+
3584
+ const WysiwygColorExtension = Extension.create({
3585
+ name: 'color',
3586
+ addOptions() {
3587
+ return {
3588
+ types: ['textStyle'],
3589
+ };
3590
+ },
3591
+ addGlobalAttributes() {
3592
+ return [
3593
+ {
3594
+ types: this.options.types,
3595
+ attributes: {
3596
+ color: {
3597
+ default: null,
3598
+ parseHTML: (element) => element.style.color.replace(/['"]+/g, ''),
3599
+ renderHTML: (attributes) => {
3600
+ if (!attributes.color) {
3601
+ return {};
3602
+ }
3603
+ return {
3604
+ style: `color: ${attributes.color}`,
3605
+ };
3606
+ },
3607
+ },
3608
+ },
3609
+ },
3610
+ ];
3611
+ },
3612
+ addCommands() {
3613
+ return {
3614
+ setColor: (color) => ({ chain }) => {
3615
+ return chain().setMark('textStyle', { color }).run();
3616
+ },
3617
+ unsetColor: () => ({ chain }) => {
3618
+ return chain()
3619
+ .setMark('textStyle', { color: null })
3620
+ .removeEmptyTextStyle()
3621
+ .run();
3622
+ },
3623
+ };
3624
+ },
3625
+ });
3626
+
3125
3627
  const WysiwygBulletListTool = (vui, options) => {
3126
3628
  const tool = {
3127
3629
  key: 'bulletList',
@@ -3262,18 +3764,73 @@ const WysiwygOrderedListTool = (vui, options) => {
3262
3764
  return tool;
3263
3765
  };
3264
3766
 
3265
- const WysiwygTextColorTool = (vui) => {
3266
- const tool = {
3267
- key: 'textColor',
3268
- icon: vui.icon('editorTextColor'),
3269
- // icon: (gen) => vui.icon('editorTextColor'),
3270
- onClick: (ctx) => {
3271
- ctx.vui.alert('ok');
3272
- },
3273
- floating: true,
3274
- };
3275
- return tool;
3276
- };
3767
+ function createWysiwygColorTool(opts) {
3768
+ const WysiwygColorTool = vui => {
3769
+ const tool = {
3770
+ key: 'textColor',
3771
+ // active: ({ editor }) => editor.isActive('textStyle'),
3772
+ icon: ({
3773
+ editor
3774
+ }) => () => {
3775
+ const color = editor.getAttributes('textStyle').color;
3776
+ const style = {
3777
+ color
3778
+ };
3779
+ return createVNode("span", {
3780
+ "class": "v-wysiwyg-color-tool__button"
3781
+ }, [createVNode(VIcon, {
3782
+ "class": "v-wysiwyg-color-tool__button__icon",
3783
+ "name": vui.icon('editorTextColor')
3784
+ }, null), createVNode("span", {
3785
+ "class": "v-wysiwyg-color-tool__button__bar",
3786
+ "style": style
3787
+ }, null)]);
3788
+ },
3789
+ onClick: (ctx, ev) => {
3790
+ ctx.vui.menu({
3791
+ class: 'v-wysiwyg-color-tool__menu',
3792
+ activator: ev,
3793
+ content: stack => createVNode("div", {
3794
+ "class": ['v-wysiwyg-color-tool__items', {
3795
+ 'v-wysiwyg-color-tool__items--with-label': opts.withLabel
3796
+ }]
3797
+ }, [opts.items.map((item, index) => createVNode("button", {
3798
+ "key": item.key == null ? index : item.key,
3799
+ "class": "v-wysiwyg-color-tool__item",
3800
+ "type": "button",
3801
+ "onClick": () => {
3802
+ const {
3803
+ color
3804
+ } = item;
3805
+ let command = ctx.editor.chain().focus();
3806
+
3807
+ if (color) {
3808
+ command = command.setColor(color);
3809
+ } else {
3810
+ command = command.unsetColor();
3811
+ }
3812
+
3813
+ command.run();
3814
+ stack.close();
3815
+ }
3816
+ }, [createVNode("span", {
3817
+ "class": "v-wysiwyg-color-tool__item__color",
3818
+ "style": item.color ? {
3819
+ color: item.color
3820
+ } : {}
3821
+ }, null), opts.withLabel && createVNode("span", {
3822
+ "class": "v-wysiwyg-color-tool__item__name"
3823
+ }, [item.name])]))])
3824
+ });
3825
+ },
3826
+ floating: true,
3827
+ extensions: [TextStyle, WysiwygColorExtension]
3828
+ };
3829
+ return tool;
3830
+ };
3831
+
3832
+ return WysiwygColorTool;
3833
+ }
3277
3834
 
3278
3835
  const {
3279
3836
  props,
@@ -4890,6 +5447,10 @@ class VuiService {
4890
5447
  return this.stack.snackbar(...args);
4891
5448
  }
4892
5449
 
5450
+ menu(...args) {
5451
+ return this.stack.menu(...args);
5452
+ }
5453
+
4893
5454
  formPrompt(settings, slot) {
4894
5455
  let form;
4895
5456
  const options = {
@@ -5041,4 +5602,4 @@ function installVuiPlugin(app, opts) {
5041
5602
  return app.use(VuiPlugin, opts);
5042
5603
  }
5043
5604
 
5044
- export { AVATAR_SIZES, CHIP_SIZES, CONTROL_FIELD_VARIANTS, CONTROL_LOADING_SPINNER_SIZES, CONTROL_SIZES, PAGINATION_ALIGNS, VApp, VAvatar, VBreadcrumbs, VBusyImage, VButton, VButtonGroup, VCard, VCardActions, VCardContent, VCheckbox, VCheckboxGroup, VChip, VContentSwitcher, VDataTable, VDrawerLayout, VForm, VFormControl, VGridContainer, VGridItem, VHero, VIcon, VListTile, VNavigation, VNavigationItem, VNumberField, VOption, VOptionGroup, VPagination, VPaper, VRadio, VRadioGroup, VSelect, VSkeltonLoaderBone, VSwitch, VSwitchGroup, VTabs, VTextField, VTextarea, VToolbar, VToolbarEdge, VToolbarMenu, VToolbarTitle, VUI_CHECKBOX_GROUP_SYMBOL, VUI_CHECKBOX_SYMBOL, VUI_FORM_SYMBOL, VUI_OPTION_SYMBOL, VUI_RADIO_GROUP_SYMBOL, VUI_RADIO_SYMBOL, VUI_SELECT_SYMBOL, VUI_SWITCH_GROUP_SYMBOL, VUI_SWITCH_SYMBOL, VUI_TEXTAREA_SYMBOL, VUI_TEXT_FIELD_SYMBOL, VUI_WYSIWYG_EDITOR_SYMBOL, VWysiwygEditor, VuiColorProviderInjectionKey, VuiControlFieldInjectionKey, VuiControlInjectionKey, VuiInjectionKey, VuiPlugin, VuiService, WysiwygBulletListTool, WysiwygFormatBoldTool, WysiwygFormatItalicTool, WysiwygFormatUnderlineTool, WysiwygHistoryTool, WysiwygLinkTool, WysiwygOrderedListTool, WysiwygTextColorTool, configureDataTableDefaults, createAvatarProps, createCardProps, createChipProps, createControlFieldProviderProps, createControlProps, createElevationProps, createListTileProps, createNavigationItemProps, createPaperBaseProps, createPaperProps, createRequiredChipRenderer, createVFormProps, defineFormSelectorComponent, iconProps, installVuiPlugin, listTileEmits, mergeVuiPluginOptions, mergeVuiServiceIconSettings, mergeVuiServiceOptions, mergeVuiServiceUISettings, paginationProps, rawIconProp, renderNavigationItemInput, resolveNavigationItemInput, resolveRawIconProp, resolveRawWysiwygEditorTools, splitAttrs, useControl, useControlField, useElevation, useVui, useVuiColorProvider, vueButtonProps };
5605
+ export { AVATAR_SIZES, CHIP_SIZES, CONTROL_FIELD_VARIANTS, CONTROL_LOADING_SPINNER_SIZES, CONTROL_SIZES, PAGINATION_ALIGNS, VApp, VAvatar, VBreadcrumbs, VBusyImage, VButton, VButtonGroup, VCard, VCardActions, VCardContent, VCheckbox, VCheckboxGroup, VChip, VContentSwitcher, VDataTable, VDrawerLayout, VForm, VFormControl, VGridContainer, VGridItem, VHero, VIcon, VListTile, VNavigation, VNavigationItem, VNumberField, VOption, VOptionGroup, VPagination, VPaper, VRadio, VRadioGroup, VSelect, VSkeltonLoaderBone, VSwitch, VSwitchGroup, VTabs, VTextField, VTextarea, VToolbar, VToolbarEdge, VToolbarMenu, VToolbarTitle, VUI_CHECKBOX_GROUP_SYMBOL, VUI_CHECKBOX_SYMBOL, VUI_FORM_SYMBOL, VUI_OPTION_SYMBOL, VUI_RADIO_GROUP_SYMBOL, VUI_RADIO_SYMBOL, VUI_SELECT_SYMBOL, VUI_SWITCH_GROUP_SYMBOL, VUI_SWITCH_SYMBOL, VUI_TEXTAREA_SYMBOL, VUI_TEXT_FIELD_SYMBOL, VUI_WYSIWYG_EDITOR_SYMBOL, VWysiwygEditor, VuiColorProviderInjectionKey, VuiControlFieldInjectionKey, VuiControlInjectionKey, VuiInjectionKey, VuiPlugin, VuiService, WysiwygBulletListTool, WysiwygColorExtension, WysiwygEditorInitializeContext, WysiwygFormatBoldTool, WysiwygFormatItalicTool, WysiwygFormatUnderlineTool, WysiwygHistoryTool, WysiwygLinkTool, WysiwygLinter, WysiwygLinterBadWords, WysiwygLinterHeadingLevel, WysiwygLinterPlugin, WysiwygLinterPunctuation, WysiwygOrderedListTool, configureDataTableDefaults, createAvatarProps, createCardProps, createChipProps, createControlFieldProviderProps, createControlProps, createElevationProps, createListTileProps, createNavigationItemProps, createPaperBaseProps, createPaperProps, createRequiredChipRenderer, createVFormProps, createWysiwygColorTool, createWysiwygExtension, defineFormSelectorComponent, iconProps, installVuiPlugin, listTileEmits, mergeVuiPluginOptions, mergeVuiServiceIconSettings, mergeVuiServiceOptions, mergeVuiServiceUISettings, paginationProps, rawIconProp, renderNavigationItemInput, resolveNavigationItemInput, resolveRawIconProp, resolveRawWysiwygEditorTools, resolveRawWysiwygExtensions, splitAttrs, useControl, useControlField, useElevation, useVui, useVuiColorProvider, vueButtonProps };