@mixd-id/web-scaffold 0.1.230406001

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/package.json +71 -0
  4. package/public/images/mixd-logo2.png +0 -0
  5. package/src/App.vue +17 -0
  6. package/src/components/Ahref.vue +34 -0
  7. package/src/components/Alert.vue +160 -0
  8. package/src/components/Button.vue +253 -0
  9. package/src/components/ButtonGroup.vue +101 -0
  10. package/src/components/Carousel.vue +293 -0
  11. package/src/components/ChatTyping.vue +69 -0
  12. package/src/components/Checkbox.vue +152 -0
  13. package/src/components/ContextMenu.vue +261 -0
  14. package/src/components/CopyToClipboard.vue +59 -0
  15. package/src/components/Countdown.vue +213 -0
  16. package/src/components/Datepicker.vue +312 -0
  17. package/src/components/Dropdown.vue +198 -0
  18. package/src/components/DynamicTemplate.vue +44 -0
  19. package/src/components/ErrorText.vue +36 -0
  20. package/src/components/Feed.vue +118 -0
  21. package/src/components/Gmaps.vue +227 -0
  22. package/src/components/Grid.vue +29 -0
  23. package/src/components/GridColumn.vue +31 -0
  24. package/src/components/HTMLEditor.vue +396 -0
  25. package/src/components/Image.vue +207 -0
  26. package/src/components/Image360.vue +140 -0
  27. package/src/components/ImageFullScreen.vue +101 -0
  28. package/src/components/ImagePreview.vue +71 -0
  29. package/src/components/ImportModal.vue +247 -0
  30. package/src/components/ListItem.vue +147 -0
  31. package/src/components/ListPage1.vue +1331 -0
  32. package/src/components/ListPage1Filter.vue +170 -0
  33. package/src/components/Modal.vue +253 -0
  34. package/src/components/OTPField.vue +126 -0
  35. package/src/components/Radio.vue +134 -0
  36. package/src/components/SearchButton.vue +57 -0
  37. package/src/components/Slider.vue +285 -0
  38. package/src/components/SplitPane.vue +129 -0
  39. package/src/components/Switch.vue +89 -0
  40. package/src/components/TabView.vue +106 -0
  41. package/src/components/TableView.vue +201 -0
  42. package/src/components/TableViewHead.vue +159 -0
  43. package/src/components/Tabs.vue +74 -0
  44. package/src/components/TextEditor.vue +85 -0
  45. package/src/components/Textarea.vue +184 -0
  46. package/src/components/Textbox.vue +200 -0
  47. package/src/components/Timepicker.vue +108 -0
  48. package/src/components/Toast.vue +93 -0
  49. package/src/components/VirtualScroll.vue +215 -0
  50. package/src/components/VirtualTable.vue +497 -0
  51. package/src/entry-client.js +27 -0
  52. package/src/entry-server.js +73 -0
  53. package/src/index.css +3 -0
  54. package/src/index.js +255 -0
  55. package/src/main.js +38 -0
  56. package/src/router.js +57 -0
  57. package/src/themes/default/index.js +200 -0
  58. package/src/utils/helpers.js +185 -0
  59. package/src/utils/helpers.mjs +197 -0
  60. package/src/utils/importer.js +156 -0
  61. package/src/utils/listpage1.js +1371 -0
  62. package/src/utils/selection.js +64 -0
