@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
@@ -1,7 +1,10 @@
1
1
  import { EditorView } from 'prosemirror-view';
2
- import { LinterPlugin, LinterResult as Issue } from '../LinterPlugin';
2
+ import {
3
+ WysiwygLinterPlugin,
4
+ WysiwygLinterResult as Issue,
5
+ } from '../LinterPlugin';
3
6
 
4
- export class HeadingLevel extends LinterPlugin {
7
+ export class WysiwygLinterHeadingLevel extends WysiwygLinterPlugin {
5
8
  fixHeader(level: number) {
6
9
  return function ({ state, dispatch }: EditorView, issue: Issue) {
7
10
  dispatch(state.tr.setNodeMarkup(issue.from - 1, undefined, { level }));
@@ -21,7 +24,10 @@ export class HeadingLevel extends LinterPlugin {
21
24
  message: `Heading too small (${level} under ${lastHeadLevel})`,
22
25
  from: position + 1,
23
26
  to: position + 1 + node.content.size,
24
- fix: this.fixHeader(lastHeadLevel + 1),
27
+ fix: {
28
+ message: '修正する',
29
+ handler: this.fixHeader(lastHeadLevel + 1),
30
+ },
25
31
  });
26
32
  }
27
33
  lastHeadLevel = level;
@@ -1,7 +1,10 @@
1
1
  import { EditorView } from 'prosemirror-view';
2
- import { LinterPlugin, LinterResult as Issue } from '../LinterPlugin';
2
+ import {
3
+ WysiwygLinterPlugin,
4
+ WysiwygLinterResult as Issue,
5
+ } from '../LinterPlugin';
3
6
 
4
- export class Punctuation extends LinterPlugin {
7
+ export class WysiwygLinterPunctuation extends WysiwygLinterPlugin {
5
8
  public regex = / ([,.!?:]) ?/g;
6
9
 
7
10
  fix(replacement: any) {
@@ -33,7 +36,10 @@ export class Punctuation extends LinterPlugin {
33
36
  message: 'Suspicious spacing around punctuation',
34
37
  from: position + matches.index,
35
38
  to: position + matches.index + matches[0].length,
36
- fix: this.fix(`${matches[1]} `),
39
+ fix: {
40
+ message: 'Fix it!!!',
41
+ handler: this.fix(`${matches[1]} `),
42
+ },
37
43
  });
38
44
  }
39
45
  });
@@ -1,3 +1,3 @@
1
- export { BadWords } from './BadWords';
2
- export { HeadingLevel } from './HeadingLevel';
3
- export { Punctuation } from './Punctuation';
1
+ export * from './BadWords';
2
+ export * from './HeadingLevel';
3
+ export * from './Punctuation';
@@ -0,0 +1,28 @@
1
+ let IDX = 256,
2
+ BUFFER: any;
3
+ const HEX: any = [];
4
+ while (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);
5
+
6
+ export function cheepUUID() {
7
+ let i = 0,
8
+ num,
9
+ out = '';
10
+
11
+ if (!BUFFER || IDX + 16 > 256) {
12
+ BUFFER = Array((i = 256));
13
+ while (i--) BUFFER[i] = (256 * Math.random()) | 0;
14
+ i = IDX = 0;
15
+ }
16
+
17
+ for (; i < 16; i++) {
18
+ num = BUFFER[IDX + i];
19
+ if (i == 6) out += HEX[(num & 15) | 64];
20
+ else if (i == 8) out += HEX[(num & 63) | 128];
21
+ else out += HEX[num];
22
+
23
+ if (i & 1 && i > 1 && i < 11) out += '-';
24
+ }
25
+
26
+ IDX++;
27
+ return out;
28
+ }
@@ -2,10 +2,14 @@ import { type VuiService } from '../../service';
2
2
  import {
3
3
  type Editor,
4
4
  type Extensions,
5
+ type Node,
6
+ type Extension,
7
+ type Mark,
5
8
  type AnyExtension,
6
9
  type EditorOptions,
7
10
  } from '@tiptap/vue-3';
8
11
  import { type IconName } from '../VIcon';
12
+ import { VNodeChild } from 'vue';
9
13
 
10
14
  const EDITOR_EVENTS = [
11
15
  'beforeCreate',
@@ -42,8 +46,18 @@ export type WysiwygEditorEventsBucket = {
42
46
 
43
47
  export class WysiwygEditorInitializeContext {
44
48
  readonly listeners: WysiwygEditorEventsBucket = {} as any;
49
+ private readonly _vui: () => VuiService;
50
+
51
+ get vui() {
52
+ return this._vui();
53
+ }
54
+
55
+ constructor(
56
+ vuiGetter: () => VuiService,
57
+ opts: WysiwygEditorEventsOptions = {},
58
+ ) {
59
+ this._vui = vuiGetter;
45
60
 
46
- constructor(opts: WysiwygEditorEventsOptions = {}) {
47
61
  EDITOR_EVENTS.forEach((event) => {
48
62
  this.listeners[event] = [];
49
63
  const prefixed = prefixedEventName(event);
@@ -91,16 +105,82 @@ export interface WysiwygEditorContext {
91
105
  vui: VuiService;
92
106
  }
93
107
 
94
- export type WysiwygExtensionFactory = (
108
+ export type WysiwygExtensionFactory<Options = any, Storage = any> = (
95
109
  ctx: WysiwygEditorInitializeContext,
96
- ) => AnyExtension;
110
+ ) =>
111
+ | Extension<Options, Storage>
112
+ | Node<Options, Storage>
113
+ | Mark<Options, Storage>;
114
+
115
+ export interface CreatedWysiwygExtension<Options = any, Storage = any> {
116
+ __isCreatedWysiwygExtension: true;
117
+ _configs: Partial<Options>[];
118
+ configure(
119
+ options?: Partial<Options>,
120
+ ): CreatedWysiwygExtension<Options, Storage>;
121
+ raw: WysiwygExtensionSource<Options, Storage>;
122
+ }
123
+
124
+ export type WysiwygExtensionSource<Options = any, Storage = any> =
125
+ | Extension<Options, Storage>
126
+ | Node<Options, Storage>
127
+ | Mark<Options, Storage>
128
+ | WysiwygExtensionFactory<Options, Storage>;
129
+
130
+ export type RawWysiwygExtension<Options = any, Storage = any> =
131
+ | WysiwygExtensionSource<Options, Storage>
132
+ | CreatedWysiwygExtension<Options, Storage>;
133
+
134
+ // export function createWysiwygExtension<Options = any, Storage = any>(
135
+ // extension: Extension<Options, Storage>,
136
+ // ): Extension<Options, Storage>;
137
+ // export function createWysiwygExtension<Options = any, Storage = any>(
138
+ // node: Node<Options, Storage>,
139
+ // ): Node<Options, Storage>;
140
+ // export function createWysiwygExtension<Options = any, Storage = any>(
141
+ // mark: Mark<Options, Storage>,
142
+ // ): Mark<Options, Storage>;
143
+ // export function createWysiwygExtension<Options = any, Storage = any>(
144
+ // factory: WysiwygExtensionFactory<Options, Storage>,
145
+ // ): WysiwygExtensionFactory<Options, Storage>;
146
+
147
+ function isCreatedWysiwygExtension<Options = any, Storage = any>(
148
+ source: unknown,
149
+ ): source is CreatedWysiwygExtension<Options, Storage> {
150
+ return (
151
+ !!source &&
152
+ typeof source === 'object' &&
153
+ (source as CreatedWysiwygExtension).__isCreatedWysiwygExtension === true
154
+ );
155
+ }
97
156
 
98
- export type RawWysiwygExtension = AnyExtension | WysiwygExtensionFactory;
157
+ export function createWysiwygExtension<Options = any, Storage = any>(
158
+ extension: WysiwygExtensionSource<Options, Storage>,
159
+ ) {
160
+ const ext: CreatedWysiwygExtension<Options, Storage> = {
161
+ __isCreatedWysiwygExtension: true,
162
+ _configs: [],
163
+ configure: (opts) => {
164
+ opts && ext._configs.push(opts);
165
+ return ext;
166
+ },
167
+ raw: extension,
168
+ };
169
+ return ext;
170
+ }
99
171
 
100
172
  function resolveRawWysiwygExtension(
101
173
  raw: RawWysiwygExtension,
102
174
  ctx: WysiwygEditorInitializeContext,
103
175
  ): AnyExtension {
176
+ if (isCreatedWysiwygExtension(raw)) {
177
+ const { raw: _raw, _configs } = raw;
178
+ let ext = typeof _raw === 'function' ? _raw(ctx) : _raw;
179
+ _configs.forEach((config) => {
180
+ ext = ext.configure(config);
181
+ });
182
+ return ext;
183
+ }
104
184
  return typeof raw === 'function' ? raw(ctx) : raw;
105
185
  }
106
186
 
@@ -113,7 +193,9 @@ export function resolveRawWysiwygExtensions(
113
193
 
114
194
  export interface WysiwygEditorTool {
115
195
  key: string;
116
- icon: IconName | ((ctx: WysiwygEditorContext) => IconName);
196
+ icon:
197
+ | IconName
198
+ | ((ctx: WysiwygEditorContext) => IconName | (() => VNodeChild));
117
199
  active?: boolean | ((ctx: WysiwygEditorContext) => boolean);
118
200
  disabled?: boolean | ((ctx: WysiwygEditorContext) => boolean);
119
201
  onClick: (ctx: WysiwygEditorContext, ev: MouseEvent) => any;
@@ -0,0 +1,140 @@
1
+ :root {
2
+ --v-wysiwyg-color-tool: 30px;
3
+ }
4
+
5
+ .v-wysiwyg-color-tool {
6
+ &__button {
7
+ position: relative;
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ vertical-align: bottom;
12
+
13
+ &__bar {
14
+ position: absolute;
15
+ right: 0;
16
+ bottom: 0;
17
+ left: 0;
18
+ display: block;
19
+ height: 2px;
20
+ background: currentColor;
21
+ }
22
+ }
23
+
24
+ &__menu {
25
+ .v-dialog__content {
26
+ min-width: 0;
27
+ }
28
+
29
+ .v-dialog__body {
30
+ padding: 2px;
31
+ }
32
+ }
33
+
34
+ &__items {
35
+ --item-size: var(--v-wysiwyg-color-tool);
36
+
37
+ display: inline-flex;
38
+ flex-wrap: wrap;
39
+ align-items: center;
40
+ max-width: calc(var(--item-size) * 5);
41
+ vertical-align: bottom;
42
+
43
+ &--with-label {
44
+ display: flex;
45
+ flex-direction: column;
46
+ // flex-wrap: nowrap;
47
+ align-items: flex-start;
48
+ max-width: none;
49
+ padding: 2px;
50
+ }
51
+ }
52
+
53
+ &__item {
54
+ --focus-offset: calc(var(--item-size) * 0.05);
55
+
56
+ position: relative;
57
+ display: flex;
58
+ flex-basis: var(--item-size);
59
+ align-items: center;
60
+ width: var(--item-size);
61
+ height: var(--item-size);
62
+ padding: 0;
63
+ margin: 0;
64
+ // margin: 2px;
65
+ cursor: pointer;
66
+ background: transparent;
67
+ border: 0;
68
+ border-radius: 0;
69
+ outline: 0;
70
+ appearance: none;
71
+
72
+ &__color {
73
+ position: relative;
74
+ flex: 0 0 var(--item-size);
75
+ width: var(--item-size);
76
+ height: var(--item-size);
77
+
78
+ &::before {
79
+ position: absolute;
80
+ top: 0;
81
+ right: 0;
82
+ bottom: 0;
83
+ left: 0;
84
+ display: block;
85
+ content: '';
86
+ background: currentColor;
87
+ border: solid 1px transparent;
88
+ transition: all 0.15s;
89
+ }
90
+ }
91
+
92
+ &__name {
93
+ position: relative;
94
+ padding: 0 8px;
95
+ font-size: 12px;
96
+ white-space: nowrap;
97
+ }
98
+
99
+ &:hover &__color,
100
+ &:focus &__color {
101
+ &::before {
102
+ top: var(--focus-offset);
103
+ right: var(--focus-offset);
104
+ bottom: var(--focus-offset);
105
+ left: var(--focus-offset);
106
+ border-color: rgba(0, 0, 0, 0.1);
107
+ }
108
+ }
109
+ }
110
+
111
+ &__items--with-label &__item {
112
+ flex-basis: auto;
113
+ width: auto;
114
+ width: 100%;
115
+
116
+ &::before {
117
+ position: absolute;
118
+ top: 0;
119
+ right: 0;
120
+ bottom: 0;
121
+ left: 0;
122
+ display: block;
123
+ content: '';
124
+ background: currentColor;
125
+ opacity: 0;
126
+ transition: opacity 0.15s;
127
+ }
128
+
129
+ &:hover,
130
+ &:focus {
131
+ &::before {
132
+ opacity: 0.1;
133
+ }
134
+ }
135
+
136
+ & + .v-wysiwyg-color-tool__item {
137
+ margin-top: 2px;
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,90 @@
1
+ import './color.scss';
2
+
3
+ import { VNodeChild } from 'vue';
4
+ import { WysiwygColorExtension } from '../extensions';
5
+ import { type VuiService } from '../../../service';
6
+ import { WysiwygEditorToolFactory, WysiwygEditorTool } from '../schemes';
7
+ import TextStyle from '@tiptap/extension-text-style';
8
+ import { VIcon } from '../../VIcon';
9
+
10
+ export interface WysiwygColorItem {
11
+ key?: string | number;
12
+ name?: VNodeChild | ((vui: VuiService) => VNodeChild);
13
+ color: string | null;
14
+ }
15
+
16
+ export interface CreateWysiwygColorToolOptions {
17
+ items: WysiwygColorItem[];
18
+ withLabel?: boolean;
19
+ }
20
+
21
+ export function createWysiwygColorTool(opts: CreateWysiwygColorToolOptions) {
22
+ const WysiwygColorTool: WysiwygEditorToolFactory = (vui) => {
23
+ const tool: WysiwygEditorTool = {
24
+ key: 'textColor',
25
+ // active: ({ editor }) => editor.isActive('textStyle'),
26
+ icon:
27
+ ({ editor }) =>
28
+ () => {
29
+ const color: string | undefined =
30
+ editor.getAttributes('textStyle').color;
31
+ const style = { color };
32
+ return (
33
+ <span class="v-wysiwyg-color-tool__button">
34
+ <VIcon
35
+ class="v-wysiwyg-color-tool__button__icon"
36
+ name={vui.icon('editorTextColor')}
37
+ />
38
+ <span class="v-wysiwyg-color-tool__button__bar" style={style} />
39
+ </span>
40
+ );
41
+ },
42
+ onClick: (ctx, ev) => {
43
+ ctx.vui.menu({
44
+ class: 'v-wysiwyg-color-tool__menu',
45
+ activator: ev,
46
+ content: (stack) => (
47
+ <div
48
+ class={[
49
+ 'v-wysiwyg-color-tool__items',
50
+ { 'v-wysiwyg-color-tool__items--with-label': opts.withLabel },
51
+ ]}>
52
+ {opts.items.map((item, index) => (
53
+ <button
54
+ key={item.key == null ? index : item.key}
55
+ class="v-wysiwyg-color-tool__item"
56
+ type="button"
57
+ onClick={() => {
58
+ const { color } = item;
59
+ let command = ctx.editor.chain().focus();
60
+ if (color) {
61
+ command = command.setColor(color);
62
+ } else {
63
+ command = command.unsetColor();
64
+ }
65
+ command.run();
66
+ stack.close();
67
+ }}>
68
+ <span
69
+ class="v-wysiwyg-color-tool__item__color"
70
+ style={item.color ? { color: item.color } : {}}
71
+ />
72
+ {opts.withLabel && (
73
+ <span class="v-wysiwyg-color-tool__item__name">
74
+ {item.name}
75
+ </span>
76
+ )}
77
+ </button>
78
+ ))}
79
+ </div>
80
+ ),
81
+ });
82
+ },
83
+ floating: true,
84
+ extensions: [TextStyle, WysiwygColorExtension],
85
+ };
86
+ return tool;
87
+ };
88
+
89
+ return WysiwygColorTool;
90
+ }
@@ -5,4 +5,4 @@ export * from './format-underline';
5
5
  export * from './history';
6
6
  export * from './link';
7
7
  export * from './ordered-list';
8
- export * from './text-color';
8
+ export * from './color';
package/src/service.tsx CHANGED
@@ -40,6 +40,7 @@ export interface VuiServiceUISettings {
40
40
  primaryScope: ScopeName;
41
41
  plainVariant: ColorVariant;
42
42
  containedVariant: ColorVariant;
43
+ warningScope: ScopeName;
43
44
  errorScope: ScopeName;
44
45
  buttonDefault: {
45
46
  color: ScopeName;
@@ -258,6 +259,10 @@ export class VuiService {
258
259
  return this.stack.snackbar(...args);
259
260
  }
260
261
 
262
+ menu(...args: Parameters<VueStackService['menu']>) {
263
+ return this.stack.menu(...args);
264
+ }
265
+
261
266
  formPrompt<T extends { [key: string]: any } = { [key: string]: any }>(
262
267
  settings: VuiFormPromptSettings<T>,
263
268
  slot: (
@@ -143,6 +143,7 @@ export function viteVuiPlugin(
143
143
  primaryScope: 'primary' as any,
144
144
  plainVariant: 'plain' as any,
145
145
  containedVariant: 'contained' as any,
146
+ warningScope: 'warning' as any,
146
147
  errorScope: 'error' as any,
147
148
  buttonDefault: {
148
149
  color: 'base' as any,