@fastkit/vui 0.7.59 → 0.7.61

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/vui.mjs CHANGED
@@ -8,12 +8,15 @@ import { getDocumentScroller as getDocumentScroller$1 } from '@fastkit/vue-scrol
8
8
  import { VProgressCircular } from '@fastkit/vue-loading';
9
9
  export * from '@fastkit/vue-loading';
10
10
  import { useScopeColorClass, colorSchemeProps, useColorClasses, toScopeColorClass } from '@fastkit/vue-color-scheme';
11
- import { loadImage, IN_WINDOW, isPromise } from '@fastkit/helpers';
11
+ import { loadImage, IN_WINDOW, isPromise, debounce } from '@fastkit/helpers';
12
12
  export { ICON_NAMES } from '@fastkit/icon-font';
13
13
  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 { DecorationSet, Decoration } 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';
@@ -2869,13 +2872,63 @@ const VTextarea = defineComponent({
2869
2872
 
2870
2873
  });
2871
2874
 
2875
+ const EDITOR_EVENTS = [
2876
+ 'beforeCreate',
2877
+ 'create',
2878
+ 'update',
2879
+ 'selectionUpdate',
2880
+ 'transaction',
2881
+ 'focus',
2882
+ 'blur',
2883
+ 'destroy',
2884
+ ];
2885
+ const prefixedEventName = (source) => {
2886
+ return `on${source.charAt(0).toUpperCase()}${source.slice(1)}`;
2887
+ };
2888
+ class WysiwygEditorInitializeContext {
2889
+ listeners = {};
2890
+ constructor(opts = {}) {
2891
+ EDITOR_EVENTS.forEach((event) => {
2892
+ this.listeners[event] = [];
2893
+ const prefixed = prefixedEventName(event);
2894
+ const fn = opts[prefixed];
2895
+ fn && this.listeners[event].push(fn);
2896
+ });
2897
+ }
2898
+ on(ev, handler) {
2899
+ this.listeners[ev].push(handler);
2900
+ return () => this.off(ev, handler);
2901
+ }
2902
+ off(ev, handler) {
2903
+ this.listeners[ev] = this.listeners[ev].filter((_handler) => _handler !== handler);
2904
+ }
2905
+ editorOptions() {
2906
+ const opts = {};
2907
+ EDITOR_EVENTS.forEach((event) => {
2908
+ const prefixed = prefixedEventName(event);
2909
+ opts[prefixed] = (props) => {
2910
+ const handlers = this.listeners[event];
2911
+ handlers.forEach((handler) => {
2912
+ handler(props);
2913
+ });
2914
+ };
2915
+ });
2916
+ return opts;
2917
+ }
2918
+ }
2919
+ function resolveRawWysiwygExtension(raw, ctx) {
2920
+ return typeof raw === 'function' ? raw(ctx) : raw;
2921
+ }
2922
+ function resolveRawWysiwygExtensions(raws, ctx) {
2923
+ return raws.map((raw) => resolveRawWysiwygExtension(raw, ctx));
2924
+ }
2872
2925
  function resolveRawWysiwygEditorTool(raw, vui) {
2873
2926
  return typeof raw === 'function' ? raw(vui) : raw;
2874
2927
  }
