@fastkit/vui 0.7.59 → 0.7.65

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 +616 -39
  4. package/dist/vui.cjs.prod.js +616 -39
  5. package/dist/vui.css +219 -0
  6. package/dist/vui.d.ts +141 -4
  7. package/dist/vui.min.css +1 -1
  8. package/dist/vui.min.css.map +1 -1
  9. package/dist/vui.mjs +606 -39
  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 +4 -0
  15. package/src/components/VWysiwygEditor/VWysiwygEditor.tsx +65 -22
  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 +214 -0
  22. package/src/components/VWysiwygEditor/extensions/linter/LinterPlugin.ts +65 -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(),
@@ -3122,6 +3260,376 @@ 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
+ icon
3296
+ } = issue;
3297
+ const message = resolveWysiwygLinterFixMessage(issue.message);
3298
+ decorations.push(Decoration.inline(issue.from, issue.to, {
3299
+ class: `${PROBLEM_CLASS_NAME} ${level}-scope`,
3300
+ 'data-issue-id': issue.id,
3301
+ title: typeof message === 'string' ? message : undefined
3302
+ }));
3303
+
3304
+ if (icon) {
3305
+ decorations.push(Decoration.widget(issue.from, view => renderIcon(view, issue)));
3306
+ }
3307
+ });
3308
+ const decorationSet = DecorationSet.create(doc, decorations);
3309
+ return {
3310
+ issues,
3311
+ decorationSet
3312
+ };
3313
+ }
3314
+
3315
+ const WysiwygLinter = createWysiwygExtension(ctx => {
3316
+ function showMenu(view, issue, event) {
3317
+ const message = resolveWysiwygLinterFixMessage(issue.message);
3318
+ const variant = ctx.vui.setting('containedVariant');
3319
+ const scope = ctx.vui.setting(`${issue.level}Scope`);
3320
+ return ctx.vui.menu({
3321
+ activator: event,
3322
+ content: stack => createVNode("div", {
3323
+ "class": "v-linter__issue-menu__body"
3324
+ }, [createVNode("div", {
3325
+ "class": ['v-linter__issue-menu__message', `${scope}-scope ${variant}`]
3326
+ }, [message]), issue.fix.length && createVNode("div", {
3327
+ "class": "v-linter__issue-menu__fixers"
3328
+ }, [issue.fix.map((fixer, index) => createVNode("div", {
3329
+ "key": index,
3330
+ "class": "v-linter__issue-menu__fixer"
3331
+ }, [createVNode("button", {
3332
+ "type": "button",
3333
+ "class": "v-linter__issue-menu__fixer__button",
3334
+ "onClick": () => {
3335
+ stack.close();
3336
+ fixer.handler(view, issue);
3337
+ }
3338
+ }, [createVNode("span", {
3339
+ "class": "v-linter__issue-menu__fixer__button__message"
3340
+ }, [resolveWysiwygLinterFixMessage(fixer.message)])])]))])])
3341
+ });
3342
+ }
3343
+
3344
+ return Extension.create({
3345
+ name: 'linter',
3346
+
3347
+ addOptions() {
3348
+ return {
3349
+ plugins: []
3350
+ };
3351
+ },
3352
+
3353
+ addStorage() {
3354
+ return {
3355
+ issues: []
3356
+ };
3357
+ },
3358
+
3359
+ addProseMirrorPlugins() {
3360
+ const {
3361
+ plugins
3362
+ } = this.options;
3363
+ const {
3364
+ storage
3365
+ } = this;
3366
+
3367
+ const runAll = doc => {
3368
+ const {
3369
+ issues,
3370
+ decorationSet
3371
+ } = runAllLinterPlugins(doc, plugins);
3372
+ storage.issues = issues;
3373
+ return decorationSet;
3374
+ };
3375
+
3376
+ const getIssue = id => storage.issues.find(issue => issue.id === id);
3377
+
3378
+ const getIssueElementByEvent = source => {
3379
+ if (!source || !(source instanceof HTMLElement)) {
3380
+ return;
3381
+ }
3382
+
3383
+ const issueId = source.getAttribute(ISSUE_DATASET_NAME);
3384
+ const issue = issueId && getIssue(issueId);
3385
+ if (!issue) return;
3386
+ return {
3387
+ issue
3388
+ };
3389
+ };
3390
+
3391
+ return [new Plugin({
3392
+ key: new PluginKey('linter'),
3393
+ state: {
3394
+ init(_, {
3395
+ doc
3396
+ }) {
3397
+ return runAll(doc);
3398
+ },
3399
+
3400
+ apply(transaction, oldState) {
3401
+ return transaction.docChanged ? runAll(transaction.doc) : oldState;
3402
+ }
3403
+
3404
+ },
3405
+ props: {
3406
+ decorations(state) {
3407
+ return this.getState(state);
3408
+ },
3409
+
3410
+ handleClick(view, _, event) {
3411
+ const info = getIssueElementByEvent(event.target);
3412
+
3413
+ if (!info) {
3414
+ return false;
3415
+ }
3416
+
3417
+ const {
3418
+ issue
3419
+ } = info;
3420
+ const {
3421
+ from,
3422
+ to
3423
+ } = issue;
3424
+
3425
+ const focus = () => view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, from, to)).scrollIntoView());
3426
+
3427
+ focus();
3428
+ showMenu(view, issue, event);
3429
+ return true;
3430
+ }
3431
+
3432
+ }
3433
+ })];
3434
+ }
3435
+
3436
+ });
3437
+ });
3438
+
3439
+ let IDX = 256, BUFFER;
3440
+ const HEX = [];
3441
+ while (IDX--)
3442
+ HEX[IDX] = (IDX + 256).toString(16).substring(1);
3443
+ function cheepUUID() {
3444
+ let i = 0, num, out = '';
3445
+ if (!BUFFER || IDX + 16 > 256) {
3446
+ BUFFER = Array((i = 256));
3447
+ while (i--)
3448
+ BUFFER[i] = (256 * Math.random()) | 0;
3449
+ i = IDX = 0;
3450
+ }
3451
+ for (; i < 16; i++) {
3452
+ num = BUFFER[IDX + i];
3453
+ if (i == 6)
3454
+ out += HEX[(num & 15) | 64];
3455
+ else if (i == 8)
3456
+ out += HEX[(num & 63) | 128];
3457
+ else
3458
+ out += HEX[num];
3459
+ if (i & 1 && i > 1 && i < 11)
3460
+ out += '-';
3461
+ }
3462
+ IDX++;
3463
+ return out;
3464
+ }
3465
+
3466
+ class WysiwygLinterPlugin {
3467
+ doc;
3468
+ results = [];
3469
+ constructor(doc) {
3470
+ this.doc = doc;
3471
+ }
3472
+ record(result) {
3473
+ const { fix = [], icon = false } = result;
3474
+ this.results.push({
3475
+ level: 'error',
3476
+ id: cheepUUID(),
3477
+ ...result,
3478
+ fix: Array.isArray(fix) ? fix : [fix],
3479
+ icon,
3480
+ });
3481
+ }
3482
+ scan() {
3483
+ return this;
3484
+ }
3485
+ getResults() {
3486
+ return this.results;
3487
+ }
3488
+ }
3489
+
3490
+ function WysiwygLinterBadWords(words) {
3491
+ const regex = new RegExp(`\\b(${words.join('|')})\\b`);
3492
+ return class WysiwygLinterBadWords extends WysiwygLinterPlugin {
3493
+ scan() {
3494
+ this.doc.descendants((node, position) => {
3495
+ if (!node.isText) {
3496
+ return;
3497
+ }
3498
+
3499
+ const matches = regex.exec(node.text);
3500
+
3501
+ if (matches) {
3502
+ const fixValue = matches[0] + '!!!!!';
3503
+ this.record({
3504
+ level: 'warning',
3505
+ message: `Try not to say '${matches[0]}'`,
3506
+ from: position + matches.index,
3507
+ to: position + matches.index + matches[0].length,
3508
+ fix: [{
3509
+ message: () => createVNode("span", null, [createVNode("code", null, [fixValue]), createTextVNode("\u306B\u4FEE\u6B63\u3059\u308B\u3002")]),
3510
+ handler: () => {
3511
+ console.log('hoge');
3512
+ }
3513
+ }, {
3514
+ message: 'どうにかする',
3515
+ handler: () => {
3516
+ console.log('hoge');
3517
+ }
3518
+ }]
3519
+ });
3520
+ }
3521
+ });
3522
+ return this;
3523
+ }
3524
+
3525
+ };
3526
+ }
3527
+
3528
+ class WysiwygLinterHeadingLevel extends WysiwygLinterPlugin {
3529
+ fixHeader(level) {
3530
+ return function ({ state, dispatch }, issue) {
3531
+ dispatch(state.tr.setNodeMarkup(issue.from - 1, undefined, { level }));
3532
+ };
3533
+ }
3534
+ scan() {
3535
+ let lastHeadLevel = null;
3536
+ this.doc.descendants((node, position) => {
3537
+ if (node.type.name === 'heading') {
3538
+ // Check whether heading levels fit under the current level
3539
+ const { level } = node.attrs;
3540
+ if (lastHeadLevel != null && level > lastHeadLevel + 1) {
3541
+ this.record({
3542
+ message: `Heading too small (${level} under ${lastHeadLevel})`,
3543
+ from: position + 1,
3544
+ to: position + 1 + node.content.size,
3545
+ fix: {
3546
+ message: '修正する',
3547
+ handler: this.fixHeader(lastHeadLevel + 1),
3548
+ },
3549
+ });
3550
+ }
3551
+ lastHeadLevel = level;
3552
+ }
3553
+ });
3554
+ return this;
3555
+ }
3556
+ }
3557
+
3558
+ class WysiwygLinterPunctuation extends WysiwygLinterPlugin {
3559
+ regex = / ([,.!?:]) ?/g;
3560
+ fix(replacement) {
3561
+ return function ({ state, dispatch }, issue) {
3562
+ dispatch(state.tr.replaceWith(issue.from, issue.to, state.schema.text(replacement)));
3563
+ };
3564
+ }
3565
+ scan() {
3566
+ this.doc.descendants((node, position) => {
3567
+ if (!node.isText) {
3568
+ return;
3569
+ }
3570
+ if (!node.text) {
3571
+ return;
3572
+ }
3573
+ const matches = this.regex.exec(node.text);
3574
+ if (matches) {
3575
+ this.record({
3576
+ message: 'Suspicious spacing around punctuation',
3577
+ from: position + matches.index,
3578
+ to: position + matches.index + matches[0].length,
3579
+ fix: {
3580
+ message: 'Fix it!!!',
3581
+ handler: this.fix(`${matches[1]} `),
3582
+ },
3583
+ });
3584
+ }
3585
+ });
3586
+ return this;
3587
+ }
3588
+ }
3589
+
3590
+ const WysiwygColorExtension = Extension.create({
3591
+ name: 'color',
3592
+ addOptions() {
3593
+ return {
3594
+ types: ['textStyle'],
3595
+ };
3596
+ },
3597
+ addGlobalAttributes() {
3598
+ return [
3599
+ {
3600
+ types: this.options.types,
3601
+ attributes: {
3602
+ color: {
3603
+ default: null,
3604
+ parseHTML: (element) => element.style.color.replace(/['"]+/g, ''),
3605
+ renderHTML: (attributes) => {
3606
+ if (!attributes.color) {
3607
+ return {};
3608
+ }
3609
+ return {
3610
+ style: `color: ${attributes.color}`,
3611
+ };
3612
+ },
3613
+ },
3614
+ },
3615
+ },
3616
+ ];
3617
+ },
3618
+ addCommands() {
3619
+ return {
3620
+ setColor: (color) => ({ chain }) => {
3621
+ return chain().setMark('textStyle', { color }).run();
3622
+ },
3623
+ unsetColor: () => ({ chain }) => {
3624
+ return chain()
3625
+ .setMark('textStyle', { color: null })
3626
+ .removeEmptyTextStyle()
3627
+ .run();
3628
+ },
3629
+ };
3630
+ },
3631
+ });
3632
+
3125
3633
  const WysiwygBulletListTool = (vui, options) => {
3126
3634
  const tool = {
3127
3635
  key: 'bulletList',
@@ -3262,18 +3770,73 @@ const WysiwygOrderedListTool = (vui, options) => {
3262
3770
  return tool;
3263
3771
  };
3264
3772
 
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
- };
3773
+ function createWysiwygColorTool(opts) {
3774
+ const WysiwygColorTool = vui => {
3775
+ const tool = {
3776
+ key: 'textColor',
3777
+ // active: ({ editor }) => editor.isActive('textStyle'),
3778
+ icon: ({
3779
+ editor
3780
+ }) => () => {
3781
+ const color = editor.getAttributes('textStyle').color;
3782
+ const style = {
3783
+ color
3784
+ };
3785
+ return createVNode("span", {
3786
+ "class": "v-wysiwyg-color-tool__button"
3787
+ }, [createVNode(VIcon, {
3788
+ "class": "v-wysiwyg-color-tool__button__icon",
3789
+ "name": vui.icon('editorTextColor')
3790
+ }, null), createVNode("span", {
3791
+ "class": "v-wysiwyg-color-tool__button__bar",
3792
+ "style": style
3793
+ }, null)]);
3794
+ },
3795
+ onClick: (ctx, ev) => {
3796
+ ctx.vui.menu({
3797
+ class: 'v-wysiwyg-color-tool__menu',
3798
+ activator: ev,
3799
+ content: stack => createVNode("div", {
3800
+ "class": ['v-wysiwyg-color-tool__items', {
3801
+ 'v-wysiwyg-color-tool__items--with-label': opts.withLabel
3802
+ }]
3803
+ }, [opts.items.map((item, index) => createVNode("button", {
3804
+ "key": item.key == null ? index : item.key,
3805
+ "class": "v-wysiwyg-color-tool__item",
3806
+ "type": "button",
3807
+ "onClick": () => {
3808
+ const {
3809
+ color
3810
+ } = item;
3811
+ let command = ctx.editor.chain().focus();
3812
+
3813
+ if (color) {
3814
+ command = command.setColor(color);
3815
+ } else {
3816
+ command = command.unsetColor();
3817
+ }
3818
+
3819
+ command.run();
3820
+ stack.close();
3821
+ }
3822
+ }, [createVNode("span", {
3823
+ "class": "v-wysiwyg-color-tool__item__color",
3824
+ "style": item.color ? {
3825
+ color: item.color
3826
+ } : {}
3827
+ }, null), opts.withLabel && createVNode("span", {
3828
+ "class": "v-wysiwyg-color-tool__item__name"
3829
+ }, [item.name])]))])
3830
+ });
3831
+ },
3832
+ floating: true,
3833
+ extensions: [TextStyle, WysiwygColorExtension]
3834
+ };
3835
+ return tool;
3836
+ };
3837
+
3838
+ return WysiwygColorTool;
3839
+ }
3277
3840
 
3278
3841
  const {
3279
3842
  props,
@@ -4890,6 +5453,10 @@ class VuiService {
4890
5453
  return this.stack.snackbar(...args);
4891
5454
  }
4892
5455
 
5456
+ menu(...args) {
5457
+ return this.stack.menu(...args);
5458
+ }
5459
+
4893
5460
  formPrompt(settings, slot) {
4894
5461
  let form;
4895
5462
  const options = {
@@ -5041,4 +5608,4 @@ function installVuiPlugin(app, opts) {
5041
5608
  return app.use(VuiPlugin, opts);
5042
5609
  }
5043
5610
 
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 };
5611
+ 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 };