@@ -0,0 +1,227 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+ <div v-if="apiKey" ref="map" class="flex-1"></div>
4
+ <div v-else class="flex-1 flex items-center justify-center text-text-400">{{ $t('Google maps api required') }}</div>
5
+ </div>
6
+ </template>
7
+
8
+ <script>
9
+
10
+ import { Loader } from '@googlemaps/js-api-loader';
11
+
12
+ export default{
13
+
14
+ props: {
15
+ data: Array,
16
+ config: Object,
17
+ apiKey: String
18
+ },
19
+
20
+ data(){
21
+ return {
22
+ map: null
23
+ }
24
+ },
25
+
26
+ mounted() {
27
+ this.load()
28
+ },
29
+
30
+ methods: {
31
+
32
+ load(){
33
+ if(!this.apiKey) return
34
+
35
+ if(!Array.isArray(this.data) || this.data.length < 1)
36
+ return
37
+
38
+ const loader = new Loader({
39
+ apiKey: this.apiKey // "AIzaSyBmzMlmrR5St-njtN--64y_oLTa9FDLYfQ"
40
+ })
41
+
42
+ loader.load()
43
+ .then(async () => {
44
+
45
+ const { Map } = await google.maps.importLibrary([ "maps" ]);
46
+ await google.maps.importLibrary("visualization", ["HeatmapLayer"]);
47
+
48
+ var center = new google.maps.LatLng(
49
+ this.config.center ? this.config.center[0] : -6.2088,
50
+ this.config.center ? this.config.center[1] : 106.8456);
51
+
52
+ this.map = new Map(this.$refs.map, {
53
+ center: center,
54
+ zoom: this.config.zoom ?? 7,
55
+ mapTypeId: 'roadmap',
56
+ streetViewControl: false,
57
+ keyboardShortcuts: false,
58
+ styles: [
59
+ { elementType: "geometry", stylers: [{ color: "#242f3e" }] },
60
+ { elementType: "labels.text.stroke", stylers: [{ color: "#242f3e" }] },
61
+ { elementType: "labels.text.fill", stylers: [{ color: "#746855" }] },
62
+ {
63
+ featureType: "administrative.locality",
64
+ elementType: "labels.text.fill",
65
+ stylers: [{ color: "#d59563" }],
66
+ },
67
+ {
68
+ featureType: "poi",
69
+ elementType: "labels.text.fill",
70
+ stylers: [{ color: "#d59563" }],
71
+ },
72
+ {
73
+ featureType: "poi.park",
74
+ elementType: "geometry",
75
+ stylers: [{ color: "#263c3f" }],
76
+ },
77
+ {
78
+ featureType: "poi.park",
79
+ elementType: "labels.text.fill",
80
+ stylers: [{ color: "#6b9a76" }],
81
+ },
82
+ {
83
+ featureType: "road",
84
+ elementType: "geometry",
85
+ stylers: [{ color: "#38414e" }],
86
+ },
87
+ {
88
+ featureType: "road",
89
+ elementType: "geometry.stroke",
90
+ stylers: [{ color: "#212a37" }],
91
+ },
92
+ {
93
+ featureType: "road",
94
+ elementType: "labels.text.fill",
95
+ stylers: [{ color: "#9ca5b3" }],
96
+ },
97
+ {
98
+ featureType: "road.highway",
99
+ elementType: "geometry",
100
+ stylers: [{ color: "#746855" }],
101
+ },
102
+ {
103
+ featureType: "road.highway",
104
+ elementType: "geometry.stroke",
105
+ stylers: [{ color: "#1f2835" }],
106
+ },
107
+ {
108
+ featureType: "road.highway",
109
+ elementType: "labels.text.fill",
110
+ stylers: [{ color: "#f3d19c" }],
111
+ },
112
+ {
113
+ featureType: "transit",
114
+ elementType: "geometry",
115
+ stylers: [{ color: "#2f3948" }],
116
+ },
117
+ {
118
+ featureType: "transit.station",
119
+ elementType: "labels.text.fill",
120
+ stylers: [{ color: "#d59563" }],
121
+ },
122
+ {
123
+ featureType: "water",
124
+ elementType: "geometry",
125
+ stylers: [{ color: "#17263c" }],
126
+ },
127
+ {
128
+ featureType: "water",
129
+ elementType: "labels.text.fill",
130
+ stylers: [{ color: "#515c6d" }],
131
+ },
132
+ {
133
+ featureType: "water",
134
+ elementType: "labels.text.stroke",
135
+ stylers: [{ color: "#17263c" }],
136
+ },
137
+ ]
138
+ });
139
+
140
+ google.maps.event.addListener(this.map, 'zoom_changed', () => {
141
+ this.config.zoom = this.map.getZoom();
142
+ });
143
+
144
+ google.maps.event.addListener(this.map, 'center_changed', () => {
145
+ this.config.center = [
146
+ this.map.getCenter().lat(),
147
+ this.map.getCenter().lng()
148
+ ]
149
+ });
150
+
151
+ var heatMapData = [
152
+ /*{location: new google.maps.LatLng(37.782, -122.447), weight: 0.5},
153
+ new google.maps.LatLng(37.782, -122.445),
154
+ {location: new google.maps.LatLng(37.782, -122.443), weight: 2},
155
+ {location: new google.maps.LatLng(37.782, -122.441), weight: 3},
156
+ {location: new google.maps.LatLng(37.782, -122.439), weight: 2},
157
+ new google.maps.LatLng(37.782, -122.437),
158
+ {location: new google.maps.LatLng(37.782, -122.435), weight: 0.5},
159
+
160
+ {location: new google.maps.LatLng(37.785, -122.447), weight: 3},
161
+ {location: new google.maps.LatLng(37.785, -122.445), weight: 2},
162
+ new google.maps.LatLng(37.785, -122.443),
163
+ {location: new google.maps.LatLng(37.785, -122.441), weight: 0.5},
164
+ new google.maps.LatLng(37.785, -122.439),
165
+ {location: new google.maps.LatLng(37.785, -122.437), weight: 2},
166
+ {location: new google.maps.LatLng(37.785, -122.435), weight: 3}*/
167
+ ];
168
+
169
+ this.data.forEach((item) => {
170
+ if(item[0] && item[1] && item[2]){
171
+ heatMapData.push({
172
+ location: new google.maps.LatLng(item[0], item[1]),
173
+ weight: item[2]
174
+ })
175
+ }
176
+ })
177
+
178
+ var heatmap = new google.maps.visualization.HeatmapLayer({
179
+ data: heatMapData,
180
+ dissipating: true
181
+ });
182
+ heatmap.set('radius', parseInt(this.config.mapRadius ?? 12))
183
+ heatmap.setMap(this.map);
184
+
185
+ const gradient = [
186
+ "rgba(0, 255, 255, 0)",
187
+ "rgba(0, 255, 255, 1)",
188
+ "rgba(0, 191, 255, 1)",
189
+ "rgba(0, 127, 255, 1)",
190
+ "rgba(0, 63, 255, 1)",
191
+ "rgba(0, 0, 255, 1)",
192
+ "rgba(0, 0, 223, 1)",
193
+ "rgba(0, 0, 191, 1)",
194
+ "rgba(0, 0, 159, 1)",
195
+ "rgba(0, 0, 127, 1)",
196
+ "rgba(63, 0, 91, 1)",
197
+ "rgba(127, 0, 63, 1)",
198
+ "rgba(191, 0, 31, 1)",
199
+ "rgba(255, 0, 0, 1)",
200
+ ];
201
+ heatmap.set("gradient", gradient);
202
+
203
+ //console.log('Gmap, data count:', this.data.length)
204
+ });
205
+ }
206
+
207
+ },
208
+
209
+ watch: {
210
+
211
+ data(){
212
+ this.load()
213
+ }
214
+
215
+ }
216
+
217
+ }
218
+
219
+ </script>
220
+
221
+ <style module>
222
+
223
+ .comp{
224
+ @apply flex-1 flex flex-col;
225
+ }
226
+
227
+ </style>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div :class="gridClass">
3
+ <component v-for="(item, index) in items" :key="index" :is="item.type" :="item"></component>
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+
9
+ const gridCols = [
10
+ "", "", "grid-cols-2", "grid-cols-3", "grid-cols-4"
11
+ ]
12
+
13
+ export default{
14
+ name: "Grid",
15
+ props:[ "type", "items", "columns" ],
16
+ computed:{
17
+ gridClass(){
18
+ return [
19
+ "grid",
20
+ gridCols[this.columns]
21
+ ]
22
+ .filter(function(item){ return item.length > 0 })
23
+ .join(' ')
24
+ }
25
+ },
26
+ mounted(){
27
+ }
28
+ }
29
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div :class="className">
3
+ <component v-for="(item, index) in items" :key="index" :is="item.type" :="item"></component>
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+
9
+ const rowSpans = [
10
+ "", "", "row-span-2", "row-span-3", "row-span-4"
11
+ ]
12
+
13
+ export default{
14
+ name: "GridColumn",
15
+ props:[ "id", "type", "items", "colSpan", "rowSpan" ],
16
+ computed:{
17
+ className(){
18
+ return [
19
+ "border-[1px]",
20
+ "border-gray-300",
21
+ typeof this.rowSpan !== 'undefined' ? rowSpans[this.rowSpan] : ''
22
+ ]
23
+ .filter(function(item){ return item.length > 0 })
24
+ .join(' ')
25
+ }
26
+ },
27
+ mounted(){
28
+ }
29
+ }
30
+
31
+ </script>
@@ -0,0 +1,396 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+
4
+ <div class="flex flex-row">
5
+ <button class="p-3" type="button" @click="format('bold')">
6
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M0 64C0 46.3 14.3 32 32 32H80 96 224c70.7 0 128 57.3 128 128c0 31.3-11.3 60.1-30 82.3c37.1 22.4 62 63.1 62 109.7c0 70.7-57.3 128-128 128H96 80 32c-17.7 0-32-14.3-32-32s14.3-32 32-32H48V256 96H32C14.3 96 0 81.7 0 64zM224 224c35.3 0 64-28.7 64-64s-28.7-64-64-64H112V224H224zM112 288V416H256c35.3 0 64-28.7 64-64s-28.7-64-64-64H224 112z"/></svg>
7
+ </button>
8
+ <button class="p-3" type="button" @click="format('italic')">
9
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M128 64c0-17.7 14.3-32 32-32H352c17.7 0 32 14.3 32 32s-14.3 32-32 32H293.3L160 416h64c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H90.7L224 96H160c-17.7 0-32-14.3-32-32z"/></svg>
10
+ </button>
11
+ <button class="p-3" type="button" @click="format('underline')">
12
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M16 64c0-17.7 14.3-32 32-32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H128V224c0 53 43 96 96 96s96-43 96-96V96H304c-17.7 0-32-14.3-32-32s14.3-32 32-32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H384V224c0 88.4-71.6 160-160 160s-160-71.6-160-160V96H48C30.3 96 16 81.7 16 64zM0 448c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32z"/></svg>
13
+ </button>
14
+ <button class="p-3" type="button" ref="alignBtn" @click="$refs.alignContext.open($refs.alignBtn)">
15
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M288 64c0 17.7-14.3 32-32 32H32C14.3 96 0 81.7 0 64S14.3 32 32 32H256c17.7 0 32 14.3 32 32zm0 256c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H256c17.7 0 32 14.3 32 32zM0 192c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg>
16
+ </button>
17
+ <button class="p-3" type="button" ref="headingsBtn" @click="$refs.headingContext.open($refs.headingsBtn)">
18
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 64C0 46.3 14.3 32 32 32H80h48c17.7 0 32 14.3 32 32s-14.3 32-32 32H112V208H336V96H320c-17.7 0-32-14.3-32-32s14.3-32 32-32h48 48c17.7 0 32 14.3 32 32s-14.3 32-32 32H400V240 416h16c17.7 0 32 14.3 32 32s-14.3 32-32 32H368 320c-17.7 0-32-14.3-32-32s14.3-32 32-32h16V272H112V416h16c17.7 0 32 14.3 32 32s-14.3 32-32 32H80 32c-17.7 0-32-14.3-32-32s14.3-32 32-32H48V240 96H32C14.3 96 0 81.7 0 64z"/></svg>
19
+ </button>
20
+ <button class="p-3" type="button" ref="listBtn" @click="$refs.listContext.open($refs.listBtn)">
21
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M64 144c26.5 0 48-21.5 48-48s-21.5-48-48-48S16 69.5 16 96s21.5 48 48 48zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zM64 464c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm48-208c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48z"/></svg>
22
+ </button>
23
+ <button class="p-3" type="button" @click="createImage">
24
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M152 120c-26.51 0-48 21.49-48 48s21.49 48 48 48s48-21.49 48-48S178.5 120 152 120zM447.1 32h-384C28.65 32-.0091 60.65-.0091 96v320c0 35.35 28.65 64 63.1 64h384c35.35 0 64-28.65 64-64V96C511.1 60.65 483.3 32 447.1 32zM463.1 409.3l-136.8-185.9C323.8 218.8 318.1 216 312 216c-6.113 0-11.82 2.768-15.21 7.379l-106.6 144.1l-37.09-46.1c-3.441-4.279-8.934-6.809-14.77-6.809c-5.842 0-11.33 2.529-14.78 6.809l-75.52 93.81c0-.0293 0 .0293 0 0L47.99 96c0-8.822 7.178-16 16-16h384c8.822 0 16 7.178 16 16V409.3z"/></svg>
25
+ </button>
26
+ <button class="p-3" type="button" @click="createLink">
27
+ <svg width="14" height="14" class="fill-text-400 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
28
+ </button>
29
+ </div>
30
+
31
+ <ContextMenu ref="headingContext">
32
+ <div class="flex flex-col">
33
+ <button class="p-3 text-4xl text-left hover:bg-primary-200" type="button" @click="format('formatblock', 'h1')">
34
+ Heading 1
35
+ </button>
36
+ <button class="p-3 text-3xl text-left hover:bg-primary-200" type="button" @click="format('formatblock', 'h2')">
37
+ Heading 2
38
+ </button>
39
+ <button class="p-3 text-2xl text-left hover:bg-primary-200" type="button" @click="format('formatblock', 'h3')">
40
+ Heading 3
41
+ </button>
42
+ <button class="p-3 text-xl text-left hover:bg-primary-200" type="button" @click="format('formatblock', 'h4')">
43
+ Heading 4
44
+ </button>
45
+ <button class="p-3 text-lg text-left hover:bg-primary-200" type="button" @click="format('formatblock', 'h5')">
46
+ Heading 5
47
+ </button>
48
+ <button class="p-3 text-left hover:bg-primary-200" type="button" @click="format('formatblock', 'p')">
49
+ Normal
50
+ </button>
51
+ </div>
52
+ </ContextMenu>
53
+
54
+ <ContextMenu ref="alignContext">
55
+ <div class="flex flex-col">
56
+ <button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('justifyleft')">
57
+ <svg width="14" height="14" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M288 64c0 17.7-14.3 32-32 32H32C14.3 96 0 81.7 0 64S14.3 32 32 32H256c17.7 0 32 14.3 32 32zm0 256c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H256c17.7 0 32 14.3 32 32zM0 192c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg>
58
+ Left
59
+ </button>
60
+ <button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('justifycenter')">
61
+ <svg width="14" height="14" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M352 64c0-17.7-14.3-32-32-32H128c-17.7 0-32 14.3-32 32s14.3 32 32 32H320c17.7 0 32-14.3 32-32zm96 128c0-17.7-14.3-32-32-32H32c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32zM0 448c0 17.7 14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H32c-17.7 0-32 14.3-32 32zM352 320c0-17.7-14.3-32-32-32H128c-17.7 0-32 14.3-32 32s14.3 32 32 32H320c17.7 0 32-14.3 32-32z"/></svg>
62
+ Center
63
+ </button>
64
+ <button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('justifyright')">
65
+ <svg width="14" height="14" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 64c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32zm0 256c0 17.7-14.3 32-32 32H192c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32zM0 192c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg>
66
+ Right
67
+ </button>
68
+ <button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('justifyFull')">
69
+ <svg width="14" height="14" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M448 64c0-17.7-14.3-32-32-32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32zm0 256c0-17.7-14.3-32-32-32H32c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32zM0 192c0 17.7 14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H32c-17.7 0-32 14.3-32 32zM448 448c0-17.7-14.3-32-32-32H32c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32z"/></svg>
70
+ Justify
71
+ </button>
72
+ </div>
73
+ </ContextMenu>
74
+
75
+ <ContextMenu ref="listContext">
76
+ <div class="flex flex-col">
77
+ <button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('insertorderedlist')">
78
+ <svg width="14" height="14" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M24 56c0-13.3 10.7-24 24-24H80c13.3 0 24 10.7 24 24V176h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H40c-13.3 0-24-10.7-24-24s10.7-24 24-24H56V80H48C34.7 80 24 69.3 24 56zM86.7 341.2c-6.5-7.4-18.3-6.9-24 1.2L51.5 357.9c-7.7 10.8-22.7 13.3-33.5 5.6s-13.3-22.7-5.6-33.5l11.1-15.6c23.7-33.2 72.3-35.6 99.2-4.9c21.3 24.4 20.8 60.9-1.1 84.7L86.8 432H120c13.3 0 24 10.7 24 24s-10.7 24-24 24H32c-9.5 0-18.2-5.6-22-14.4s-2.1-18.9 4.3-25.9l72-78c5.3-5.8 5.4-14.6 .3-20.5zM224 64H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32z"/></svg>
79
+ Number List
80
+ </button>
81
+ <button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('insertunorderedlist')">
82
+ <svg width="14" height="14" class="fill-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M64 144c26.5 0 48-21.5 48-48s-21.5-48-48-48S16 69.5 16 96s21.5 48 48 48zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zM64 464c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm48-208c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48z"/></svg>
83
+ Bullet List
84
+ </button>
85
+ </div>
86
+ </ContextMenu>
87
+
88
+ <Modal ref="imageModal" width="360" height="420" dismissable="true">
89
+ <template v-slot:head>
90
+ <div class="relative p-5">
91
+ <h3>Add Image</h3>
92
+ <div class="absolute top-0 right-0 p-2">
93
+ <button type="button" class="p-2" @click="$refs.imageModal.close()">
94
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
95
+ <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
96
+ </svg>
97
+ </button>
98
+ </div>
99
+ </div>
100
+ </template>
101
+ <template v-slot:foot>
102
+ <div class="p-5">
103
+ <Button class="w-full" :disabled="!imageCanSave" @click="onImageAdd">Add Image</Button>
104
+ </div>
105
+ </template>
106
+ <div class="flex-1 flex flex-col gap-5 p-5">
107
+ <div>
108
+ <Image class="min-h-[100px] bg-base-300 rounded-xl" ref="image" :src="newImage.file" editable="true"
109
+ @click="$refs.image.edit()" @change="onImageChanged">
110
+ <template #empty="{ instance }">
111
+ <div class="absolute right-0 top-0 bottom-0 left-0 flex items-center justify-center">
112
+ Click to Add Image...
113
+ </div>
114
+ </template>
115
+ </Image>
116
+ </div>
117
+ <div class="grid grid-cols-2 gap-5">
118
+ <div>
119
+ <label>Width</label>
120
+ <Textbox class="mt-1" v-model="newImage.width" />
121
+ </div>
122
+ <div>
123
+ <label>Height</label>
124
+ <Textbox class="mt-1" v-model="newImage.height" />
125
+ </div>
126
+ <div>
127
+ <label>Max Width</label>
128
+ <Textbox class="mt-1" v-model="newImage.maxWidth" />
129
+ </div>
130
+ <div>
131
+ <label>Max Height</label>
132
+ <Textbox class="mt-1" v-model="newImage.maxHeight" />
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </Modal>
137
+
138
+ <Modal ref="linkModal" width="360" height="360" dismissable="true">
139
+ <template v-slot:head>
140
+ <div class="relative p-5">
141
+ <h3>Add Link</h3>
142
+ <div class="absolute top-0 right-0 p-2">
143
+ <button type="button" class="p-2" @click="$refs.linkModal.close()">
144
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
145
+ <path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
146
+ </svg>
147
+ </button>
148
+ </div>
149
+ </div>
150
+ </template>
151
+ <template v-slot:foot>
152
+ <div class="p-5">
153
+ <Button class="w-full" :disabled="!linkCanSave" @click="addLink">Add Link</Button>
154
+ </div>
155
+ </template>
156
+ <div class="flex-1 flex flex-col gap-5 p-5">
157
+ <div>
158
+ <label>Href</label>
159
+ <Textarea class="w-full mt-1" v-model="newLink.href" rows="2"/>
160
+ </div>
161
+ <div>
162
+ <label>Text</label>
163
+ <Textbox class="w-full mt-1" v-model="newLink.text" />
164
+ </div>
165
+ <div>
166
+ <label>Target</label>
167
+ <Dropdown class="w-full mt-1" v-model="newLink.target">
168
+ <option value="">_self</option>
169
+ <option value="_blank">_blank</option>
170
+ <option value="_parent">_parent</option>
171
+ <option value="_top">_top</option>
172
+ </Dropdown>
173
+ </div>
174
+ </div>
175
+ </Modal>
176
+
177
+ <article ref="article" contenteditable="true" v-html="html" @paste="onPaste"
178
+ @input="onInput"
179
+ @blur="onBlur">
180
+ </article>
181
+
182
+ </div>
183
+ </template>
184
+
185
+ <script>
186
+
187
+ import * as Vue from "vue/dist/vue.esm-bundler";
188
+ import {restoreSelection, saveSelection} from "../utils/selection";
189
+ import {downsizeImage} from "../utils/helpers.mjs";
190
+ import axios from "axios";
191
+
192
+ export default{
193
+
194
+ props:{
195
+ modelValue: String,
196
+ uploadImage: String
197
+ },
198
+
199
+ computed: {
200
+
201
+ linkCanSave(){
202
+ return this.newLink.href && this.newLink.text
203
+ },
204
+
205
+ imageCanSave(){
206
+ return true
207
+ }
208
+
209
+ },
210
+
211
+ data(){
212
+ return {
213
+ html: '',
214
+ newLink: null,
215
+ newImage: null,
216
+ components: null,
217
+ selection: null
218
+ }
219
+ },
220
+
221
+ mounted() {
222
+ this.html = this.modelValue
223
+ },
224
+
225
+ methods: {
226
+
227
+ onInput(e){
228
+ this.updateModelValue()
229
+ },
230
+
231
+ onBlur(){
232
+ this.$emit('change')
233
+ },
234
+
235
+ format(type, value){
236
+
237
+ switch(type){
238
+
239
+ case 'bold':
240
+ case 'italic':
241
+ default:
242
+ document.execCommand(type, false, value);
243
+ this.$emit('change')
244
+ break
245
+
246
+ }
247
+
248
+ },
249
+
250
+ createLink(){
251
+
252
+ const { text = '' } = saveSelection(this.$refs.article)
253
+
254
+ this.newLink = {
255
+ href: 'https://',
256
+ text,
257
+ target: ''
258
+ }
259
+ this.$refs.linkModal.open()
260
+ },
261
+
262
+ addLink(){
263
+
264
+ restoreSelection()
265
+
266
+ document.execCommand("insertHTML", false,
267
+ "<a style='font-size:inherit;font-family:inherit;background-color:inherit' " +
268
+ "href=\"" + this.newLink.href + "\" " +
269
+ "target=\"" + this.newLink.target + "\">" +
270
+ this.newLink.text +
271
+ "</a>");
272
+
273
+ this.newLink = null
274
+ this.$refs.linkModal.close()
275
+ },
276
+
277
+ createImage(){
278
+
279
+ saveSelection(this.$refs.article)
280
+
281
+ this.newImage = {
282
+ width: "",
283
+ height: ""
284
+ }
285
+ this.$refs.imageModal.open()
286
+ },
287
+
288
+ onImageChanged(base64, file){
289
+ this.newImage.base64 = base64
290
+ this.newImage.file = file
291
+ },
292
+
293
+ onImageAdd(){
294
+ if(this.uploadImage){
295
+ this.saveImage()
296
+ }
297
+ else{
298
+ this.addImage()
299
+ }
300
+ },
301
+
302
+ async addImage(){
303
+
304
+ restoreSelection()
305
+
306
+ const props = {}
307
+ if(this.newImage.width)
308
+ props.width = this.newImage.width
309
+ if(this.newImage.height)
310
+ props.height = this.newImage.height
311
+
312
+ let propHtml = []
313
+ for(let key in props){
314
+ propHtml.push(`${key}='${props[key]}'`)
315
+ }
316
+
317
+ document.execCommand("insertHTML", false,
318
+ "<img src=\"" + this.newImage.base64 + "\" " + propHtml.join(' ') + "/>");
319
+
320
+ this.$refs.imageModal.close()
321
+ },
322
+
323
+ async saveImage(){
324
+
325
+ const blob = await downsizeImage(this.newImage.file, this.newImage.width ?? 600)
326
+
327
+ const data = new FormData()
328
+ data.append('image', blob)
329
+
330
+ const res = await axios.post(this.uploadImage,
331
+ data,
332
+ {
333
+ headers: {
334
+ "Content-Type": "multipart/form-data",
335
+ },
336
+ }
337
+ )
338
+
339
+ const { url } = res.data
340
+
341
+ restoreSelection()
342
+
343
+ const props = {}
344
+ if(this.newImage.width)
345
+ props.width = this.newImage.width
346
+ if(this.newImage.height)
347
+ props.height = this.newImage.height
348
+
349
+ let propHtml = []
350
+ for(let key in props){
351
+ propHtml.push(`${key}='${props[key]}'`)
352
+ }
353
+
354
+ document.execCommand("insertHTML", false,
355
+ "<img src=\"" + url + "\" " + propHtml.join(' ') + "/>");
356
+
357
+ this.$refs.imageModal.close()
358
+ },
359
+
360
+ onPaste(e){
361
+ e.preventDefault()
362
+
363
+ let text = (e.clipboardData || window.clipboardData).getData("text");
364
+ const el = document.createElement('div')
365
+ el.innerHTML = text
366
+ text = el.innerText
367
+
368
+ document.execCommand("insertText", false, text);
369
+
370
+ this.updateModelValue()
371
+ },
372
+
373
+ updateModelValue(){
374
+
375
+ let html = this.$refs.article.innerHTML
376
+ html = html.replaceAll("<a ", "<Ahref ").replaceAll("</a>", "</Ahref>")
377
+
378
+ this.$emit('update:modelValue', html)
379
+ }
380
+
381
+ }
382
+
383
+ }
384
+
385
+ </script>
386
+
387
+ <style module>
388
+
389
+ .comp{
390
+ }
391
+
392
+ .comp article {
393
+ @apply min-h-[var(--h-cp)] w-full outline-none p-2;
394
+ }
395
+
396
+ </style>