@fastkit/vui 0.7.61 → 0.7.66

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 +443 -228
  4. package/dist/vui.cjs.prod.js +443 -228
  5. package/dist/vui.css +166 -12
  6. package/dist/vui.d.ts +103 -54
  7. package/dist/vui.min.css +1 -1
  8. package/dist/vui.min.css.map +1 -1
  9. package/dist/vui.mjs +437 -225
  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/VWysiwygEditor.scss +4 -0
  14. package/src/components/VWysiwygEditor/VWysiwygEditor.tsx +19 -9
  15. package/src/components/VWysiwygEditor/extensions/.DS_Store +0 -0
  16. package/src/components/VWysiwygEditor/extensions/color.ts +72 -0
  17. package/src/components/VWysiwygEditor/extensions/index.ts +1 -0
  18. package/src/components/VWysiwygEditor/extensions/linter/.DS_Store +0 -0
  19. package/src/components/VWysiwygEditor/extensions/linter/Linter.scss +80 -15
  20. package/src/components/VWysiwygEditor/extensions/linter/Linter.tsx +214 -0
  21. package/src/components/VWysiwygEditor/extensions/linter/LinterPlugin.ts +32 -11
  22. package/src/components/VWysiwygEditor/extensions/linter/index.ts +3 -3
  23. package/src/components/VWysiwygEditor/extensions/linter/plugins/BadWords.tsx +50 -0
  24. package/src/components/VWysiwygEditor/extensions/linter/plugins/HeadingLevel.ts +9 -3
  25. package/src/components/VWysiwygEditor/extensions/linter/plugins/Punctuation.ts +9 -3
  26. package/src/components/VWysiwygEditor/extensions/linter/plugins/index.ts +3 -3
  27. package/src/components/VWysiwygEditor/extensions/linter/utils.ts +28 -0
  28. package/src/components/VWysiwygEditor/schemes.ts +87 -5
  29. package/src/components/VWysiwygEditor/tools/color.scss +140 -0
  30. package/src/components/VWysiwygEditor/tools/color.tsx +90 -0
  31. package/src/components/VWysiwygEditor/tools/index.ts +1 -1
  32. package/src/service.tsx +5 -0
  33. package/src/tool/vite-plugin.ts +1 -0
  34. package/src/components/VWysiwygEditor/extensions/linter/Linter.ts +0 -262
  35. package/src/components/VWysiwygEditor/extensions/linter/plugins/BadWords.ts +0 -34
  36. package/src/components/VWysiwygEditor/tools/text-color.ts +0 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastkit/vui",
3
- "version": "0.7.61",
3
+ "version": "0.7.66",
4
4
  "description": "@fastkit/vui",
5
5
  "buildOptions": {
6
6
  "name": "Vui",
@@ -32,13 +32,14 @@
32
32
  "vue": "^3.2.26"
33
33
  },
34
34
  "dependencies": {
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",
35
+ "@fastkit/color-scheme": "0.7.66",
36
+ "@fastkit/icon-font": "0.7.66",
37
+ "@fastkit/tiny-logger": "0.7.66",
38
+ "@fastkit/vite-kit": "0.7.66",
39
+ "@fastkit/vue-kit": "0.7.66",
40
40
  "@tiptap/extension-link": "^2.0.0-beta.36",
41
41
  "@tiptap/extension-underline": "^2.0.0-beta.23",
42
+ "@tiptap/extension-text-style": "^2.0.0-beta.23",
42
43
  "@tiptap/starter-kit": "^2.0.0-beta.183",
43
44
  "@tiptap/vue-3": "^2.0.0-beta.90",
44
45
  "eta": "^1.12.3",
@@ -20,4 +20,8 @@
20
20
  z-index: 1;
21
21
  }
22
22
  }
23
+
24
+ // &--has-icon &__item {
25
+
26
+ // }
23
27
  }
