@zipify/wysiwyg 3.5.0-ai-prototype → 3.5.0

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.
@@ -42,7 +42,7 @@
42
42
  :page-blocks="pageBlocks"
43
43
  :active="isActive"
44
44
  :readonly="isReadonly"
45
- :ai-adapter="aiAdapterRef"
45
+ :ai-component="aiComponent"
46
46
  @update-favorite-colors="updateFavoriteColors"
47
47
  />
48
48
  <pre class="zw-content-structure" v-html="structurePreview" />
@@ -55,7 +55,7 @@ import { Wysiwyg } from '../lib/entryLib';
55
55
  import { FONTS } from './fonts';
56
56
  import { PRESETS, renderPresetVariable } from './presets';
57
57
  import { PAGE_BLOCKS } from './pageBlocks';
58
- import { aiAdapter } from './aiAdapter';
58
+ import AiComponent from './ai-component/AiComponent';
59
59
 
60
60
  function getInitialContent() {
61
61
  const data = sessionStorage.getItem('wswg-data');
@@ -77,7 +77,7 @@ export default {
77
77
  },
78
78
 
79
79
  setup() {
80
- const aiAdapterRef = ref(aiAdapter);
80
+ const aiComponent = AiComponent;
81
81
  const wswgRef = ref(null);
82
82
  const content = ref(getInitialContent());
83
83
  const presets = ref(PRESETS);
@@ -139,7 +139,7 @@ export default {
139
139
  isActive,
140
140
  pageBlocks,
141
141
  isReadonly,
142
- aiAdapterRef
142
+ aiComponent
143
143
  };
144
144
  }
145
145
  };
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <div ref="wrapperRef">
3
+ <Button icon skin="toolbar" :active="isActive" @click="toggler.open">
4
+ <Icon name="sparkles" size="28px" />
5
+ <span class="zw-ai-control__caption">AI</span>
6
+ </Button>
7
+
8
+ <Modal ref="modalRef" class="zw-ai-component__modal" :toggler="toggler" focus-first-control>
9
+ <p>Modal content</p>
10
+ </Modal>
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ import { inject, ref, unref, computed } from 'vue';
16
+ import { Button, Icon, useModalToggler, Modal } from '../../lib/components/base';
17
+ import { InjectionTokens } from '../../lib/injectionTokens';
18
+
19
+ export default {
20
+ name: 'AiComponent',
21
+
22
+ components: {
23
+ Modal,
24
+ Button,
25
+ Icon
26
+ },
27
+
28
+ setup() {
29
+ const editor = inject(InjectionTokens.EDITOR);
30
+ const wrapperRef = ref(null);
31
+ const modalRef = ref(null);
32
+
33
+ const onBeforeOpened = () => {};
34
+
35
+ const toggler = useModalToggler({
36
+ options: {
37
+ placement: 'bottom-start',
38
+ strategy: 'absolute',
39
+ offset: [-8, 5]
40
+ },
41
+ onBeforeOpened: () => onBeforeOpened(),
42
+ wrapperRef,
43
+ modalRef
44
+ });
45
+
46
+ const isActive = computed(() => unref(toggler.isOpened));
47
+
48
+ return {
49
+ editor,
50
+ wrapperRef,
51
+ modalRef,
52
+ toggler,
53
+ isActive
54
+ };
55
+ }
56
+ };
57
+ </script>
package/lib/Wysiwyg.vue CHANGED
@@ -4,9 +4,10 @@
4
4
  :toolbar="toolbar"
5
5
  :device="device"
6
6
  :popup-mode="popupMode"
7
+ :ai-component="aiComponent"
7
8
  ref="toolbarRef"
8
9
  />
9
- <FloatingMenuControl />
10
+
10
11
  <EditorContent :editor="editor" />
11
12
  </div>
12
13
  </template>
@@ -22,7 +23,6 @@ import { ContextWindow, FavoriteColors, Storage } from './services';
22
23
  import { Devices } from './enums';
23
24
  import { outClick } from './directives';
24
25
  import { Font } from './models';
25
- import { FloatingMenuControl } from './components/floatingMenu';
26
26
 
27
27
  const MIN_FONT_SIZE = 5;
28
28
  const MAX_FONT_SIZE = 112;
