@mixd-id/web-scaffold 0.1.2301231345 → 0.1.2301231347
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/package.json +1 -1
- package/src/components/Ahref.vue +8 -182
- package/src/components/Alert.vue +3 -5
- package/src/components/Button.vue +21 -11
- package/src/components/DynamicTemplate.vue +44 -0
- package/src/components/HTMLEditor.vue +253 -26
- package/src/components/ListPage1.vue +10 -6
- package/src/components/Modal.vue +18 -4
- package/src/components/Textbox.vue +7 -2
- package/src/components/Toast.vue +1 -1
- package/src/index.js +2 -1
- package/src/utils/helpers.js +151 -9
- package/src/utils/helpers.mjs +70 -7
- package/src/utils/selection.js +64 -0
package/package.json
CHANGED
package/src/components/Ahref.vue
CHANGED
|
@@ -1,54 +1,28 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<a v-if="isExternalLink" :
|
|
2
|
+
<a v-if="isExternalLink" :href="href" :target="target">
|
|
3
3
|
<slot />
|
|
4
4
|
</a>
|
|
5
|
-
<router-link v-else
|
|
5
|
+
<router-link v-else :to="href" :target="target">
|
|
6
6
|
<slot />
|
|
7
7
|
</router-link>
|
|
8
8
|
</template>
|
|
9
9
|
|
|
10
10
|
<script>
|
|
11
|
-
import { RouterLink } from 'vue-router'
|
|
12
11
|
|
|
13
12
|
export default {
|
|
14
13
|
|
|
15
14
|
props: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
variant: {
|
|
22
|
-
type: [ String, Number ],
|
|
23
|
-
default: "primary" // primary|secondary|outline, default:primary
|
|
24
|
-
},
|
|
25
|
-
size: {
|
|
26
|
-
type: [ String ],
|
|
27
|
-
default: '' // sm|md|lg, default: md
|
|
28
|
-
},
|
|
29
|
-
spacing: {
|
|
30
|
-
type: [ String, Number ],
|
|
31
|
-
default: '' // 0|1|2|3, default: 2
|
|
32
|
-
},
|
|
33
|
-
state: [ String, Number ],
|
|
15
|
+
|
|
16
|
+
href: String,
|
|
17
|
+
|
|
18
|
+
target: String
|
|
19
|
+
|
|
34
20
|
},
|
|
35
21
|
|
|
36
22
|
computed: {
|
|
37
23
|
|
|
38
24
|
isExternalLink() {
|
|
39
|
-
return typeof this.
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
computedClass(){
|
|
43
|
-
|
|
44
|
-
return [
|
|
45
|
-
this.$style.button,
|
|
46
|
-
this.$style['variant-' + this.variant],
|
|
47
|
-
this.$style['size-' + this.size],
|
|
48
|
-
this.$style['spacing-' + this.spacing],
|
|
49
|
-
parseInt(this.state) === 2 ? this.$style.loading : '',
|
|
50
|
-
]
|
|
51
|
-
.join(' ')
|
|
25
|
+
return typeof this.href === 'string' && (this.href.startsWith('http') || this.href.startsWith('tel:'))
|
|
52
26
|
}
|
|
53
27
|
},
|
|
54
28
|
}
|
|
@@ -57,152 +31,4 @@ export default {
|
|
|
57
31
|
<style module>
|
|
58
32
|
|
|
59
33
|
|
|
60
|
-
.button{
|
|
61
|
-
@apply relative inline-flex items-center justify-center;
|
|
62
|
-
@apply whitespace-nowrap text-ellipsis overflow-hidden;
|
|
63
|
-
}
|
|
64
|
-
.button>*:first-child{
|
|
65
|
-
@apply flex items-center justify-center
|
|
66
|
-
}
|
|
67
|
-
.button:disabled{
|
|
68
|
-
@apply opacity-50;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.variant-,
|
|
72
|
-
.variant-primary{
|
|
73
|
-
@apply bg-primary-500 text-white rounded-md border-[2px] border-primary-500;
|
|
74
|
-
}
|
|
75
|
-
.variant-:hover,
|
|
76
|
-
.variant-primary:hover{
|
|
77
|
-
@apply bg-primary-600 border-primary-600;
|
|
78
|
-
}
|
|
79
|
-
.variant-:active,
|
|
80
|
-
.variant-primary:active{
|
|
81
|
-
@apply top-[1px] left-[1px] relative;
|
|
82
|
-
}
|
|
83
|
-
.variant-:disabled,
|
|
84
|
-
.variant-primary:disabled{
|
|
85
|
-
@apply top-0 left-0 cursor-not-allowed;
|
|
86
|
-
}
|
|
87
|
-
.variant- *,
|
|
88
|
-
.variant-primary *{
|
|
89
|
-
@apply text-white fill-white;
|
|
90
|
-
}
|
|
91
|
-
.variant-.loading *,
|
|
92
|
-
.variant-primary.loading *{
|
|
93
|
-
@apply fill-transparent
|
|
94
|
-
}
|
|
95
|
-
.variant- .svgBg,
|
|
96
|
-
.variant-primary .svgBg{
|
|
97
|
-
stroke: #fff;
|
|
98
|
-
stroke-opacity: 25%;
|
|
99
|
-
}
|
|
100
|
-
.variant- .svgHg,
|
|
101
|
-
.variant-primary .svgHg{
|
|
102
|
-
fill: #fff;
|
|
103
|
-
fill-opacity: 75%;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.variant-outline{
|
|
107
|
-
@apply bg-transparent text-primary-500 rounded-md border-[2px] border-primary-500;
|
|
108
|
-
}
|
|
109
|
-
.variant-outline:hover{
|
|
110
|
-
@apply border-primary-300 text-primary-300;
|
|
111
|
-
}
|
|
112
|
-
.variant-outline:active{
|
|
113
|
-
@apply top-[1px] left-[1px] active:relative;
|
|
114
|
-
}
|
|
115
|
-
.variant-outline:disabled{
|
|
116
|
-
@apply opacity-50 top-0 left-0 cursor-not-allowed;
|
|
117
|
-
@apply text-primary-500 border-primary-500;
|
|
118
|
-
}
|
|
119
|
-
.variant-outline *{
|
|
120
|
-
@apply text-primary-500 fill-primary-500;
|
|
121
|
-
}
|
|
122
|
-
.variant-outline.loading *{
|
|
123
|
-
@apply fill-transparent
|
|
124
|
-
}
|
|
125
|
-
.variant-outline .svgBg{
|
|
126
|
-
stroke: var(--primary);
|
|
127
|
-
stroke-opacity: 25%;
|
|
128
|
-
}
|
|
129
|
-
.variant-outline .svgHg{
|
|
130
|
-
fill: var(--primary);
|
|
131
|
-
fill-opacity: 75%;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.variant-secondary{
|
|
135
|
-
@apply bg-secondary-500 text-primary rounded-md border-[2px] border-secondary-500;
|
|
136
|
-
}
|
|
137
|
-
.variant-secondary:hover{
|
|
138
|
-
@apply bg-secondary-400 border-secondary-400;
|
|
139
|
-
}
|
|
140
|
-
.variant-secondary:active{
|
|
141
|
-
@apply top-[1px] left-[1px] relative;
|
|
142
|
-
}
|
|
143
|
-
.variant-secondary:disabled{
|
|
144
|
-
@apply bg-secondary-500 border-secondary-500 opacity-50 top-0 left-0 cursor-not-allowed;
|
|
145
|
-
}
|
|
146
|
-
.variant-secondary *{
|
|
147
|
-
@apply text-white fill-white;
|
|
148
|
-
}
|
|
149
|
-
.variant-secondary.loading *{
|
|
150
|
-
@apply fill-transparent
|
|
151
|
-
}
|
|
152
|
-
.variant-secondary .svgBg{
|
|
153
|
-
stroke: var(--text);
|
|
154
|
-
stroke-opacity: 25%;
|
|
155
|
-
}
|
|
156
|
-
.variant-secondary .svgHg{
|
|
157
|
-
fill: #fff;
|
|
158
|
-
fill-opacity: 75%;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.variant-minimal .svgBg{
|
|
162
|
-
stroke: var(--base-500);
|
|
163
|
-
stroke-opacity: 25%;
|
|
164
|
-
}
|
|
165
|
-
.variant-minimal .svgHg{
|
|
166
|
-
fill: var(--text);
|
|
167
|
-
fill-opacity: 75%;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.loadingPane{
|
|
171
|
-
@apply absolute left-0 top-0 right-0 bottom-0 flex items-center justify-center
|
|
172
|
-
}
|
|
173
|
-
.loading>*:first-child{
|
|
174
|
-
@apply opacity-0
|
|
175
|
-
}
|
|
176
|
-
.loading:hover{
|
|
177
|
-
@apply bg-primary
|
|
178
|
-
}
|
|
179
|
-
.loading:active{
|
|
180
|
-
@apply top-0 left-0;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.spacing-,
|
|
184
|
-
.spacing-2{
|
|
185
|
-
@apply p-2
|
|
186
|
-
}
|
|
187
|
-
.spacing-1{
|
|
188
|
-
@apply p-1
|
|
189
|
-
}
|
|
190
|
-
.spacing-3{
|
|
191
|
-
@apply p-3
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.size-sm{
|
|
195
|
-
@apply text-sm
|
|
196
|
-
}
|
|
197
|
-
.size-lg{
|
|
198
|
-
@apply text-lg
|
|
199
|
-
}
|
|
200
|
-
.size-xl{
|
|
201
|
-
@apply text-xl
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.spinner{
|
|
205
|
-
@apply animate-spin h-5 w-5;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
34
|
</style>
|
package/src/components/Alert.vue
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 20.5C16.6944 20.5 20.5 16.6944 20.5 12C20.5 7.30558 16.6944 3.5 12 3.5C7.30558 3.5 3.5 7.30558 3.5 12C3.5 16.6944 7.30558 20.5 12 20.5ZM12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/>
|
|
18
18
|
</svg>
|
|
19
19
|
|
|
20
|
-
<div class="my-4 text-center">
|
|
20
|
+
<div class="my-4 text-center flex-1 overflow-y-auto">
|
|
21
21
|
<h5 class="my-1 break-all">{{ title }}</h5>
|
|
22
22
|
<p class="break-all overflow-y-auto whitespace-pre-line text-left">{{ description }}</p>
|
|
23
23
|
</div>
|
|
@@ -35,10 +35,8 @@
|
|
|
35
35
|
{{ text }}
|
|
36
36
|
</strong>
|
|
37
37
|
</Button>
|
|
38
|
-
<Button variant="minimal" @click="$emit('dismiss')" class="min-w-[88px]">
|
|
39
|
-
|
|
40
|
-
Cancel
|
|
41
|
-
</label>
|
|
38
|
+
<Button variant="minimal" @click="$emit('dismiss')" class="min-w-[88px] px-4 hover:text-primary">
|
|
39
|
+
Cancel
|
|
42
40
|
</Button>
|
|
43
41
|
</div>
|
|
44
42
|
</div>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<button :class="computedClass" :disabled="isDisabled">
|
|
3
|
-
<
|
|
4
|
-
<slot></slot>
|
|
5
|
-
</div>
|
|
3
|
+
<slot></slot>
|
|
6
4
|
<div v-if="isLoading" :class="$style.loadingPane">
|
|
7
5
|
<svg :class="$style.spinner" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
8
6
|
<circle :class="$style.svgBg" cx="12" cy="12" r="10" stroke-width="4"></circle>
|
|
@@ -33,10 +31,7 @@ export default{
|
|
|
33
31
|
default: '' // 0|1|2|3, default: 2
|
|
34
32
|
},
|
|
35
33
|
|
|
36
|
-
state:
|
|
37
|
-
type: [ String, Number ],
|
|
38
|
-
default: 1 // -1:disabled, 1:normal, 2:loading, default: normal
|
|
39
|
-
}
|
|
34
|
+
state: [ String, Number ], // -1:disabled, 1:normal, 2:loading, default: normal
|
|
40
35
|
|
|
41
36
|
},
|
|
42
37
|
|
|
@@ -49,29 +44,44 @@ export default{
|
|
|
49
44
|
this.$style['variant-' + this.variant],
|
|
50
45
|
this.$style['size-' + this.size],
|
|
51
46
|
this.$style['spacing-' + this.spacing],
|
|
52
|
-
parseInt(this.
|
|
47
|
+
parseInt(this.computedState) === 2 ? this.$style.loading : '',
|
|
53
48
|
]
|
|
54
49
|
.join(' ')
|
|
55
50
|
},
|
|
56
51
|
|
|
57
52
|
isDisabled(){
|
|
58
|
-
return parseInt(this.
|
|
53
|
+
return parseInt(this.computedState) === -1
|
|
59
54
|
},
|
|
60
55
|
|
|
61
56
|
isNormal(){
|
|
62
|
-
return parseInt(this.
|
|
57
|
+
return parseInt(this.computedState) !== 2
|
|
63
58
|
},
|
|
64
59
|
|
|
65
60
|
isLoading(){
|
|
66
|
-
return parseInt(this.
|
|
61
|
+
return parseInt(this.computedState) === 2
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
computedState(){
|
|
65
|
+
return this.state ?? this._state
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
},
|
|
70
69
|
|
|
70
|
+
data(){
|
|
71
|
+
return {
|
|
72
|
+
_state: null
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
71
76
|
methods:{
|
|
72
77
|
|
|
73
78
|
focus(){
|
|
74
79
|
this.$el.focus()
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
setState(state){
|
|
83
|
+
console.log('setState', state)
|
|
84
|
+
this._state = state
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.comp">
|
|
3
|
+
<component :is="components"></component>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
|
|
9
|
+
import * as Vue from "vue/dist/vue.esm-bundler";
|
|
10
|
+
|
|
11
|
+
export default{
|
|
12
|
+
|
|
13
|
+
props: [ 'template' ],
|
|
14
|
+
|
|
15
|
+
data(){
|
|
16
|
+
return {
|
|
17
|
+
components: null
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
watch: {
|
|
22
|
+
|
|
23
|
+
template: {
|
|
24
|
+
immediate: true,
|
|
25
|
+
handler(to){
|
|
26
|
+
this.components = Vue.compile(to)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<style module>
|
|
39
|
+
|
|
40
|
+
.comp{
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="$style.comp">
|
|
3
3
|
|
|
4
|
-
<div class="
|
|
4
|
+
<div class="flex flex-row">
|
|
5
5
|
<button class="p-3" type="button" @click="format('bold')">
|
|
6
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
7
|
</button>
|
|
@@ -11,26 +11,24 @@
|
|
|
11
11
|
<button class="p-3" type="button" @click="format('underline')">
|
|
12
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
13
|
</button>
|
|
14
|
-
<button class="p-3" type="button" ref="alignBtn" @click="
|
|
14
|
+
<button class="p-3" type="button" ref="alignBtn" @click="$refs.alignContext.open($refs.alignBtn)">
|
|
15
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
16
|
</button>
|
|
17
|
-
<button class="p-3" type="button" ref="headingsBtn" @click="
|
|
17
|
+
<button class="p-3" type="button" ref="headingsBtn" @click="$refs.headingContext.open($refs.headingsBtn)">
|
|
18
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
19
|
</button>
|
|
20
|
-
<button class="p-3" type="button" ref="listBtn" @click="
|
|
20
|
+
<button class="p-3" type="button" ref="listBtn" @click="$refs.listContext.open($refs.listBtn)">
|
|
21
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
22
|
</button>
|
|
23
|
-
<button class="p-3" type="button">
|
|
23
|
+
<button class="p-3" type="button" @click="createImage">
|
|
24
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
25
|
</button>
|
|
26
|
-
<button class="p-3" type="button">
|
|
26
|
+
<button class="p-3" type="button" @click="createLink">
|
|
27
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
28
|
</button>
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
|
-
<ContextMenu
|
|
32
|
-
:caller="contextMenu ? contextMenu.caller : null"
|
|
33
|
-
@dismiss="contextMenu = null">
|
|
31
|
+
<ContextMenu ref="headingContext">
|
|
34
32
|
<div class="flex flex-col">
|
|
35
33
|
<button class="p-3 text-4xl text-left hover:bg-primary-200" type="button" @click="format('formatblock', 'h1')">
|
|
36
34
|
Heading 1
|
|
@@ -53,9 +51,7 @@
|
|
|
53
51
|
</div>
|
|
54
52
|
</ContextMenu>
|
|
55
53
|
|
|
56
|
-
<ContextMenu
|
|
57
|
-
:caller="contextMenu ? contextMenu.caller : null"
|
|
58
|
-
@dismiss="contextMenu = null">
|
|
54
|
+
<ContextMenu ref="alignContext">
|
|
59
55
|
<div class="flex flex-col">
|
|
60
56
|
<button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('justifyleft')">
|
|
61
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>
|
|
@@ -76,9 +72,7 @@
|
|
|
76
72
|
</div>
|
|
77
73
|
</ContextMenu>
|
|
78
74
|
|
|
79
|
-
<ContextMenu
|
|
80
|
-
:caller="contextMenu ? contextMenu.caller : null"
|
|
81
|
-
@dismiss="contextMenu = null">
|
|
75
|
+
<ContextMenu ref="listContext">
|
|
82
76
|
<div class="flex flex-col">
|
|
83
77
|
<button class="p-3 text-left hover:bg-primary-200 flex flex-row items-center gap-2" type="button" @click="format('insertorderedlist')">
|
|
84
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>
|
|
@@ -90,26 +84,137 @@
|
|
|
90
84
|
</button>
|
|
91
85
|
</div>
|
|
92
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>
|
|
93
176
|
|
|
94
|
-
<article ref="article" contenteditable="true" v-html="html"
|
|
177
|
+
<article ref="article" contenteditable="true" v-html="html" @paste="onPaste"
|
|
95
178
|
@input="onInput"
|
|
96
|
-
@blur="onBlur"
|
|
179
|
+
@blur="onBlur">
|
|
180
|
+
</article>
|
|
97
181
|
|
|
98
182
|
</div>
|
|
99
183
|
</template>
|
|
100
184
|
|
|
101
185
|
<script>
|
|
102
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
|
+
|
|
103
192
|
export default{
|
|
104
193
|
|
|
105
194
|
props:{
|
|
106
|
-
modelValue: String
|
|
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
|
+
|
|
107
209
|
},
|
|
108
210
|
|
|
109
211
|
data(){
|
|
110
212
|
return {
|
|
111
213
|
html: '',
|
|
112
|
-
|
|
214
|
+
newLink: null,
|
|
215
|
+
newImage: null,
|
|
216
|
+
components: null,
|
|
217
|
+
selection: null
|
|
113
218
|
}
|
|
114
219
|
},
|
|
115
220
|
|
|
@@ -120,7 +225,7 @@ export default{
|
|
|
120
225
|
methods: {
|
|
121
226
|
|
|
122
227
|
onInput(e){
|
|
123
|
-
this
|
|
228
|
+
this.updateModelValue()
|
|
124
229
|
},
|
|
125
230
|
|
|
126
231
|
onBlur(){
|
|
@@ -140,14 +245,137 @@ export default{
|
|
|
140
245
|
|
|
141
246
|
}
|
|
142
247
|
|
|
143
|
-
}
|
|
248
|
+
},
|
|
144
249
|
|
|
145
|
-
|
|
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(){
|
|
146
374
|
|
|
147
|
-
|
|
375
|
+
let html = this.$refs.article.innerHTML
|
|
376
|
+
html = html.replaceAll("<a ", "<Ahref ").replaceAll("</a>", "</Ahref>")
|
|
148
377
|
|
|
149
|
-
|
|
150
|
-
//console.log('watch modelValue', from)
|
|
378
|
+
this.$emit('update:modelValue', html)
|
|
151
379
|
}
|
|
152
380
|
|
|
153
381
|
}
|
|
@@ -159,7 +387,6 @@ export default{
|
|
|
159
387
|
<style module>
|
|
160
388
|
|
|
161
389
|
.comp{
|
|
162
|
-
@apply flex flex-col border-[1px] border-text-200 bg-base-50 rounded-lg overflow-hidden;
|
|
163
390
|
}
|
|
164
391
|
|
|
165
392
|
.comp article {
|
|
@@ -198,6 +198,8 @@ export default{
|
|
|
198
198
|
|
|
199
199
|
props:{
|
|
200
200
|
|
|
201
|
+
datasource: String,
|
|
202
|
+
|
|
201
203
|
model: String,
|
|
202
204
|
|
|
203
205
|
title: String,
|
|
@@ -237,6 +239,10 @@ export default{
|
|
|
237
239
|
|
|
238
240
|
sortableColumns(){
|
|
239
241
|
return Object.values(this.columns).filter((_) => _.sortable)
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
computedDataSource(){
|
|
245
|
+
return this.datasource ?? this.model
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
},
|
|
@@ -272,7 +278,8 @@ export default{
|
|
|
272
278
|
methods:{
|
|
273
279
|
|
|
274
280
|
patch(){
|
|
275
|
-
|
|
281
|
+
|
|
282
|
+
this.socketEmit(`${this.computedDataSource}.patch`, {}, (res) => {
|
|
276
283
|
this.columns = res.columns
|
|
277
284
|
this.presets = res.presets
|
|
278
285
|
this.presetIdx = res.presetIdx
|
|
@@ -324,7 +331,7 @@ export default{
|
|
|
324
331
|
},
|
|
325
332
|
|
|
326
333
|
load(){
|
|
327
|
-
this.socketEmit(`${this.
|
|
334
|
+
this.socketEmit(`${this.computedDataSource}.load`, {
|
|
328
335
|
preset: this.preset
|
|
329
336
|
}, (res) => {
|
|
330
337
|
this.items = res.items
|
|
@@ -333,7 +340,7 @@ export default{
|
|
|
333
340
|
},
|
|
334
341
|
|
|
335
342
|
loadNext(){
|
|
336
|
-
this.socketEmit(`${this.
|
|
343
|
+
this.socketEmit(`${this.computedDataSource}.load`, {
|
|
337
344
|
preset: this.preset,
|
|
338
345
|
afterItem: this.items[this.items.length - 1]
|
|
339
346
|
}, (res) => {
|
|
@@ -394,8 +401,6 @@ export default{
|
|
|
394
401
|
|
|
395
402
|
onHooks(module, event, items){
|
|
396
403
|
|
|
397
|
-
//console.log('onHooks', module, event, items)
|
|
398
|
-
|
|
399
404
|
if(!Array.isArray(items)) return
|
|
400
405
|
items = items.filter((_) => _).map((_) => {
|
|
401
406
|
_['_highlight'] = 1
|
|
@@ -426,7 +431,6 @@ export default{
|
|
|
426
431
|
case 'destroy':
|
|
427
432
|
items.forEach((item) => {
|
|
428
433
|
const idx = this.items.findIndex((_) => _.id === item.id)
|
|
429
|
-
console.log(idx, item)
|
|
430
434
|
if(idx >= 0){
|
|
431
435
|
this.items.splice(idx, 1)
|
|
432
436
|
}
|
package/src/components/Modal.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<Teleport to=".bW9k">
|
|
4
4
|
<Transition :name="transition" @after-leave="onAfterLeave" @after-enter="$emit('show')">
|
|
5
|
-
<div v-if="
|
|
5
|
+
<div v-if="computedState" :class="computedClass" ref="modal" :style="computedStyle">
|
|
6
6
|
<form @submit.prevent="$emit('submit')">
|
|
7
7
|
<div class="modal-head">
|
|
8
8
|
<slot name="head"></slot>
|
|
@@ -83,19 +83,24 @@ export default{
|
|
|
83
83
|
width: parseInt(this.width) + 'px',
|
|
84
84
|
height: parseInt(this.height) + 'px',
|
|
85
85
|
}
|
|
86
|
-
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
computedState(){
|
|
89
|
+
return this.state ? Boolean(this.state) : this._state
|
|
90
|
+
}
|
|
87
91
|
|
|
88
92
|
},
|
|
89
93
|
|
|
90
94
|
data(){
|
|
91
95
|
return {
|
|
92
|
-
isDisabled: 0
|
|
96
|
+
isDisabled: 0,
|
|
97
|
+
_state: false
|
|
93
98
|
}
|
|
94
99
|
},
|
|
95
100
|
|
|
96
101
|
watch:{
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
computedState(to, from){
|
|
99
104
|
|
|
100
105
|
if(to){
|
|
101
106
|
let overlay = document.querySelector('.bW9k')
|
|
@@ -119,6 +124,14 @@ export default{
|
|
|
119
124
|
|
|
120
125
|
methods: {
|
|
121
126
|
|
|
127
|
+
open(){
|
|
128
|
+
this._state = true
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
close(){
|
|
132
|
+
this._state = false
|
|
133
|
+
},
|
|
134
|
+
|
|
122
135
|
onAfterLeave(){
|
|
123
136
|
let overlay = document.querySelector('.bW9k')
|
|
124
137
|
if(overlay){
|
|
@@ -142,6 +155,7 @@ export default{
|
|
|
142
155
|
onDismiss(e){
|
|
143
156
|
if(this.dismissable && this.$refs.modal && e.target.classList.contains('bW9k')){
|
|
144
157
|
this.$emit('dismiss')
|
|
158
|
+
this.close()
|
|
145
159
|
}
|
|
146
160
|
},
|
|
147
161
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<slot name="start"></slot>
|
|
4
4
|
<input :type="type" :disabled="isDisabled" @focus="isActive = true" @blur="onBlur"
|
|
5
5
|
:placeholder="placeholder" :maxlength="maxlength" ref="input" autocomplete="new-password"
|
|
6
|
-
:value="modelValue ?? value" @input="onInput"
|
|
6
|
+
:value="modelValue ?? value" @input="onInput" :readonly="Boolean(readonly)"
|
|
7
7
|
@keydown="onKeyDown"/>
|
|
8
8
|
<div v-if="!!(errors)">
|
|
9
9
|
<svg :class="$style.svg" width="24" height="24" class="fill-red-500" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -62,6 +62,7 @@ export default{
|
|
|
62
62
|
value: String,
|
|
63
63
|
|
|
64
64
|
placeholder: [ String, Number ],
|
|
65
|
+
readonly: undefined,
|
|
65
66
|
maxlength: String,
|
|
66
67
|
type: String,
|
|
67
68
|
|
|
@@ -91,7 +92,7 @@ export default{
|
|
|
91
92
|
return this.customClass ?? [
|
|
92
93
|
this.$style.textbox,
|
|
93
94
|
this.$style['state-' + this.computedState],
|
|
94
|
-
this.isActive ? this.$style.active : '',
|
|
95
|
+
this.isActive && !this.readonly ? this.$style.active : '',
|
|
95
96
|
this.align ? this.$style['align-' + this.align] : '',
|
|
96
97
|
this.size ? this.$style['size-' + this.size] : ''
|
|
97
98
|
].join(' ')
|
|
@@ -162,6 +163,10 @@ export default{
|
|
|
162
163
|
@apply flex items-center border-[1px] border-text-200 bg-base-50 rounded-lg overflow-hidden;
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
.textbox input:read-only{
|
|
167
|
+
@apply bg-text-50;
|
|
168
|
+
}
|
|
169
|
+
|
|
165
170
|
.size-sm{ @apply min-h-[var(--h-cp-sm)]; }
|
|
166
171
|
.size-sm input{ @apply text-sm; }
|
|
167
172
|
|
package/src/components/Toast.vue
CHANGED
|
@@ -85,7 +85,7 @@ export default{
|
|
|
85
85
|
<style module>
|
|
86
86
|
|
|
87
87
|
.toast{
|
|
88
|
-
@apply max-w-[90vw] md:max-w-[640px] bg-base-400 mt-4 mx-auto rounded-
|
|
88
|
+
@apply max-w-[90vw] md:max-w-[640px] bg-base-400 mt-4 mx-auto rounded-xl border-[1px] border-text-50;
|
|
89
89
|
@apply min-h-[var(--h-cp)];
|
|
90
90
|
z-index: 61;
|
|
91
91
|
}
|
package/src/index.js
CHANGED
|
@@ -157,8 +157,9 @@ export default{
|
|
|
157
157
|
app.component('Checkbox', defineAsyncComponent(() => import("./components/Checkbox.vue")))
|
|
158
158
|
app.component('CopyToClipboard', defineAsyncComponent(() => import("./components/CopyToClipboard.vue")))
|
|
159
159
|
app.component('Countdown', defineAsyncComponent(() => import("./components/Countdown.vue")))
|
|
160
|
-
app.component('Dropdown', defineAsyncComponent(() => import("./components/Dropdown.vue")))
|
|
161
160
|
app.component('Datepicker', defineAsyncComponent(() => import("./components/Datepicker.vue")))
|
|
161
|
+
app.component('Dropdown', defineAsyncComponent(() => import("./components/Dropdown.vue")))
|
|
162
|
+
app.component('DynamicTemplate', defineAsyncComponent(() => import("./components/DynamicTemplate.vue")))
|
|
162
163
|
app.component('ErrorText', defineAsyncComponent(() => import("./components/ErrorText.vue")))
|
|
163
164
|
app.component('Feed', defineAsyncComponent(() => import("./components/Feed.vue")))
|
|
164
165
|
app.component('HTMLEditor', defineAsyncComponent(() => import("./components/HTMLEditor.vue")))
|
package/src/utils/helpers.js
CHANGED
|
@@ -1,6 +1,95 @@
|
|
|
1
1
|
const md5 = require("md5");
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const ceil = (num, precision = 0) => {
|
|
7
|
+
var p = Math.pow(10, precision)
|
|
8
|
+
var n = (num * p) * (1 + Number.EPSILON)
|
|
9
|
+
return Math.ceil(n) / p
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const floor = (num, precision = 0) => {
|
|
13
|
+
var p = Math.pow(10, precision)
|
|
14
|
+
var n = (num * p) * (1 + Number.EPSILON)
|
|
15
|
+
return Math.floor(n) / p
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ftWildcard = (key) => {
|
|
19
|
+
const term = key.replace(/[\-\+\<\>\@\(\)\~]/, ' ', 'gi')
|
|
20
|
+
|
|
21
|
+
const searchTerms = []
|
|
22
|
+
term.split(' ').forEach((txt) => {
|
|
23
|
+
if(txt.length >= 3){
|
|
24
|
+
searchTerms.push(`+${txt}*`)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
return searchTerms.join(' ')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const moveFile = function(oldPath, newPath, callback) {
|
|
31
|
+
|
|
32
|
+
fs.rename(oldPath, newPath, function (err) {
|
|
33
|
+
if (err) {
|
|
34
|
+
if (err.code === 'EXDEV') {
|
|
35
|
+
copy();
|
|
36
|
+
} else {
|
|
37
|
+
callback(err);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
callback();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function copy() {
|
|
45
|
+
var readStream = fs.createReadStream(oldPath);
|
|
46
|
+
var writeStream = fs.createWriteStream(newPath);
|
|
47
|
+
|
|
48
|
+
readStream.on('error', callback);
|
|
49
|
+
writeStream.on('error', callback);
|
|
50
|
+
|
|
51
|
+
readStream.on('close', function () {
|
|
52
|
+
fs.unlink(oldPath, callback);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
readStream.pipe(writeStream);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const round = (num, precision = 0) => {
|
|
60
|
+
var p = Math.pow(10, precision)
|
|
61
|
+
var n = (num * p) * (1 + Number.EPSILON)
|
|
62
|
+
return Math.round(n) / p
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const sanitizeMobileNumber = (mobileNumber) => {
|
|
66
|
+
if(typeof mobileNumber !== 'string') return mobileNumber
|
|
67
|
+
|
|
68
|
+
if(mobileNumber.length > 0){
|
|
69
|
+
mobileNumber = mobileNumber.replace(/[\- ]/g, '')
|
|
70
|
+
if(mobileNumber.substring(0, 1) === '0') mobileNumber = '+62' + mobileNumber.substring(1)
|
|
71
|
+
if(mobileNumber.substring(0, 1) !== '+'){
|
|
72
|
+
if(mobileNumber.substring(0, 2) === '62')
|
|
73
|
+
mobileNumber = '+' + mobileNumber
|
|
74
|
+
else
|
|
75
|
+
mobileNumber = '+62' + mobileNumber
|
|
76
|
+
}
|
|
77
|
+
if(mobileNumber.length > 0){
|
|
78
|
+
mobileNumber = mobileNumber[0] + mobileNumber.slice(1).replace(/\D/g, '')
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return mobileNumber
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const saveBase64 = async(data, dir = '/storage/files/images') => {
|
|
86
|
+
if(data.indexOf(',') >= 0){
|
|
87
|
+
data = data.substring(data.indexOf(',') + 1)
|
|
88
|
+
}
|
|
89
|
+
const buffer = Buffer.from(data, 'base64');
|
|
90
|
+
return await saveBuffer(buffer, dir)
|
|
91
|
+
}
|
|
92
|
+
|
|
4
93
|
const saveBuffer = async(buffer, dir = '/storage/files/images') => {
|
|
5
94
|
|
|
6
95
|
const { fileTypeFromBuffer } = await import('file-type')
|
|
@@ -17,19 +106,72 @@ const saveBuffer = async(buffer, dir = '/storage/files/images') => {
|
|
|
17
106
|
return filename
|
|
18
107
|
}
|
|
19
108
|
|
|
20
|
-
const
|
|
21
|
-
const term = key.replace(/[\-\+\<\>\@\(\)\~]/, ' ', 'gi')
|
|
109
|
+
const saveFromUrl = async(url, dir = '/storage/files/images') => {
|
|
22
110
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
111
|
+
const res = await axios({
|
|
112
|
+
method: 'GET',
|
|
113
|
+
url,
|
|
114
|
+
responseType: 'arraybuffer',
|
|
28
115
|
})
|
|
29
|
-
|
|
116
|
+
const buffer = Buffer.from(res.data)
|
|
117
|
+
return await saveBuffer(buffer, dir)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
121
|
+
|
|
122
|
+
const strSlug = (title, separator) => {
|
|
123
|
+
if (typeof separator == 'undefined') separator = '-';
|
|
124
|
+
|
|
125
|
+
// Convert all dashes/underscores into separator
|
|
126
|
+
var flip = separator === '-' ? '_' : '-';
|
|
127
|
+
title = title.replace(flip, separator);
|
|
128
|
+
|
|
129
|
+
// Remove all characters that are not the separator, letters, numbers, or whitespace.
|
|
130
|
+
title = title.toLowerCase()
|
|
131
|
+
.replace(new RegExp('[^a-z0-9' + separator + '\\s]', 'g'), '');
|
|
132
|
+
|
|
133
|
+
// Replace all separator characters and whitespace by a single separator
|
|
134
|
+
title = title.replace(new RegExp('[' + separator + '\\s]+', 'g'), separator);
|
|
135
|
+
|
|
136
|
+
return title.replace(new RegExp('^[' + separator + '\\s]+|[' + separator + '\\s]+$', 'g'),'');
|
|
30
137
|
}
|
|
31
138
|
|
|
139
|
+
const strVars = (text, vars) => {
|
|
140
|
+
|
|
141
|
+
(text.match(/\{.*?(?=\})\}/gi) ?? []).forEach((match) => {
|
|
142
|
+
const key = match.substring(1, match.length - 1)
|
|
143
|
+
const value = vars[key] ?? ''
|
|
144
|
+
text = text.replace(match, value)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return text
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const writeStorage = (path, content, append = false) => {
|
|
151
|
+
|
|
152
|
+
if(path.startsWith('/')) path = path.substring(1)
|
|
153
|
+
|
|
154
|
+
if(append){
|
|
155
|
+
fs.appendFileSync(process.env.ROOT_PATH + `/storage/${path}`, content)
|
|
156
|
+
}
|
|
157
|
+
else{
|
|
158
|
+
fs.writeFileSync(process.env.ROOT_PATH + `/storage/${path}`, content)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
32
163
|
module.exports = {
|
|
164
|
+
ceil,
|
|
165
|
+
floor,
|
|
33
166
|
ftWildcard,
|
|
34
|
-
|
|
167
|
+
moveFile,
|
|
168
|
+
round,
|
|
169
|
+
sanitizeMobileNumber,
|
|
170
|
+
saveBase64,
|
|
171
|
+
saveBuffer,
|
|
172
|
+
saveFromUrl,
|
|
173
|
+
sleep,
|
|
174
|
+
strSlug,
|
|
175
|
+
strVars,
|
|
176
|
+
writeStorage
|
|
35
177
|
}
|
package/src/utils/helpers.mjs
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
let __uid = new Date().getTime()
|
|
3
|
-
|
|
4
|
-
const uid = function(prefix){
|
|
5
|
-
return (prefix ?? '') + __uid++;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
2
|
const csvToArray = (str, delimiter = null) => {
|
|
9
3
|
|
|
10
4
|
if(!delimiter){
|
|
@@ -33,16 +27,85 @@ const csvToArray = (str, delimiter = null) => {
|
|
|
33
27
|
return arr;
|
|
34
28
|
}
|
|
35
29
|
|
|
30
|
+
const downsizeImage = async (url, targetWidth, imageType, quality, opt = {}) => {
|
|
31
|
+
|
|
32
|
+
let defaultImageType = 'image/jpeg'
|
|
33
|
+
|
|
34
|
+
if(url instanceof File){
|
|
35
|
+
|
|
36
|
+
defaultImageType = url.type
|
|
37
|
+
|
|
38
|
+
await (new Promise((resolve, reject) => {
|
|
39
|
+
var reader = new FileReader();
|
|
40
|
+
reader.addEventListener('load', () => {
|
|
41
|
+
url = reader.result
|
|
42
|
+
resolve()
|
|
43
|
+
}, false)
|
|
44
|
+
reader.addEventListener('error', (e) => {
|
|
45
|
+
reject(e)
|
|
46
|
+
})
|
|
47
|
+
reader.readAsDataURL(url);
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
|
|
53
|
+
const image = new Image()
|
|
54
|
+
image.addEventListener('load', () => {
|
|
55
|
+
|
|
56
|
+
const canvasWidth = image.width > targetWidth ? targetWidth : image.width
|
|
57
|
+
const canvasHeight = Math.round((image.height / image.width) * canvasWidth)
|
|
58
|
+
|
|
59
|
+
const canvas = document.createElement('canvas');
|
|
60
|
+
canvas.width = canvasWidth;
|
|
61
|
+
canvas.height = canvasHeight;
|
|
62
|
+
|
|
63
|
+
if(opt.constraint){
|
|
64
|
+
if(canvasWidth / canvasHeight !== opt.constraint)
|
|
65
|
+
reject(new Error('Image constraint error'))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ctx = canvas.getContext("2d");
|
|
69
|
+
ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
|
|
70
|
+
|
|
71
|
+
canvas.toBlob((blob) => {
|
|
72
|
+
resolve(blob)
|
|
73
|
+
}, imageType ?? defaultImageType, quality ?? .8)
|
|
74
|
+
})
|
|
75
|
+
image.addEventListener('error', (e) => {
|
|
76
|
+
reject(e)
|
|
77
|
+
})
|
|
78
|
+
image.src = url
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
36
82
|
const parseBoolean = function(exp){
|
|
37
83
|
return [ 'true', 1, true ].includes(typeof exp === 'string' ?
|
|
38
84
|
exp.toLowerCase() : exp)
|
|
39
85
|
}
|
|
40
86
|
|
|
87
|
+
const strVars = function(text, vars){
|
|
88
|
+
|
|
89
|
+
(text.match(/\{.*?(?=\})\}/gi) ?? []).forEach((match) => {
|
|
90
|
+
const key = match.substring(1, match.length - 1)
|
|
91
|
+
const value = vars[key] ?? ''
|
|
92
|
+
text = text.replace(match, value)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
return text
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let __uid = new Date().getTime()
|
|
99
|
+
const uid = function(prefix){
|
|
100
|
+
return (prefix ?? '') + __uid++;
|
|
101
|
+
}
|
|
102
|
+
|
|
41
103
|
export {
|
|
104
|
+
downsizeImage,
|
|
42
105
|
uid,
|
|
43
106
|
observeInit,
|
|
44
107
|
csvToArray,
|
|
45
|
-
parseBoolean
|
|
108
|
+
parseBoolean,
|
|
46
109
|
}
|
|
47
110
|
|
|
48
111
|
function observeInit(){
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
let selection
|
|
3
|
+
|
|
4
|
+
const saveSelection = (container) =>{
|
|
5
|
+
|
|
6
|
+
const sel = window.getSelection()
|
|
7
|
+
if(sel.rangeCount < 1) return
|
|
8
|
+
const range = window.getSelection().getRangeAt(0)
|
|
9
|
+
|
|
10
|
+
const preSelectionRange = range.cloneRange()
|
|
11
|
+
preSelectionRange.selectNodeContents(container)
|
|
12
|
+
preSelectionRange.setEnd(range.startContainer, range.startOffset)
|
|
13
|
+
const start = preSelectionRange.toString().length
|
|
14
|
+
|
|
15
|
+
selection = {
|
|
16
|
+
start: start,
|
|
17
|
+
end: start + range.toString().length,
|
|
18
|
+
container,
|
|
19
|
+
text: range.toString()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return selection
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const restoreSelection = () => {
|
|
26
|
+
if(!selection) return
|
|
27
|
+
|
|
28
|
+
const range = document.createRange();
|
|
29
|
+
range.setStart(selection.container, 0);
|
|
30
|
+
range.collapse(true);
|
|
31
|
+
|
|
32
|
+
const savedSel = selection
|
|
33
|
+
|
|
34
|
+
let charIndex = 0
|
|
35
|
+
let nodeStack = [selection.container], node, foundStart = false, stop = false;
|
|
36
|
+
while (!stop && (node = nodeStack.pop())) {
|
|
37
|
+
if (node.nodeType === 3) {
|
|
38
|
+
var nextCharIndex = charIndex + node.length;
|
|
39
|
+
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
|
|
40
|
+
range.setStart(node, savedSel.start - charIndex);
|
|
41
|
+
foundStart = true;
|
|
42
|
+
}
|
|
43
|
+
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
|
|
44
|
+
range.setEnd(node, savedSel.end - charIndex);
|
|
45
|
+
stop = true;
|
|
46
|
+
}
|
|
47
|
+
charIndex = nextCharIndex;
|
|
48
|
+
} else {
|
|
49
|
+
var i = node.childNodes.length;
|
|
50
|
+
while (i--) {
|
|
51
|
+
nodeStack.push(node.childNodes[i]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const sel = window.getSelection();
|
|
57
|
+
sel.removeAllRanges();
|
|
58
|
+
sel.addRange(range);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
saveSelection,
|
|
63
|
+
restoreSelection
|
|
64
|
+
}
|