@@ -25,6 +25,7 @@ export const VButtonGroup = defineComponent({
25
25
  },
26
26
  setup(props, ctx) {
27
27
  return () => {
28
+ let hasIcon = false;
28
29
  let buttonLength = 0;
29
30
 
30
31
  let tmp = renderSlotOrEmpty(ctx.slots) || [];
@@ -43,6 +44,10 @@ export const VButtonGroup = defineComponent({
43
44
 
44
45
  buttonLength++;
45
46
 
47
+ if (!hasIcon && node.props && !!node.props.icon) {
48
+ hasIcon = true;
49
+ }
50
+
46
51
  return {
47
52
  isButton: true,
48
53
  node,
@@ -67,6 +72,7 @@ export const VButtonGroup = defineComponent({
67
72
  class: [
68
73
  'v-button-group__item',
69
74
  {
75
+ 'v-button--icon': hasIcon,
70
76
  'v-button-group__item--has-left': hasLeft,
71
77
  'v-button-group__item--has-right': hasRight,
72
78
  },
@@ -75,7 +81,12 @@ export const VButtonGroup = defineComponent({
75
81
  }
76
82
  return child.node;
77
83
  });
78
- return <div class="v-button-group">{$children}</div>;
84
+ return (
85
+ <div
86
+ class={['v-button-group', { 'v-button-group--has-icon': hasIcon }]}>
87
+ {$children}
88
+ </div>
89
+ );
79
90
  };
80
91
  },
81
92
  });
@@ -20,6 +20,10 @@
20
20
  position: relative;
21
21
  }
22
22
 
23
+ .v-control-field__body {
24
+ width: 100%;
25
+ }
26
+
23
27
  &__floating-toolbar {
24
28
  position: absolute;
25
29
  right: 0;
@@ -6,6 +6,7 @@ import {
6
6
  ref,
7
7
  watch,
8
8
  onBeforeUnmount,
9
+ VNodeChild,
9
10
  } from 'vue';
10
11
  import {
11
12
  createFormControlProps,
@@ -49,7 +50,6 @@ import {
49
50
  // EditorEventsOptions,
50
51
  WysiwygEditorInitializeContext,
51
52
  } from './schemes';
52
- // import { Linter, BadWords, Punctuation, HeadingLevel } from './extensions';
53
53
 
54
54
  export const VWysiwygEditor = defineComponent({
55
55
  name: 'VWysiwygEditor',
@@ -94,7 +94,7 @@ export const VWysiwygEditor = defineComponent({
94
94
  validationValue: () => textRef.value,
95
95
  });
96
96
 
97
- const initializeCtx = new WysiwygEditorInitializeContext({
97
+ const initializeCtx = new WysiwygEditorInitializeContext(() => vui, {
98
98
  onCreate: ({ editor }) => {
99
99
  textRef.value = editor.getText();
100
100
  updateEditable();
@@ -206,9 +206,12 @@ export const VWysiwygEditor = defineComponent({
206
206
 
207
207
  return (
208
208
  <VButtonGroup
209
- class={{
210
- 'v-wysiwyg-editor__floating-toolbar': props.floatingToolbar,
211
- }}
209
+ class={[
210
+ 'v-wysiwyg-editor__toolbar',
211
+ {
212
+ 'v-wysiwyg-editor__floating-toolbar': props.floatingToolbar,
213
+ },
214
+ ]}
212
215
  variant={variant}>
213
216
  {tools.map(({ key, icon, onClick, disabled, active }) => {
214
217
  const isActive =
@@ -217,16 +220,22 @@ export const VWysiwygEditor = defineComponent({
217
220
  !inputControl.canOperation ||
218
221
  (!!_editor && resolveContextableValue(context, disabled));
219
222
  let iconName: IconName | undefined;
223
+ let child: VNodeChild;
220
224
 
221
225
  if (typeof icon === 'function') {
222
226
  if (_editor) {
223
- iconName = icon(context);
227
+ const _child = icon(context);
228
+ if (typeof _child === 'function') {
229
+ child = _child();
230
+ } else {
231
+ iconName = child as any;
232
+ }
224
233
  }
225
234
  } else {
226
235
  iconName = icon;
227
236
  }
228
237
 
229
- if (!iconName) return;
238
+ if (!iconName && child == null) return;
230
239
 
231
240
  return (
232
241
  <VButton
@@ -235,8 +244,9 @@ export const VWysiwygEditor = defineComponent({
235
244
  icon={iconName}
236
245
  onClick={(ev) => onClick(context, ev)}
237
246
  color={isActive ? toolButtonActiveColor : undefined}
238
- disabled={isDisabled}
239
- />
247
+ disabled={isDisabled}>
248
+ {child}
249
+ </VButton>
240
250
  );
241
251
  })}
242
252
  </VButtonGroup>
@@ -0,0 +1,72 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import '@tiptap/extension-text-style';
3
+
4
+ export type WysiwygColorOptions = {
5
+ types: string[];
6
+ };
7
+
8
+ declare module '@tiptap/core' {
9
+ interface Commands<ReturnType> {
10
+ color: {
11
+ /**
12
+ * Set the text color
13
+ */
14
+ setColor: (color: string) => ReturnType;
15
+ /**
16
+ * Unset the text color
17
+ */
18
+ unsetColor: () => ReturnType;
19
+ };
20
+ }
21
+ }
22
+
23
+ export const WysiwygColorExtension = Extension.create<WysiwygColorOptions>({
24
+ name: 'color',
25
+
26
+ addOptions() {
27
+ return {
28
+ types: ['textStyle'],
29
+ };
30
+ },
31
+
32
+ addGlobalAttributes() {
33
+ return [
34
+ {
35
+ types: this.options.types,
36
+ attributes: {
37
+ color: {
38
+ default: null,
39
+ parseHTML: (element) => element.style.color.replace(/['"]+/g, ''),
40
+ renderHTML: (attributes) => {
41
+ if (!attributes.color) {
42
+ return {};
43
+ }
44
+
45
+ return {
46
+ style: `color: ${attributes.color}`,
47
+ };
48
+ },
49
+ },
50
+ },
51
+ },
52
+ ];
53
+ },
54
+
55
+ addCommands() {
56
+ return {
57
+ setColor:
58
+ (color) =>
59
+ ({ chain }) => {
60
+ return chain().setMark('textStyle', { color }).run();
61
+ },
62
+ unsetColor:
63
+ () =>
64
+ ({ chain }) => {
65
+ return chain()
66
+ .setMark('textStyle', { color: null })
67
+ .removeEmptyTextStyle()
68
+ .run();
69
+ },
70
+ };
71
+ },
72
+ });
@@ -1 +1,2 @@
1
1
  export * from './linter';
2
+ export * from './color';
@@ -40,26 +40,91 @@
40
40
  }
41
41
 
42
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
43
  font-size: 87.5%;
44
+ // font-size: 12px;
51
45
  line-height: 1.5;
52
- pointer-events: none;
53
- background: #fff;
54
- border-radius: 4px;
55
- opacity: 0;
56
- transition: opacity 0.15s;
57
46
 
58
- &--active {
59
- pointer-events: auto;
60
- opacity: 1;
47
+ .v-dialog__body {
48
+ padding: 0;
49
+ font-size: inherit;
50
+ }
51
+
52
+ &__message {
53
+ padding: 8px;
54
+ }
55
+
56
+ &__fixers {
57
+ position: relative;
58
+ padding: 4px 0;
59
+ // padding: 8px;
60
+ // border-top: solid 1px var(--divider-color);
61
+ &::before {
62
+ position: absolute;
63
+ top: 0;
64
+ right: 0;
65
+ left: 0;
66
+ display: block;
67
+ height: 1px;
68
+ content: '';
69
+ background: currentColor;
70
+ opacity: 0.5;
71
+ }
61
72
  }
62
73
 
74
+ &__fixer {
75
+ &__button {
76
+ position: relative;
77
+ display: block;
78
+ width: 100%;
79
+ padding: 8px;
80
+ font: inherit;
81
+ font-size: 12px;
82
+ // margin-top: 8px;
83
+ // color: var(--palette-link);
84
+ text-align: left;
85
+ // text-decoration: underline;
86
+ cursor: pointer;
87
+ background: transparent;
88
+ border: 0;
89
+ outline: 0;
90
+ appearance: none;
91
+
92
+ &::before {
93
+ position: absolute;
94
+ top: 0;
95
+ right: 0;
96
+ bottom: 0;
97
+ left: 0;
98
+ display: block;
99
+ content: '';
100
+ background: currentColor;
101
+ opacity: 0;
102
+ transition: 0.15s;
103
+ }
104
+
105
+ &:hover,
106
+ &:focus {
107
+ &::before {
108
+ opacity: 0.1;
109
+ }
110
+ }
111
+
112
+ &__message {
113
+ position: relative;
114
+ }
115
+ }
116
+ }
117
+ // pointer-events: none;
118
+ // background: #fff;
119
+ // border-radius: 4px;
120
+ // opacity: 0;
121
+ // transition: opacity 0.15s;
122
+
123
+ // &--active {
124
+ // pointer-events: auto;
125
+ // opacity: 1;
126
+ // }
127
+
63
128
  &__fix {
64
129
  display: inline;
65
130
  margin-top: 8px;
@@ -0,0 +1,214 @@
1
+ import './Linter.scss';
2
+
3
+ import { Extension } from '@tiptap/core';
4
+ import { Decoration, DecorationSet } from 'prosemirror-view';
5
+ import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
6
+ import { Node as ProsemirrorNode } from 'prosemirror-model';
7
+ import {
8
+ WysiwygLinterPlugin,
9
+ WysiwygLinterResult as Issue,
10
+ WysiwygLinterFixMessage,
11
+ } from './LinterPlugin';
12
+ import { EditorView } from 'prosemirror-view';
13
+ import { createWysiwygExtension } from '../../schemes';
14
+
15
+ const PROBLEM_CLASS_NAME = 'v-linter__problem';
16
+ const ISSUE_ICON_CLASS_NAME = 'v-linter__issue-icon';
17
+ const ISSUE_DATASET_NAME = 'data-issue-id';
18
+
19
+ function resolveWysiwygLinterFixMessage(message: WysiwygLinterFixMessage) {
20
+ return typeof message === 'function' ? message() : message;
21
+ }
22
+
23
+ function renderIcon(view: EditorView<any>, issue: Issue) {
24
+ const { level = 'warning' } = issue;
25
+ const icon = document.createElement('div');
26
+ const message = resolveWysiwygLinterFixMessage(issue.message);
27
+
28
+ icon.className = `${ISSUE_ICON_CLASS_NAME} ${level}-scope`;
29
+ icon.setAttribute(ISSUE_DATASET_NAME, issue.id);
30
+
31
+ if (typeof message === 'string') {
32
+ icon.title = message;
33
+ }
34
+
35
+ return icon;
36
+ }
37
+
38
+ function runAllLinterPlugins(
39
+ doc: ProsemirrorNode,
40
+ plugins: Array<typeof WysiwygLinterPlugin>,
41
+ ) {
42
+ const decorations: [any?] = [];
43
+
44
+ const issues = plugins
45
+ .map((RegisteredLinterPlugin) => {
46
+ return new RegisteredLinterPlugin(doc).scan().getResults();
47
+ })
48
+ .flat();
49
+
50
+ issues.forEach((issue) => {
51
+ const { level = 'warning', icon } = issue;
52
+ const message = resolveWysiwygLinterFixMessage(issue.message);
53
+ decorations.push(
54
+ Decoration.inline(issue.from, issue.to, {
55
+ class: `${PROBLEM_CLASS_NAME} ${level}-scope`,
56
+ 'data-issue-id': issue.id,
57
+ title: typeof message === 'string' ? message : undefined,
58
+ }),
59
+ );
60
+ if (icon) {
61
+ decorations.push(
62
+ Decoration.widget(issue.from, (view) => renderIcon(view, issue)),
63
+ );
64
+ }
65
+ });
66
+
67
+ const decorationSet = DecorationSet.create(doc, decorations);
68
+
69
+ return {
70
+ issues,
71
+ decorationSet,
72
+ };
73
+ }
74
+
75
+ export interface WysiwygLinterOptions {
76
+ plugins: Array<typeof WysiwygLinterPlugin>;
77
+ }
78
+
79
+ export interface WysiwygLinterStorage {
80
+ issues: Issue[];
81
+ }
82
+
83
+ export const WysiwygLinter = createWysiwygExtension((ctx) => {
84
+ function showMenu(view: EditorView<any>, issue: Issue, event: Event) {
85
+ const message = resolveWysiwygLinterFixMessage(issue.message);
86
+ const variant = ctx.vui.setting('containedVariant');
87
+ const scope = ctx.vui.setting(`${issue.level}Scope`);
88
+
89
+ return ctx.vui.menu({
90
+ activator: event,
91
+ content: (stack) => (
92
+ <div class="v-linter__issue-menu__body">
93
+ <div
94
+ class={[
95
+ 'v-linter__issue-menu__message',
96
+ `${scope}-scope ${variant}`,
97
+ ]}>
98
+ {message}
99
+ </div>
100
+ {issue.fix.length && (
101
+ <div class="v-linter__issue-menu__fixers">
102
+ {issue.fix.map((fixer, index) => (
103
+ <div key={index} class="v-linter__issue-menu__fixer">
104
+ <button
105
+ type="button"
106
+ class="v-linter__issue-menu__fixer__button"
107
+ onClick={() => {
108
+ stack.close();
109
+ fixer.handler(view, issue);
110
+ }}>
111
+ <span class="v-linter__issue-menu__fixer__button__message">
112
+ {resolveWysiwygLinterFixMessage(fixer.message)}
113
+ </span>
114
+ </button>
115
+ </div>
116
+ ))}
117
+ </div>
118
+ )}
119
+ </div>
120
+ ),
121
+ });
122
+ }
123
+
124
+ return Extension.create<WysiwygLinterOptions, WysiwygLinterStorage>({
125
+ name: 'linter',
126
+
127
+ addOptions() {
128
+ return {
129
+ plugins: [],
130
+ };
131
+ },
132
+ addStorage() {
133
+ return {
134
+ issues: [],
135
+ };
136
+ },
137
+ addProseMirrorPlugins() {
138
+ const { plugins } = this.options;
139
+ const { storage } = this;
140
+
141
+ const runAll = (doc: ProsemirrorNode) => {
142
+ const { issues, decorationSet } = runAllLinterPlugins(doc, plugins);
143
+ storage.issues = issues;
144
+ return decorationSet;
145
+ };
146
+
147
+ const getIssue = (id: string) =>
148
+ storage.issues.find((issue) => issue.id === id);
149
+
150
+ const getIssueElementByEvent = (
151
+ source: EventTarget | null,
152
+ ):
153
+ | {
154
+ issue: Issue;
155
+ }
156
+ | undefined => {
157
+ if (!source || !(source instanceof HTMLElement)) {
158
+ return;
159
+ }
160
+ const issueId = source.getAttribute(ISSUE_DATASET_NAME);
161
+ const issue = issueId && getIssue(issueId);
162
+ if (!issue) return;
163
+
164
+ return {
165
+ issue,
166
+ };
167
+ };
168
+
169
+ return [
170
+ new Plugin({
171
+ key: new PluginKey('linter'),
172
+ state: {
173
+ init(_, { doc }) {
174
+ return runAll(doc);
175
+ },
176
+ apply(transaction, oldState) {
177
+ return transaction.docChanged
178
+ ? runAll(transaction.doc)
179
+ : oldState;
180
+ },
181
+ },
182
+ props: {
183
+ decorations(state) {
184
+ return this.getState(state);
185
+ },
186
+ handleClick(view, _, event) {
187
+ const info = getIssueElementByEvent(event.target);
188
+ if (!info) {
189
+ return false;
190
+ }
191
+
192
+ const { issue } = info;
193
+ const { from, to } = issue;
194
+
195
+ const focus = () =>
196
+ view.dispatch(
197
+ view.state.tr
198
+ .setSelection(
199
+ TextSelection.create(view.state.doc, from, to),
200
+ )
201
+ .scrollIntoView(),
202
+ );
203
+
204
+ focus();
205
+
206
+ showMenu(view, issue, event);
207
+ return true;
208
+ },
209
+ },
210
+ }),
211
+ ];
212
+ },
213
+ });
214
+ });
@@ -1,36 +1,57 @@
1
+ import { VNodeChild } from 'vue';
1
2
  import { Node as ProsemirrorNode } from 'prosemirror-model';
2
3
  import { EditorView } from 'prosemirror-view';
4
+ import { cheepUUID } from './utils';
3
5
 
4
- export type FixFn = (view: EditorView, issue: LinterResult) => any;
6
+ export type WysiwygLinterFixFn = (
7
+ view: EditorView,
8
+ issue: WysiwygLinterResult,
9
+ ) => any;
5
10
 
6
- export type LinterResultLevel = 'warning' | 'error';
11
+ export type WysiwygLinterFixMessage = string | (() => VNodeChild);
7
12
 
8
- export interface LinterResult {
9
- level: LinterResultLevel;
10
- message: string;
13
+ export interface WysiwygLinterFixer {
14
+ message: WysiwygLinterFixMessage;
15
+ handler: WysiwygLinterFixFn;
16
+ }
17
+
18
+ export type WysiwygLinterResultLevel = 'warning' | 'error';
19
+
20
+ // fixers
21
+ export interface WysiwygLinterResult {
22
+ id: string;
23
+ icon: boolean;
24
+ level: WysiwygLinterResultLevel;
25
+ message: WysiwygLinterFixMessage;
11
26
  from: number;
12
27
  to: number;
13
- fix?: FixFn;
14
- fixMessage?: string;
28
+ fix: WysiwygLinterFixer[];
15
29
  }
16
30
 
17
- export interface RawLinterResult extends Omit<LinterResult, 'level'> {
18
- level?: LinterResultLevel;
31
+ export interface RawLinterResult
32
+ extends Omit<WysiwygLinterResult, 'level' | 'fix' | 'id' | 'icon'> {
33
+ level?: WysiwygLinterResultLevel;
34
+ icon?: boolean;
35
+ fix?: WysiwygLinterFixer | WysiwygLinterFixer[];
19
36
  }
20
37
 
21
- export class LinterPlugin {
38
+ export class WysiwygLinterPlugin {
22
39
  protected doc: ProsemirrorNode;
23
40
 
24
- private results: Array<LinterResult> = [];
41
+ private results: Array<WysiwygLinterResult> = [];
25
42
 
26
43
  constructor(doc: ProsemirrorNode) {
27
44
  this.doc = doc;
28
45
  }
29
46
 
30
47
  record(result: RawLinterResult) {
48
+ const { fix = [], icon = false } = result;
31
49
  this.results.push({
32
50
  level: 'error',
51
+ id: cheepUUID(),
33
52
  ...result,
53
+ fix: Array.isArray(fix) ? fix : [fix],
54
+ icon,
34
55
  });
35
56
  }
36
57
 
@@ -1,5 +1,5 @@
1
- export { Linter } from './Linter';
2
- export { LinterPlugin } from './LinterPlugin';
3
- export type { LinterOptions } from './Linter';
1
+ export { WysiwygLinter } from './Linter';
2
+ export { WysiwygLinterPlugin } from './LinterPlugin';
3
+ export type { WysiwygLinterOptions } from './Linter';
4
4
 
5
5
  export * from './plugins';
@@ -0,0 +1,50 @@
1
+ import { WysiwygLinterPlugin } from '../LinterPlugin';
2
+
3
+ export function WysiwygLinterBadWords(
4
+ words: string[],
5
+ ): typeof WysiwygLinterPlugin {
6
+ const regex = new RegExp(`\\b(${words.join('|')})\\b`);
7
+
8
+ return class WysiwygLinterBadWords extends WysiwygLinterPlugin {
9
+ scan() {
10
+ this.doc.descendants((node: any, position: number) => {
11
+ if (!node.isText) {
12
+ return;
13
+ }
14
+
15
+ const matches = regex.exec(node.text);
16
+
17
+ if (matches) {
18
+ const fixValue = matches[0] + '!!!!!';
19
+
20
+ this.record({
21
+ level: 'warning',
22
+ message: `Try not to say '${matches[0]}'`,
23
+ from: position + matches.index,
24
+ to: position + matches.index + matches[0].length,
25
+ fix: [
26
+ {
27
+ message: () => (
28
+ <span>
29
+ <code>{fixValue}</code>に修正する。
30
+ </span>
31
+ ),
32
+ handler: () => {
33
+ console.log('hoge');
34
+ },
35
+ },
36
+ {
37
+ message: 'どうにかする',
38
+ handler: () => {
39
+ console.log('hoge');
40
+ },
41
+ },
42
+ ],
43
+ });
44
+ }
45
+ });
46
+
47
+ return this;
48
+ }
49
+ };
50
+ }