@@ -32,8 +32,7 @@ export default {
32
32
 
33
33
  components: {
34
34
  Toolbar,
35
- EditorContent,
36
- FloatingMenuControl
35
+ EditorContent
37
36
  },
38
37
 
39
38
  directives: {
@@ -136,7 +135,7 @@ export default {
136
135
  default: () => window
137
136
  },
138
137
 
139
- aiAdapter: {
138
+ aiComponent: {
140
139
  type: Object,
141
140
  required: false,
142
141
  default: null
@@ -188,7 +187,6 @@ export default {
188
187
  basePresetClass: props.basePresetClass,
189
188
  baseListClass: props.baseListClass,
190
189
  deviceRef: toRef(props, 'device'),
191
- aiComponent: props.aiAdapter,
192
190
  pageBlocksRef: pageBlocks,
193
191
  wrapperRef
194
192
  })
@@ -7,7 +7,6 @@ export { default as Range } from './Range';
7
7
  export { default as NumberField } from './NumberField';
8
8
  export { default as Modal } from './Modal';
9
9
  export { default as TextField } from './TextField';
10
- export { default as TextArea } from './TextArea';
11
10
  export { default as Checkbox } from './Checkbox';
12
11
  export { useModalToggler, useElementRef } from './composables';
13
12
  export * from './dropdown';
@@ -2,7 +2,7 @@
2
2
  <keep-alive>
3
3
  <transition name="zw-toolbar-" duration="150">
4
4
  <div class="zw-toolbar" :style="toolbarStyles" ref="toolbarRef" v-if="isVisible">
5
- <component :is="layoutComponent" />
5
+ <component :is="layoutComponent" :ai-component="aiComponent" />
6
6
  </div>
7
7
  </transition>
8
8
  </keep-alive>
@@ -30,6 +30,12 @@ export default {
30
30
  popupMode: {
31
31
  type: Boolean,
32
32
  required: true
33
+ },
34
+
35
+ aiComponent: {
36
+ type: Object,
37
+ required: false,
38
+ default: null
33
39
  }
34
40
  },
35
41
 
@@ -13,5 +13,4 @@ export { default as AlignmentControl } from './AlignmentControl';
13
13
  export { default as LineHeightControl } from './LineHeightControl';
14
14
  export { default as ListControl } from './ListControl';
15
15
  export { default as RemoveFormatControl } from './RemoveFormatControl';
16
- export { default as AiControl } from './aiComponent/AiControl';
17
16
  export { LinkControl } from './link';
@@ -1,7 +1,10 @@
1
1
  <template>
2
2
  <div>
3
3
  <ToolbarRow>
4
- <AiControl />
4
+ <template v-if="aiComponent">
5
+ <component :is="aiComponent" />
6
+ <ToolbarDivider vertical />
7
+ </template>
5
8
  <StylePresetControl />
6
9
  <ToolbarDivider vertical />
7
10
  <FontFamilyControl />
@@ -65,8 +68,7 @@ import {
65
68
  UnderlineControl,
66
69
  ListControl,
67
70
  RemoveFormatControl,
68
- LinkControl,
69
- AiControl
71
+ LinkControl
70
72
  } from '../controls';
71
73
 
72
74
  export default {
@@ -91,8 +93,15 @@ export default {
91
93
  LineHeightControl,
92
94
  ListControl,
93
95
  RemoveFormatControl,
94
- LinkControl,
95
- AiControl
96
+ LinkControl
97
+ },
98
+
99
+ props: {
100
+ aiComponent: {
101
+ type: Object,
102
+ required: false,
103
+ default: null
104
+ }
96
105
  }
97
106
  };
98
107
  </script>
@@ -1,6 +1,10 @@
1
1
  <template>
2
2
  <div>
3
3
  <ToolbarRow>
4
+ <template v-if="aiComponent">
5
+ <component :is="aiComponent" />
6
+ <ToolbarDivider vertical />
7
+ </template>
4
8
  <StylePresetControl />
5
9
  <ToolbarDivider vertical />
6
10
  <FontFamilyControl />
@@ -90,6 +94,14 @@ export default {
90
94
  ListControl,
91
95
  RemoveFormatControl,
92
96
  LinkControl
97
+ },
98
+
99
+ props: {
100
+ aiComponent: {
101
+ type: Object,
102
+ required: false,
103
+ default: null
104
+ }
93
105
  }
94
106
  };
95
107
  </script>
package/lib/entryLib.js CHANGED
@@ -2,3 +2,5 @@ export { default as Wysiwyg } from './Wysiwyg';
2
2
  export { NodeFactory, HtmlToJsonParser } from './services';
3
3
  export { NodeTypes, TextSettings, Alignments } from './enums';
4
4
  export { isWysiwygContent, unmarkWysiwygContent, markWysiwygContent } from './utils';
5
+ export * from './components/base';
6
+ export { InjectionTokens } from './injectionTokens';
@@ -1,5 +1,4 @@
1
1
  import { reactive, toRef, watch } from 'vue';
2
- import FloatingMenu from '@tiptap/extension-floating-menu';
3
2
  import { StylePresetRenderer } from '../services';
4
3
  import { buildCoreExtensions } from './core';
5
4
  import { FontFamily } from './FontFamily';
@@ -18,7 +17,6 @@ import { List } from './list';
18
17
  import { Link } from './Link';
19
18
  import { Superscript } from './Superscript';
20
19
  import { Margin } from './Margin';
21
- import { AiComponent } from './AiComponent';
22
20
 
23
21
  export function buildExtensions(options) {
24
22
  const getPresetById = (id) => options.presetsRef.value.find((preset) => preset.id === id);
@@ -39,9 +37,6 @@ export function buildExtensions(options) {
39
37
  linkPresetId: options.linkPresetId
40
38
  })
41
39
  }),
