@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.cjs.js +382 -20
- package/dist/vui.cjs.prod.js +382 -20
- package/dist/vui.css +65 -0
- package/dist/vui.d.ts +89 -1
- package/dist/vui.min.css +1 -1
- package/dist/vui.min.css.map +1 -1
- package/dist/vui.mjs +377 -22
- package/package.json +6 -6
- package/src/components/VWysiwygEditor/.DS_Store +0 -0
- package/src/components/VWysiwygEditor/VWysiwygEditor.tsx +48 -15
- package/src/components/VWysiwygEditor/extensions/.DS_Store +0 -0
- package/src/components/VWysiwygEditor/extensions/index.ts +1 -0
- package/src/components/VWysiwygEditor/extensions/linter/.DS_Store +0 -0
- package/src/components/VWysiwygEditor/extensions/linter/Linter.scss +75 -0
- package/src/components/VWysiwygEditor/extensions/linter/Linter.ts +262 -0
- package/src/components/VWysiwygEditor/extensions/linter/LinterPlugin.ts +44 -0
- package/src/components/VWysiwygEditor/extensions/linter/index.ts +5 -0
- package/src/components/VWysiwygEditor/extensions/linter/plugins/BadWords.ts +34 -0
- package/src/components/VWysiwygEditor/extensions/linter/plugins/HeadingLevel.ts +33 -0
- package/src/components/VWysiwygEditor/extensions/linter/plugins/Punctuation.ts +43 -0
- package/src/components/VWysiwygEditor/extensions/linter/plugins/index.ts +3 -0
- package/src/components/VWysiwygEditor/index.ts +1 -0
- package/src/components/VWysiwygEditor/schemes.ts +107 -3
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(
|
|
2928
|
+
function resolveRawWysiwygEditorTools(rawTools, vui) {
|
|
2876
2929
|
const tools = [];
|
|
2877
2930
|
const extensions = [];
|
|
2878
|
-
|
|
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
|
-
}),
|
|
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
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
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.
|
|
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.
|
|
36
|
-
"@fastkit/icon-font": "0.7.
|
|
37
|
-
"@fastkit/tiny-logger": "0.7.
|
|
38
|
-
"@fastkit/vite-kit": "0.7.
|
|
39
|
-
"@fastkit/vue-kit": "0.7.
|
|
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",
|
|
Binary file
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import './VWysiwygEditor.scss';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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(),
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './linter';
|
|
Binary file
|
|
@@ -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
|
+
}
|