2875
- function resolveRawWysiwygEditorTools(raws, vui) {
2928
+ function resolveRawWysiwygEditorTools(rawTools, vui) {
2876
2929
  const tools = [];
2877
2930
  const extensions = [];
2878
- raws.forEach((raw) => {
2931
+ rawTools.forEach((raw) => {
2879
2932
  let resolved = resolveRawWysiwygEditorTool(raw, vui);
2880
2933
  if (!Array.isArray(resolved)) {
2881
2934
  resolved = [resolved];
@@ -2906,6 +2959,10 @@ const VWysiwygEditor = defineComponent({
2906
2959
  ...createControlFieldProviderProps(),
2907
2960
  ...createControlProps(),
2908
2961
  ...defineSlotsProps(),
2962
+ extensions: {
2963
+ type: Array,
2964
+ default: () => []
2965
+ },
2909
2966
  tools: {
2910
2967
  type: Array,
2911
2968
  default: () => []
@@ -2928,13 +2985,40 @@ const VWysiwygEditor = defineComponent({
2928
2985
  nodeType: VUI_WYSIWYG_EDITOR_SYMBOL,
2929
2986
  validationValue: () => textRef.value
2930
2987
  });
2988
+ const initializeCtx = new WysiwygEditorInitializeContext({
2989
+ onCreate: ({
2990
+ editor
2991
+ }) => {
2992
+ textRef.value = editor.getText();
2993
+ updateEditable();
2994
+ },
2995
+ onFocus: ({
2996
+ event
2997
+ }) => {
2998
+ inputControl.focusHandler(event);
2999
+ },
3000
+ onBlur: ({
3001
+ event
3002
+ }) => {
3003
+ inputControl.blurHandler(event);
3004
+ },
3005
+ onUpdate: ({
3006
+ editor
3007
+ }) => {
3008
+ inputControl.value = editor.getHTML();
3009
+ textRef.value = editor.getText();
3010
+ }
3011
+ });
2931
3012
  const extensions = [StarterKit.configure({
2932
3013
  bold: false,
2933
3014
  bulletList: false,
2934
3015
  orderedList: false,
2935
3016
  history: false,
2936
3017
  italic: false
2937
- }), ...wysiwygSettings.value.extensions];
3018
+ }), // Linter.configure({
3019
+ // plugins: [BadWords(['abc', 'evidently']), Punctuation, HeadingLevel],
3020
+ // }),
3021
+ ...resolveRawWysiwygExtensions(props.extensions, initializeCtx), ...wysiwygSettings.value.extensions];
2938
3022
 
2939
3023
  const updateEditable = () => {
2940
3024
  if (!editor.value) return;
@@ -2948,23 +3032,13 @@ const VWysiwygEditor = defineComponent({
2948
3032
  $editor.commands.setContent(modelValue == null ? '' : modelValue, false);
2949
3033
  textRef.value = $editor.getText();
2950
3034
  });
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
- },
3035
+ initializeCtx.on('create', ({
3036
+ editor
3037
+ }) => {
3038
+ textRef.value = editor.getText();
3039
+ updateEditable();
3040
+ });
3041
+ const editor = useEditor({ ...initializeCtx.editorOptions(),
2968
3042
  autofocus: props.autofocus,
2969
3043
  content: props.modelValue,
2970
3044
  extensions,
@@ -3047,6 +3121,9 @@ const VWysiwygEditor = defineComponent({
3047
3121
  });
3048
3122
  };
3049
3123
 
3124
+ onBeforeUnmount(() => {
3125
+ editor.value && editor.value.destroy();
3126
+ });
3050
3127
  return {
3051
3128
  editor,
3052
3129
  ...inputControl.expose(),
@@ -3122,6 +3199,284 @@ function resolveContextableValue(ctx, source) {
3122
3199
  return typeof source === 'function' ? source(ctx) : source;
3123
3200
  }
3124
3201
 
3202
+ const PROBLEM_CLASS_NAME = 'v-linter__problem';
3203
+ const ISSUE_ICON_CLASS_NAME = 'v-linter__issue-icon';
3204
+ const ISSUE_MENU_CLASS_NAME = 'v-linter__issue-menu';
3205
+ function renderIcon(view, issue) {
3206
+ const { level = 'warning' } = issue;
3207
+ const icon = document.createElement('div');
3208
+ icon.className = `${ISSUE_ICON_CLASS_NAME} ${level}-scope`;
3209
+ icon.title = issue.message;
3210
+ icon.issue = issue;
3211
+ return icon;
3212
+ }
3213
+ function renderMenu(view, issue) {
3214
+ const menuWrapper = document.createElement('div');
3215
+ menuWrapper.className = 'v-linter__issue-menu-wrapper';
3216
+ const menu = document.createElement('div');
3217
+ menu.className = `${ISSUE_MENU_CLASS_NAME} elevation-3`;
3218
+ menu.innerText = issue.message;
3219
+ const { fix, fixMessage } = issue;
3220
+ if (fix && fixMessage) {
3221
+ const $fix = document.createElement('button');
3222
+ $fix.type = 'button';
3223
+ $fix.innerHTML = fixMessage;
3224
+ $fix.addEventListener('click', () => {
3225
+ fix(view, issue);
3226
+ });
3227
+ $fix.className = `v-linter__issue-menu__fix`;
3228
+ menu.appendChild($fix);
3229
+ }
3230
+ menu.issue = issue;
3231
+ menuWrapper.appendChild(menu);
3232
+ return menuWrapper;
3233
+ }
3234
+ function getIssueElement(source, className) {
3235
+ if (!source || !(source instanceof HTMLElement)) {
3236
+ return;
3237
+ }
3238
+ if (source.classList.contains(className)) {
3239
+ return source;
3240
+ }
3241
+ const el = source.closest(`.${className}`);
3242
+ if (el) {
3243
+ return el;
3244
+ }
3245
+ }
3246
+ function getIconElement(source) {
3247
+ return getIssueElement(source, ISSUE_ICON_CLASS_NAME);
3248
+ }
3249
+ function getMenuElement(source) {
3250
+ return getIssueElement(source, ISSUE_MENU_CLASS_NAME);
3251
+ }
3252
+ function getProblemElement(source) {
3253
+ return getIssueElement(source, PROBLEM_CLASS_NAME);
3254
+ }
3255
+ function runAllLinterPlugins(doc, plugins) {
3256
+ const decorations = [];
3257
+ const results = plugins
3258
+ .map((RegisteredLinterPlugin) => {
3259
+ return new RegisteredLinterPlugin(doc).scan().getResults();
3260
+ })
3261
+ .flat();
3262
+ results.forEach((issue) => {
3263
+ const { level = 'warning' } = issue;
3264
+ decorations.push(Decoration.inline(issue.from, issue.to, {
3265
+ class: `${PROBLEM_CLASS_NAME} ${level}-scope`,
3266
+ 'data-issue': JSON.stringify(issue),
3267
+ title: issue.message,
3268
+ }), Decoration.widget(issue.from, (view) => renderIcon(view, issue)), Decoration.widget(issue.from, (view) => renderMenu(view, issue)));
3269
+ });
3270
+ return DecorationSet.create(doc, decorations);
3271
+ }
3272
+ function updateMenusPosition(editorElement) {
3273
+ const { left, right } = editorElement.getBoundingClientRect();
3274
+ const menus = Array.from(editorElement.querySelectorAll(`.${ISSUE_MENU_CLASS_NAME}`));
3275
+ menus.forEach((menu) => {
3276
+ const { left: menuLeft, right: menuRight } = menu.getBoundingClientRect();
3277
+ const overflow = menuRight - right;
3278
+ let offset = 0;
3279
+ if (overflow > 0) {
3280
+ offset = -overflow;
3281
+ if (menuLeft + offset < left) {
3282
+ offset = 0;
3283
+ }
3284
+ }
3285
+ menu.style.transform = `translateX(${offset}px)`;
3286
+ });
3287
+ }
3288
+ const debouncedUpdateMenusPosition = debounce(updateMenusPosition, 250);
3289
+ const Linter = Extension.create({
3290
+ name: 'linter',
3291
+ addOptions() {
3292
+ return {
3293
+ plugins: [],
3294
+ };
3295
+ },
3296
+ onCreate() {
3297
+ const { dom } = this.editor.view;
3298
+ debouncedUpdateMenusPosition(dom);
3299
+ },
3300
+ onUpdate() {
3301
+ const { dom } = this.editor.view;
3302
+ debouncedUpdateMenusPosition(dom);
3303
+ },
3304
+ addProseMirrorPlugins() {
3305
+ const { plugins } = this.options;
3306
+ return [
3307
+ new Plugin({
3308
+ key: new PluginKey('linter'),
3309
+ state: {
3310
+ init(_, { doc }) {
3311
+ return runAllLinterPlugins(doc, plugins);
3312
+ },
3313
+ apply(transaction, oldState) {
3314
+ return transaction.docChanged
3315
+ ? runAllLinterPlugins(transaction.doc, plugins)
3316
+ : oldState;
3317
+ },
3318
+ },
3319
+ props: {
3320
+ decorations(state) {
3321
+ return this.getState(state);
3322
+ },
3323
+ handleClick(view, _, event) {
3324
+ const activeMenus = Array.from(view.dom.querySelectorAll(`.${ISSUE_MENU_CLASS_NAME}--active`));
3325
+ activeMenus.forEach((menu) => menu.classList.remove(`${ISSUE_MENU_CLASS_NAME}--active`));
3326
+ const problem = getProblemElement(event.target);
3327
+ const issueString = problem && problem.dataset['issue'];
3328
+ const issue = issueString && JSON.parse(issueString);
3329
+ if (issue) {
3330
+ const menuWrapper = problem.previousElementSibling;
3331
+ const menu = menuWrapper &&
3332
+ menuWrapper.querySelector(`.${ISSUE_MENU_CLASS_NAME}`);
3333
+ menu && menu.classList.add(`${ISSUE_MENU_CLASS_NAME}--active`);
3334
+ const { from, to } = issue;
3335
+ view.dispatch(view.state.tr
3336
+ .setSelection(TextSelection.create(view.state.doc, from, to))
3337
+ .scrollIntoView());
3338
+ return true;
3339
+ }
3340
+ const menu = getMenuElement(event.target);
3341
+ if (menu) {
3342
+ menu.classList.add(`${ISSUE_MENU_CLASS_NAME}--active`);
3343
+ }
3344
+ const target = getIconElement(event.target);
3345
+ if (target && target.issue) {
3346
+ const menuWrapper = target.nextElementSibling;
3347
+ const menu = menuWrapper &&
3348
+ menuWrapper.querySelector(`.${ISSUE_MENU_CLASS_NAME}`);
3349
+ menu && menu.classList.add(`${ISSUE_MENU_CLASS_NAME}--active`);
3350
+ const { from, to } = target.issue;
3351
+ view.dispatch(view.state.tr
3352
+ .setSelection(TextSelection.create(view.state.doc, from, to))
3353
+ .scrollIntoView());
3354
+ return true;
3355
+ }
3356
+ return false;
3357
+ },
3358
+ handleDoubleClick(view, _, event) {
3359
+ const target = getIconElement(event.target);
3360
+ if (target && target.issue) {
3361
+ const prob = target.issue;
3362
+ if (prob.fix) {
3363
+ prob.fix(view, prob);
3364
+ view.focus();
3365
+ return true;
3366
+ }
3367
+ }
3368
+ return false;
3369
+ },
3370
+ },
3371
+ }),
3372
+ ];
3373
+ },
3374
+ });
3375
+
3376
+ class LinterPlugin {
3377
+ doc;
3378
+ results = [];
3379
+ constructor(doc) {
3380
+ this.doc = doc;
3381
+ }
3382
+ record(result) {
3383
+ this.results.push({
3384
+ level: 'error',
3385
+ ...result,
3386
+ });
3387
+ }
3388
+ scan() {
3389
+ return this;
3390
+ }
3391
+ getResults() {
3392
+ return this.results;
3393
+ }
3394
+ }
3395
+
3396
+ function BadWords(words) {
3397
+ const regex = new RegExp(`\\b(${words.join('|')})\\b`);
3398
+ return class BadWords extends LinterPlugin {
3399
+ scan() {
3400
+ this.doc.descendants((node, position) => {
3401
+ if (!node.isText) {
3402
+ return;
3403
+ }
3404
+ const matches = regex.exec(node.text);
3405
+ if (matches) {
3406
+ const fixValue = matches[0] + '!!!!!';
3407
+ this.record({
3408
+ level: 'warning',
3409
+ message: `Try not to say '${matches[0]}'`,
3410
+ from: position + matches.index,
3411
+ to: position + matches.index + matches[0].length,
3412
+ fix: () => {
3413
+ console.log('hoge');
3414
+ },
3415
+ fixMessage: `「${fixValue}」に修正する。`,
3416
+ });
3417
+ }
3418
+ });
3419
+ return this;
3420
+ }
3421
+ };
3422
+ }
3423
+
3424
+ class HeadingLevel extends LinterPlugin {
3425
+ fixHeader(level) {
3426
+ return function ({ state, dispatch }, issue) {
3427
+ dispatch(state.tr.setNodeMarkup(issue.from - 1, undefined, { level }));
3428
+ };
3429
+ }
3430
+ scan() {
3431
+ let lastHeadLevel = null;
3432
+ this.doc.descendants((node, position) => {
3433
+ if (node.type.name === 'heading') {
3434
+ // Check whether heading levels fit under the current level
3435
+ const { level } = node.attrs;
3436
+ if (lastHeadLevel != null && level > lastHeadLevel + 1) {
3437
+ this.record({
3438
+ message: `Heading too small (${level} under ${lastHeadLevel})`,
3439
+ from: position + 1,
3440
+ to: position + 1 + node.content.size,
3441
+ fix: this.fixHeader(lastHeadLevel + 1),
3442
+ });
3443
+ }
3444
+ lastHeadLevel = level;
3445
+ }
3446
+ });
3447
+ return this;
3448
+ }
3449
+ }
3450
+
3451
+ class Punctuation extends LinterPlugin {
3452
+ regex = / ([,.!?:]) ?/g;
3453
+ fix(replacement) {
3454
+ return function ({ state, dispatch }, issue) {
3455
+ dispatch(state.tr.replaceWith(issue.from, issue.to, state.schema.text(replacement)));
3456
+ };
3457
+ }
3458
+ scan() {
3459
+ this.doc.descendants((node, position) => {
3460
+ if (!node.isText) {
3461
+ return;
3462
+ }
3463
+ if (!node.text) {
3464
+ return;
3465
+ }
3466
+ const matches = this.regex.exec(node.text);
3467
+ if (matches) {
3468
+ this.record({
3469
+ message: 'Suspicious spacing around punctuation',
3470
+ from: position + matches.index,
3471
+ to: position + matches.index + matches[0].length,
3472
+ fix: this.fix(`${matches[1]} `),
3473
+ });
3474
+ }
3475
+ });
3476
+ return this;
3477
+ }
3478
+ }
3479
+
3125
3480
  const WysiwygBulletListTool = (vui, options) => {
3126
3481
  const tool = {
3127
3482
  key: 'bulletList',
@@ -5041,4 +5396,4 @@ function installVuiPlugin(app, opts) {
5041
5396
  return app.use(VuiPlugin, opts);
5042
5397
  }
5043
5398
 
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 };
5399
+ export { AVATAR_SIZES, BadWords, CHIP_SIZES, CONTROL_FIELD_VARIANTS, CONTROL_LOADING_SPINNER_SIZES, CONTROL_SIZES, HeadingLevel, Linter, LinterPlugin, PAGINATION_ALIGNS, Punctuation, 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, WysiwygEditorInitializeContext, 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, resolveRawWysiwygExtensions, splitAttrs, useControl, useControlField, useElevation, useVui, useVuiColorProvider, vueButtonProps };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastkit/vui",
3
- "version": "0.7.59",
3
+ "version": "0.7.61",
4
4
  "description": "@fastkit/vui",
5
5
  "buildOptions": {
6
6
  "name": "Vui",
@@ -32,11 +32,11 @@
32
32
  "vue": "^3.2.26"
33
33
  },
34
34
  "dependencies": {
35
- "@fastkit/color-scheme": "0.7.59",
36
- "@fastkit/icon-font": "0.7.59",
37
- "@fastkit/tiny-logger": "0.7.59",
38
- "@fastkit/vite-kit": "0.7.59",
39
- "@fastkit/vue-kit": "0.7.59",
35
+ "@fastkit/color-scheme": "0.7.61",
36
+ "@fastkit/icon-font": "0.7.61",
37
+ "@fastkit/tiny-logger": "0.7.61",
38
+ "@fastkit/vite-kit": "0.7.61",
39
+ "@fastkit/vue-kit": "0.7.61",
40
40
  "@tiptap/extension-link": "^2.0.0-beta.36",
41
41
  "@tiptap/extension-underline": "^2.0.0-beta.23",
42
42
  "@tiptap/starter-kit": "^2.0.0-beta.183",
@@ -1,5 +1,12 @@
1
1
  import './VWysiwygEditor.scss';
2
- import { defineComponent, PropType, computed, ref, watch } from 'vue';
2
+ import {
3
+ defineComponent,
4
+ PropType,
5
+ computed,
6
+ ref,
7
+ watch,
8
+ onBeforeUnmount,
9
+ } from 'vue';
3
10
  import {
4
11
  createFormControlProps,
5
12
  FormControlSlots,
@@ -34,10 +41,15 @@ import {
34
41
  import StarterKit from '@tiptap/starter-kit';
35
42
  import { VButton, VButtonGroup } from '../VButton';
36
43
  import {
44
+ RawWysiwygExtension,
45
+ resolveRawWysiwygExtensions,
37
46
  RawWysiwygEditorTool,
38
47
  resolveRawWysiwygEditorTools,
39
48
  WysiwygEditorContext,
49
+ // EditorEventsOptions,
50
+ WysiwygEditorInitializeContext,
40
51
  } from './schemes';
52
+ // import { Linter, BadWords, Punctuation, HeadingLevel } from './extensions';
41
53
 
42
54
  export const VWysiwygEditor = defineComponent({
43
55
  name: 'VWysiwygEditor',
@@ -49,6 +61,10 @@ export const VWysiwygEditor = defineComponent({
49
61
  ...createControlFieldProviderProps(),
50
62
  ...createControlProps(),
51
63
  ...defineSlotsProps<FormControlSlots & InputBoxSlots>(),
64
+ extensions: {
65
+ type: Array as PropType<RawWysiwygExtension[]>,
66
+ default: () => [],
67
+ },
52
68
  tools: {
53
69
  type: Array as PropType<RawWysiwygEditorTool[]>,
54
70
  default: () => [],
@@ -78,6 +94,23 @@ export const VWysiwygEditor = defineComponent({
78
94
  validationValue: () => textRef.value,
79
95
  });
80
96
 
97
+ const initializeCtx = new WysiwygEditorInitializeContext({
98
+ onCreate: ({ editor }) => {
99
+ textRef.value = editor.getText();
100
+ updateEditable();
101
+ },
102
+ onFocus: ({ event }) => {
103
+ inputControl.focusHandler(event);
104
+ },
105
+ onBlur: ({ event }) => {
106
+ inputControl.blurHandler(event);
107
+ },
108
+ onUpdate: ({ editor }) => {
109
+ inputControl.value = editor.getHTML();
110
+ textRef.value = editor.getText();
111
+ },
112
+ });
113
+
81
114
  const extensions = [
82
115
  StarterKit.configure({
83
116
  bold: false,
@@ -86,6 +119,10 @@ export const VWysiwygEditor = defineComponent({
86
119
  history: false,
87
120
  italic: false,
88
121
  }),
122
+ // Linter.configure({
123
+ // plugins: [BadWords(['abc', 'evidently']), Punctuation, HeadingLevel],
124
+ // }),
125
+ ...resolveRawWysiwygExtensions(props.extensions, initializeCtx),
89
126
  ...wysiwygSettings.value.extensions,
90
127
  ];
91
128
 
@@ -110,21 +147,13 @@ export const VWysiwygEditor = defineComponent({
110
147
  },
111
148
  );
112
149
 
150
+ initializeCtx.on('create', ({ editor }) => {
151
+ textRef.value = editor.getText();
152
+ updateEditable();
153
+ });
154
+
113
155
  const editor = useEditor({
114
- onCreate: ({ editor }) => {
115
- textRef.value = editor.getText();
116
- updateEditable();
117
- },
118
- onFocus: (ctx) => {
119
- inputControl.focusHandler(ctx.event);
120
- },
121
- onBlur: (ctx) => {
122
- inputControl.blurHandler(ctx.event);
123
- },
124
- onUpdate: (ev) => {
125
- inputControl.value = ev.editor.getHTML();
126
- textRef.value = ev.editor.getText();
127
- },
156
+ ...initializeCtx.editorOptions(),
128
157
  autofocus: props.autofocus,
129
158
  content: props.modelValue,
130
159
  extensions,
@@ -214,6 +243,10 @@ export const VWysiwygEditor = defineComponent({
214
243
  );
215
244
  };
216
245
 
246
+ onBeforeUnmount(() => {
247
+ editor.value && editor.value.destroy();
248
+ });
249
+
217
250
  return {
218
251
  editor,
219
252
  ...inputControl.expose(),
@@ -0,0 +1 @@
1
+ export * from './linter';
@@ -0,0 +1,75 @@
1
+ .v-linter {
2
+ &__problem {
3
+ margin-bottom: -1px;
4
+ cursor: help;
5
+ background: var(--deep-color);
6
+ border-bottom: 1px solid var(--main-color);
7
+ }
8
+
9
+ &__issue-icon {
10
+ position: absolute;
11
+ right: 2px;
12
+ display: inline-flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ width: 1.2em;
16
+ height: 1.2em;
17
+ margin-top: 0.2em;
18
+ // padding-left: 0.5px;
19
+ font-family: times, georgia, serif;
20
+ font-size: 15px;
21
+ font-weight: 700;
22
+ line-height: 1;
23
+ color: var(--text-color);
24
+ text-align: center;
25
+ cursor: pointer;
26
+ background: var(--main-color);
27
+ border-radius: 100px;
28
+
29
+ &::before {
30
+ content: '!';
31
+ }
32
+ }
33
+
34
+ &__issue-menu-wrapper {
35
+ position: relative;
36
+ display: inline-block;
37
+ width: 0px;
38
+ height: 1em;
39
+ overflow: visible;
40
+ }
41
+
42
+ &__issue-menu {
43
+ position: absolute;
44
+ top: calc(100% + 8px);
45
+ left: 0;
46
+ min-width: 200px;
47
+ max-width: calc(100vw - 40px);
48
+ padding: 8px;
49
+ // font-size: 12px;
50
+ font-size: 87.5%;
51
+ line-height: 1.5;
52
+ pointer-events: none;
53
+ background: #fff;
54
+ border-radius: 4px;
55
+ opacity: 0;
56
+ transition: opacity 0.15s;
57
+
58
+ &--active {
59
+ pointer-events: auto;
60
+ opacity: 1;
61
+ }
62
+
63
+ &__fix {
64
+ display: inline;
65
+ margin-top: 8px;
66
+ color: var(--palette-link);
67
+ text-decoration: underline;
68
+ cursor: pointer;
69
+ background: transparent;
70
+ border: 0;
71
+ outline: 0;
72
+ appearance: none;
73
+ }
74
+ }
75
+ }