42
- AiComponent.configure({
43
- aiComponent: options.aiComponent
44
- }),
45
40
  List.configure({
46
41
  baseClass: options.baseListClass,
47
42
  presetClass: options.basePresetClass + options.defaultPresetId
@@ -46,12 +46,10 @@ export class PastePlugin extends ProseMirrorPlugin {
46
46
  return true;
47
47
  }
48
48
 
49
- _insertPastedContent({ state, input }, slice) {
50
- if (!this._isFullBlockSelected(state)) {
51
- return state.tr.replaceSelection(slice);
52
- }
53
-
54
- return state.tr.replaceSelectionWith(slice.content, input.shiftKey);
49
+ _insertPastedContent({ state }, slice) {
50
+ return this._isFullBlockSelected(state)
51
+ ? state.tr.replaceSelectionWith(slice.content, false)
52
+ : state.tr.replaceSelection(slice);
55
53
  }
56
54
 
57
55
  _isFullBlockSelected(state) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zipify/wysiwyg",
3
- "version": "3.5.0-ai-prototype",
3
+ "version": "3.5.0",
4
4
  "description": "Zipify modification of TipTap text editor",
5
5
  "main": "dist/wysiwyg.mjs",
6
6
  "bin": {
@@ -33,7 +33,6 @@
33
33
  "@popperjs/core": "^2.11.7",
34
34
  "@tiptap/core": "^2.0.0",
35
35
  "@tiptap/extension-document": "^2.0.0",
36
- "@tiptap/extension-floating-menu": "^2.0.3",
37
36
  "@tiptap/extension-heading": "^2.0.0",
38
37
  "@tiptap/extension-history": "^2.0.0",
39
38
  "@tiptap/extension-link": "^2.0.0",
@@ -1,21 +0,0 @@
1
- export const aiAdapter = {
2
- generateText: async ({ prompt, context, istruction }) => {
3
- try {
4
- const response = await fetch('https://mendelson-test.eu.ngrok.io/process-text', {
5
- method: 'POST',
6
- headers: {
7
- 'Content-Type': 'application/json'
8
- },
9
- body: JSON.stringify({ text: prompt, context })
10
- });
11
-
12
- if (!response.ok) {
13
- throw new Error(`HTTP error: ${response.status}`);
14
- }
15
-
16
- return await response.json();
17
- } catch (error) {
18
- console.error('An error occurred while sending the request:', error);
19
- }
20
- }
21
- };
@@ -1,11 +0,0 @@
1
- <svg xml:space="preserve" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 100 100">
2
- <circle cx="6" cy="50" r="6" fill="var(--zw-icon-foreground)">
3
- <animateTransform attributeName="transform" begin=".1" dur="1s" repeatCount="indefinite" type="translate" values="0 15 ; 0 -15; 0 15"/>
4
- </circle>
5
- <circle cx="30" cy="50" r="6" fill="var(--zw-icon-foreground)">
6
- <animateTransform attributeName="transform" begin=".2" dur="1s" repeatCount="indefinite" type="translate" values="0 10 ; 0 -10; 0 10"/>
7
- </circle>
8
- <circle cx="54" cy="50" r="6" fill="var(--zw-icon-foreground)">
9
- <animateTransform attributeName="transform" begin=".3" dur="1s" repeatCount="indefinite" type="translate" values="0 5 ; 0 -5; 0 5"/>
10
- </circle>
11
- </svg>
@@ -1,3 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 24 24">
2
- <path stroke="var(--zw-icon-foreground)" stroke-width="2" d="m7 10.2.4.8c.3.5.4.7.4 1 0 .3 0 .5-.4 1l-.4.8c-1.2 2.1-1.9 3.2-1.4 3.7.5.6 1.6 0 4-1l6.2-2.7c1.8-.8 2.7-1.1 2.7-1.8s-.9-1-2.7-1.8L9.5 7.4c-2.3-1-3.4-1.5-3.9-1-.5.6.2 1.7 1.4 3.8Z"/>
3
- </svg>
@@ -1,3 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" fill="#9c6ade" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 20 20">
2
- <path d="M12.643 7.61c.124-.376.186-.564.288-.599a.216.216 0 0 1 .138 0c.102.035.164.223.288.6.519 1.577.778 2.366 1.268 2.991.225.288.485.548.773.773.625.49 1.414.75 2.991 1.268.377.124.565.186.6.288a.217.217 0 0 1 0 .138c-.035.102-.223.164-.6.288-1.577.519-2.366.778-2.991 1.268a4.53 4.53 0 0 0-.773.773c-.49.625-.75 1.414-1.268 2.991-.124.377-.186.565-.288.6a.217.217 0 0 1-.138 0c-.102-.035-.164-.223-.288-.6-.519-1.577-.778-2.366-1.268-2.991a4.53 4.53 0 0 0-.773-.773c-.625-.49-1.414-.75-2.991-1.268-.377-.124-.565-.186-.6-.288a.216.216 0 0 1 0-.138c.035-.102.223-.164.6-.288 1.577-.519 2.366-.778 2.991-1.268a4.53 4.53 0 0 0 .773-.773c.49-.625.75-1.414 1.268-2.991ZM4.762 4.407c.083-.251.124-.377.192-.4.03-.01.062-.01.092 0 .068.023.11.149.192.4.346 1.052.519 1.578.845 1.994.15.192.324.365.516.516.416.326.942.5 1.994.845.251.083.377.124.4.192.01.03.01.062 0 .092-.023.068-.149.11-.4.192-1.052.346-1.578.519-1.994.845-.192.15-.365.324-.516.516-.326.416-.5.942-.845 1.994-.083.251-.124.377-.192.4a.144.144 0 0 1-.092 0c-.068-.023-.11-.149-.192-.4-.346-1.052-.519-1.578-.845-1.994a3.022 3.022 0 0 0-.516-.516c-.416-.326-.942-.5-1.994-.845-.251-.083-.377-.124-.4-.192a.144.144 0 0 1 0-.092c.023-.068.149-.11.4-.192 1.052-.346 1.578-.519 1.994-.845.192-.15.365-.324.516-.516.326-.416.5-.942.845-1.994Zm5.589-3.153c.052-.157.078-.235.12-.25a.09.09 0 0 1 .058 0c.042.015.068.093.12.25.216.658.324.986.528 1.247.094.12.202.228.322.322.26.204.59.312 1.247.528.156.052.235.078.25.12a.089.089 0 0 1 0 .058c-.015.042-.094.068-.25.12-.658.216-.987.324-1.247.528-.12.094-.228.202-.322.322-.204.26-.312.59-.528 1.247-.052.156-.078.235-.12.25a.09.09 0 0 1-.058 0c-.042-.015-.068-.094-.12-.25-.216-.658-.324-.986-.528-1.247a1.888 1.888 0 0 0-.322-.322c-.26-.204-.59-.312-1.247-.528-.156-.052-.235-.078-.25-.12a.09.09 0 0 1 0-.058c.015-.042.094-.068.25-.12.658-.216.986-.324 1.247-.528.12-.094.228-.202.322-.322.204-.26.312-.59.528-1.247Z"/>
3
- </svg>
@@ -1,108 +0,0 @@
1
- <template>
2
- <div class="zw-field">
3
- <label v-if="label" class="zw-field__label" :for="fieldId" data-test-selector="label">
4
- {{ label }}
5
- </label>
6
-
7
- <textarea
8
- class="zw-field__input"
9
- :value="value"
10
- :id="fieldId"
11
- :placeholder="placeholder"
12
- @input="onInput"
13
- data-test-selector="input"
14
- />
15
-
16
- <p class="zw-field__label--error" v-if="error" data-test-selector="error">
17
- {{ error }}
18
- </p>
19
- </div>
20
- </template>
21
-
22
- <script>
23
- import { computed } from 'vue';
24
-
25
- export default {
26
- name: 'TextArea',
27
-
28
- props: {
29
- value: {
30
- type: [Number, String],
31
- required: true
32
- },
33
-
34
- label: {
35
- type: String,
36
- required: false,
37
- default: ''
38
- },
39
-
40
- placeholder: {
41
- type: String,
42
- required: false,
43
- default: ''
44
- },
45
-
46
- error: {
47
- type: String,
48
- required: false,
49
- default: null
50
- }
51
- },
52
-
53
- setup(props, { emit }) {
54
- const onInput = (event) => emit('input', event.target.value);
55
- const fieldId = computed(() => {
56
- return props.label.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
57
- });
58
-
59
- return { onInput, fieldId };
60
- }
61
- };
62
- </script>
63
-
64
- <style scoped>
65
- .zw-field {
66
- display: flex;
67
- flex-direction: column;
68
- }
69
-
70
- .zw-field__input {
71
- --border-color: rgb(var(--zw-color-n60));
72
- --text-color: rgb(var(--zw-color-n85));
73
-
74
- border: 1px solid var(--border-color);
75
- background-color: transparent;
76
- color: var(--text-color);
77
- font-size: var(--zw-font-size-xxs);
78
- outline: none;
79
- padding: 6px;
80
- line-height: var(--zw-line-height-xxs);
81
- transition: 0.1s border ease-out, 0.1s color ease-out;
82
- will-change: border, color;
83
- }
84
-
85
- .zw-field__input:hover {
86
- --border-color: rgb(var(--zw-color-n80));
87
- --text-color: rgb(var(--zw-color-n85));
88
- }
89
-
90
- .zw-field__input:focus,
91
- .zw-field__input:focus-within {
92
- --border-color: rgb(var(--zw-color-white));
93
- --text-color: rgb(var(--zw-color-white));
94
- }
95
-
96
- .zw-field__label {
97
- display: inline-block;
98
- font-size: var(--zw-font-size-xxs);
99
- padding-bottom: var(--zw-offset-xxs);
100
- line-height: var(--zw-line-height-xxs);
101
- }
102
-
103
- .zw-field__label--error {
104
- font-size: var(--zw-font-size-xxs);
105
- margin: var(--zw-offset-xxs) 0 0;
106
- color: rgb(var(--zw-color-red));
107
- }
108
- </style>
@@ -1,74 +0,0 @@
1
- <template>
2
- <div class="zw-ai-component__suggestion">
3
- <p class="zw-ai-component-suggestion__request zw-margin-bottom--sm">
4
- {{ suggestion.request }}
5
- </p>
6
- <p class="zw-margin-bottom--sm" v-html="suggestion.content" />
7
-
8
- <Button v-if="suggestion.state !== 'loading'" @click="insert" class="zw-ai-component-suggestion__button">
9
- Insert
10
- </Button>
11
- </div>
12
- </template>
13
-
14
- <script>
15
- import { inject } from 'vue';
16
- import { Button } from '../base';
17
- import { InjectionTokens } from '../../injectionTokens';
18
-
19
- export default {
20
- name: 'AiWidgetSuggestionItem',
21
-
22
- components: {
23
- Button
24
- },
25
-
26
- props: {
27
- suggestion: {
28
- type: Object,
29
- required: true
30
- }
31
- },
32
-
33
- setup(props, { emit }) {
34
- const editor = inject(InjectionTokens.EDITOR);
35
-
36
- const insert = () => {
37
- editor.chain().insertContent(props.suggestion.content).run();
38
- emit('close');
39
- };
40
-
41
- return {
42
- insert
43
- };
44
- }
45
- };
46
- </script>
47
-
48
- <style scoped>
49
- .zw-ai-component__suggestion {
50
- font-size: 14px;
51
- margin-bottom: 8px;
52
- padding: 16px 8px;
53
- background-color: #FFF;
54
- border: 0.5px solid #CDD1DC;
55
- border-radius: 2px;
56
- }
57
-
58
- .zw-ai-component-suggestion__request {
59
- font-size: 12px;
60
- color: #666;
61
- }
62
-
63
- .zw-ai-component-suggestion__button {
64
- font-size: 14px;
65
- padding: 2px 4px;
66
- background-color: #3AAA35;
67
- color: #FFF;
68
- border-radius: 2px;
69
- }
70
-
71
- .zw-ai-component-suggestion__button:hover {
72
- opacity: 0.8;
73
- }
74
- </style>