@fy-/fws-vue 2.1.6 → 2.1.8
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/components/fws/CmsArticleBoxed.vue +23 -20
- package/components/fws/CmsArticleSingle.vue +74 -68
- package/components/fws/DataTable.vue +132 -125
- package/components/fws/FilterData.vue +99 -101
- package/components/fws/UserData.vue +33 -32
- package/components/fws/UserFlow.vue +163 -155
- package/components/fws/UserOAuth2.vue +73 -72
- package/components/fws/UserProfile.vue +98 -101
- package/components/fws/UserProfileStrict.vue +65 -64
- package/components/ssr/ClientOnly.ts +7 -7
- package/components/ui/DefaultBreadcrumb.vue +13 -13
- package/components/ui/DefaultConfirm.vue +35 -34
- package/components/ui/DefaultDateSelection.vue +19 -17
- package/components/ui/DefaultDropdown.vue +25 -25
- package/components/ui/DefaultDropdownLink.vue +15 -14
- package/components/ui/DefaultGallery.vue +179 -168
- package/components/ui/DefaultInput.vue +121 -126
- package/components/ui/DefaultLoader.vue +17 -17
- package/components/ui/DefaultModal.vue +35 -33
- package/components/ui/DefaultNotif.vue +50 -52
- package/components/ui/DefaultPaging.vue +92 -95
- package/components/ui/DefaultSidebar.vue +29 -25
- package/components/ui/DefaultTagInput.vue +121 -119
- package/components/ui/transitions/CollapseTransition.vue +1 -1
- package/components/ui/transitions/ExpandTransition.vue +1 -1
- package/components/ui/transitions/FadeTransition.vue +1 -1
- package/components/ui/transitions/ScaleTransition.vue +1 -1
- package/components/ui/transitions/SlideTransition.vue +3 -3
- package/composables/event-bus.ts +10 -8
- package/composables/rest.ts +59 -56
- package/composables/seo.ts +111 -95
- package/composables/ssr.ts +64 -62
- package/composables/templating.ts +57 -57
- package/composables/translations.ts +13 -13
- package/env.d.ts +6 -4
- package/index.ts +101 -98
- package/package.json +7 -7
- package/stores/serverRouter.ts +25 -25
- package/stores/user.ts +79 -72
- package/types.d.ts +65 -65
|
@@ -1,3 +1,119 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref } from 'vue'
|
|
3
|
+
import { useEventBus } from '../../composables/event-bus'
|
|
4
|
+
|
|
5
|
+
type colorType = 'blue' | 'red' | 'green' | 'purple' | 'orange' | 'neutral'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(
|
|
8
|
+
defineProps<{
|
|
9
|
+
modelValue: string[]
|
|
10
|
+
color?: colorType
|
|
11
|
+
label?: string
|
|
12
|
+
id: string
|
|
13
|
+
separators?: string[]
|
|
14
|
+
autofocus?: boolean
|
|
15
|
+
help?: string
|
|
16
|
+
maxLenghtPerTag?: number
|
|
17
|
+
error?: string
|
|
18
|
+
copyButton?: boolean
|
|
19
|
+
}>(),
|
|
20
|
+
{
|
|
21
|
+
copyButton: false,
|
|
22
|
+
maxLenghtPerTag: 0,
|
|
23
|
+
color: 'blue',
|
|
24
|
+
label: 'Tags',
|
|
25
|
+
separators: () => [','],
|
|
26
|
+
autofocus: false,
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const textInput = ref<HTMLElement>()
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits(['update:modelValue'])
|
|
33
|
+
const model = computed({
|
|
34
|
+
get: () => props.modelValue,
|
|
35
|
+
set: (items) => {
|
|
36
|
+
emit('update:modelValue', items)
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
onMounted(() => {
|
|
41
|
+
if (props.autofocus) {
|
|
42
|
+
focusInput()
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
const eventBus = useEventBus()
|
|
46
|
+
async function copyText() {
|
|
47
|
+
const text = model.value.join(', ')
|
|
48
|
+
await navigator.clipboard.writeText(text)
|
|
49
|
+
eventBus.emit('SendNotif', {
|
|
50
|
+
title: 'Text copied!',
|
|
51
|
+
type: 'success',
|
|
52
|
+
time: 2500,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleInput(event: any) {
|
|
57
|
+
const separatorsRegex = new RegExp(props.separators.join('|'))
|
|
58
|
+
if (separatorsRegex.test(event.data)) {
|
|
59
|
+
addTag()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function addTag() {
|
|
64
|
+
if (!textInput.value) return
|
|
65
|
+
|
|
66
|
+
const separatorsRegex = new RegExp(props.separators.join('|'))
|
|
67
|
+
if (!textInput.value.textContent) return
|
|
68
|
+
const newTags = textInput.value.textContent
|
|
69
|
+
.split(separatorsRegex)
|
|
70
|
+
.map((tag: string) => tag.trim())
|
|
71
|
+
.filter((tag: string) => tag.length > 0)
|
|
72
|
+
model.value.push(...newTags)
|
|
73
|
+
textInput.value.textContent = ''
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function removeTag(index: number) {
|
|
77
|
+
model.value.splice(index, 1)
|
|
78
|
+
focusInput()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function removeLastTag() {
|
|
82
|
+
if (!textInput.value) return
|
|
83
|
+
if (textInput.value.textContent === '') {
|
|
84
|
+
model.value.pop()
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (!textInput.value.textContent) return
|
|
88
|
+
textInput.value.textContent = textInput.value.textContent.slice(0, -1)
|
|
89
|
+
|
|
90
|
+
const range = document.createRange()
|
|
91
|
+
const sel = window.getSelection()
|
|
92
|
+
range.selectNodeContents(textInput.value)
|
|
93
|
+
range.collapse(false)
|
|
94
|
+
if (!sel) return
|
|
95
|
+
sel.removeAllRanges()
|
|
96
|
+
sel.addRange(range)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function focusInput() {
|
|
100
|
+
if (!textInput.value) return
|
|
101
|
+
|
|
102
|
+
textInput.value.focus()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handlePaste(e: any) {
|
|
106
|
+
if (!textInput.value) return
|
|
107
|
+
// @ts-expect-error: Property 'clipboardData' does not exist on type 'ClipboardEvent'.
|
|
108
|
+
const text = (e.clipboardData || window.clipboardData).getData('text')
|
|
109
|
+
const separatorsRegex = new RegExp(props.separators.join('|'), 'g')
|
|
110
|
+
const pasteText = text.replace(separatorsRegex, ',')
|
|
111
|
+
textInput.value.textContent += pasteText
|
|
112
|
+
e.preventDefault()
|
|
113
|
+
addTag()
|
|
114
|
+
}
|
|
115
|
+
</script>
|
|
116
|
+
|
|
1
117
|
<template>
|
|
2
118
|
<div>
|
|
3
119
|
<div
|
|
@@ -34,16 +150,16 @@
|
|
|
34
150
|
</button>
|
|
35
151
|
</span>
|
|
36
152
|
<div
|
|
37
|
-
contenteditable
|
|
38
|
-
class="input"
|
|
39
153
|
:id="`tags_${id}`"
|
|
40
154
|
ref="textInput"
|
|
155
|
+
contenteditable
|
|
156
|
+
class="input"
|
|
157
|
+
placeholder="Add a tag..."
|
|
41
158
|
@input="handleInput"
|
|
42
159
|
@paste.prevent="handlePaste"
|
|
43
|
-
|
|
44
|
-
></div>
|
|
160
|
+
/>
|
|
45
161
|
</div>
|
|
46
|
-
<div class="flex justify-end mt-1"
|
|
162
|
+
<div v-if="copyButton" class="flex justify-end mt-1">
|
|
47
163
|
<button class="btn neutral small" type="button" @click.prevent="copyText">
|
|
48
164
|
Copy tags
|
|
49
165
|
</button>
|
|
@@ -51,120 +167,6 @@
|
|
|
51
167
|
</div>
|
|
52
168
|
</template>
|
|
53
169
|
|
|
54
|
-
<script setup lang="ts">
|
|
55
|
-
import { ref, computed, onMounted } from "vue";
|
|
56
|
-
import { useEventBus } from "../../composables/event-bus";
|
|
57
|
-
type colorType = "blue" | "red" | "green" | "purple" | "orange" | "neutral";
|
|
58
|
-
|
|
59
|
-
const props = withDefaults(
|
|
60
|
-
defineProps<{
|
|
61
|
-
modelValue: string[];
|
|
62
|
-
color?: colorType;
|
|
63
|
-
label?: string;
|
|
64
|
-
id: string;
|
|
65
|
-
separators?: string[];
|
|
66
|
-
autofocus?: boolean;
|
|
67
|
-
help?: string;
|
|
68
|
-
maxLenghtPerTag?: number;
|
|
69
|
-
error?: string;
|
|
70
|
-
copyButton?: boolean;
|
|
71
|
-
}>(),
|
|
72
|
-
{
|
|
73
|
-
copyButton: false,
|
|
74
|
-
maxLenghtPerTag: 0,
|
|
75
|
-
color: "blue",
|
|
76
|
-
label: "Tags",
|
|
77
|
-
separators: () => [","],
|
|
78
|
-
autofocus: false,
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const textInput = ref<HTMLElement>();
|
|
83
|
-
|
|
84
|
-
const emit = defineEmits(["update:modelValue"]);
|
|
85
|
-
const model = computed({
|
|
86
|
-
get: () => props.modelValue,
|
|
87
|
-
set: (items) => {
|
|
88
|
-
emit("update:modelValue", items);
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
onMounted(() => {
|
|
93
|
-
if (props.autofocus) {
|
|
94
|
-
focusInput();
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
const eventBus = useEventBus();
|
|
98
|
-
const copyText = async () => {
|
|
99
|
-
const text = model.value.join(", ");
|
|
100
|
-
await navigator.clipboard.writeText(text);
|
|
101
|
-
eventBus.emit("SendNotif", {
|
|
102
|
-
title: "Text copied!",
|
|
103
|
-
type: "success",
|
|
104
|
-
time: 2500,
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const handleInput = (event: any) => {
|
|
109
|
-
const separatorsRegex = new RegExp(props.separators.join("|"));
|
|
110
|
-
if (separatorsRegex.test(event.data)) {
|
|
111
|
-
addTag();
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const addTag = () => {
|
|
116
|
-
if (!textInput.value) return;
|
|
117
|
-
|
|
118
|
-
const separatorsRegex = new RegExp(props.separators.join("|"));
|
|
119
|
-
const newTags = textInput.value.innerText
|
|
120
|
-
.split(separatorsRegex)
|
|
121
|
-
.map((tag: string) => tag.trim())
|
|
122
|
-
.filter((tag: string) => tag.length > 0);
|
|
123
|
-
model.value.push(...newTags);
|
|
124
|
-
textInput.value.innerText = "";
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const removeTag = (index: number) => {
|
|
128
|
-
model.value.splice(index, 1);
|
|
129
|
-
focusInput();
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const removeLastTag = () => {
|
|
133
|
-
if (!textInput.value) return;
|
|
134
|
-
if (textInput.value.innerText === "") {
|
|
135
|
-
model.value.pop();
|
|
136
|
-
} else {
|
|
137
|
-
const currentLength = textInput.value.innerText.length;
|
|
138
|
-
textInput.value.innerText = textInput.value.innerText.slice(0, -1);
|
|
139
|
-
|
|
140
|
-
const range = document.createRange();
|
|
141
|
-
const sel = window.getSelection();
|
|
142
|
-
range.selectNodeContents(textInput.value);
|
|
143
|
-
range.collapse(false);
|
|
144
|
-
if (!sel) return;
|
|
145
|
-
sel.removeAllRanges();
|
|
146
|
-
sel.addRange(range);
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
const focusInput = () => {
|
|
150
|
-
if (!textInput.value) return;
|
|
151
|
-
|
|
152
|
-
textInput.value.focus();
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const handlePaste = (e: any) => {
|
|
156
|
-
if (!textInput.value) return;
|
|
157
|
-
|
|
158
|
-
// @ts-ignore
|
|
159
|
-
const text = (e.clipboardData || window.clipboardData).getData("text");
|
|
160
|
-
const separatorsRegex = new RegExp(props.separators.join("|"), "g");
|
|
161
|
-
const pasteText = text.replace(separatorsRegex, ",");
|
|
162
|
-
textInput.value.innerText += pasteText;
|
|
163
|
-
e.preventDefault();
|
|
164
|
-
addTag();
|
|
165
|
-
};
|
|
166
|
-
</script>
|
|
167
|
-
|
|
168
170
|
<style scoped>
|
|
169
171
|
.tags-input {
|
|
170
172
|
cursor: text;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const props = defineProps<{
|
|
3
|
-
animation: string
|
|
4
|
-
}>()
|
|
3
|
+
animation: string
|
|
4
|
+
}>()
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<template>
|
|
8
8
|
<Transition :name="props.animation" mode="out-in">
|
|
9
|
-
<slot
|
|
9
|
+
<slot />
|
|
10
10
|
</Transition>
|
|
11
11
|
</template>
|
|
12
12
|
|
package/composables/event-bus.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { Emitter } from 'mitt'
|
|
2
|
+
import { inject } from 'vue'
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
[key: string]: any
|
|
6
|
-
}
|
|
4
|
+
export interface Events {
|
|
5
|
+
[key: string]: any
|
|
6
|
+
}
|
|
7
7
|
|
|
8
|
+
// @ts-expect-error: Emitter is not exported
|
|
8
9
|
export function useEventBus(): Emitter<Events> {
|
|
9
|
-
|
|
10
|
+
// @ts-expect-error: Emitter is not exported
|
|
11
|
+
const eventBus = inject<Emitter<Events>>('fwsVueEventBus')
|
|
10
12
|
|
|
11
|
-
if (!eventBus) throw new Error(
|
|
13
|
+
if (!eventBus) throw new Error('Did you apply app.use(fwsVue)?')
|
|
12
14
|
|
|
13
|
-
return eventBus
|
|
15
|
+
return eventBus
|
|
14
16
|
}
|
package/composables/rest.ts
CHANGED
|
@@ -1,91 +1,94 @@
|
|
|
1
|
-
import { RestMethod, RestParams
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { useEventBus } from
|
|
1
|
+
import type { RestMethod, RestParams } from '@fy-/fws-js'
|
|
2
|
+
import { getMode, rest, stringHash } from '@fy-/fws-js'
|
|
3
|
+
import { useServerRouter } from '../stores/serverRouter'
|
|
4
|
+
import { useEventBus } from './event-bus'
|
|
5
|
+
import { isServerRendered } from './ssr'
|
|
5
6
|
|
|
6
7
|
export interface APIPaging {
|
|
7
|
-
page_no: number
|
|
8
|
-
results_per_page: number
|
|
9
|
-
page_max: number
|
|
10
|
-
page_max_relation: string
|
|
11
|
-
count: number
|
|
8
|
+
page_no: number
|
|
9
|
+
results_per_page: number
|
|
10
|
+
page_max: number
|
|
11
|
+
page_max_relation: string
|
|
12
|
+
count: number
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface APIResult {
|
|
15
|
-
result:
|
|
16
|
-
param?: string
|
|
17
|
-
code?: number
|
|
18
|
-
error?: string
|
|
19
|
-
request?: string
|
|
20
|
-
time?: number
|
|
21
|
-
token?: string
|
|
22
|
-
paging?: APIPaging
|
|
23
|
-
message?: string
|
|
24
|
-
fvReject?: boolean
|
|
25
|
-
data?: any
|
|
26
|
-
status?: number
|
|
16
|
+
result: 'redirect' | 'success' | 'error'
|
|
17
|
+
param?: string
|
|
18
|
+
code?: number
|
|
19
|
+
error?: string
|
|
20
|
+
request?: string
|
|
21
|
+
time?: number
|
|
22
|
+
token?: string
|
|
23
|
+
paging?: APIPaging
|
|
24
|
+
message?: string
|
|
25
|
+
fvReject?: boolean
|
|
26
|
+
data?: any
|
|
27
|
+
status?: number
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export function useRest(): <ResultType extends APIResult>(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
url: string,
|
|
32
|
+
method: RestMethod,
|
|
33
|
+
params?: RestParams,
|
|
33
34
|
) => Promise<ResultType> {
|
|
34
|
-
const serverRouter = useServerRouter()
|
|
35
|
-
const eventBus = useEventBus()
|
|
35
|
+
const serverRouter = useServerRouter()
|
|
36
|
+
const eventBus = useEventBus()
|
|
36
37
|
|
|
37
38
|
return async <ResultType extends APIResult>(
|
|
38
39
|
url: string,
|
|
39
40
|
method: RestMethod,
|
|
40
41
|
params?: RestParams,
|
|
41
42
|
): Promise<ResultType> => {
|
|
42
|
-
let urlForHash: string = url
|
|
43
|
+
let urlForHash: string = url
|
|
43
44
|
try {
|
|
44
|
-
const urlParse = new URL(url)
|
|
45
|
-
urlForHash = urlParse.pathname + urlParse.search
|
|
46
|
-
}
|
|
47
|
-
|
|
45
|
+
const urlParse = new URL(url)
|
|
46
|
+
urlForHash = urlParse.pathname + urlParse.search
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
urlForHash = url
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
const requestHash = stringHash(
|
|
51
53
|
urlForHash + method + JSON.stringify(params),
|
|
52
|
-
)
|
|
54
|
+
)
|
|
53
55
|
if (isServerRendered()) {
|
|
54
|
-
const hasResult = serverRouter.getResult(requestHash)
|
|
56
|
+
const hasResult = serverRouter.getResult(requestHash)
|
|
55
57
|
if (hasResult !== undefined) {
|
|
56
|
-
const result = hasResult as ResultType
|
|
57
|
-
serverRouter.removeResult(requestHash)
|
|
58
|
-
if (result.result ===
|
|
59
|
-
eventBus.emit(
|
|
60
|
-
eventBus.emit(
|
|
61
|
-
return Promise.reject(result)
|
|
58
|
+
const result = hasResult as ResultType
|
|
59
|
+
serverRouter.removeResult(requestHash)
|
|
60
|
+
if (result.result === 'error') {
|
|
61
|
+
eventBus.emit('main-loading', false)
|
|
62
|
+
eventBus.emit('rest-error', result)
|
|
63
|
+
return Promise.reject(result)
|
|
62
64
|
}
|
|
63
|
-
return Promise.resolve(result)
|
|
65
|
+
return Promise.resolve(result)
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
try {
|
|
68
|
-
const restResult: ResultType = await rest(url, method, params)
|
|
69
|
-
if (getMode() ===
|
|
70
|
+
const restResult: ResultType = await rest(url, method, params)
|
|
71
|
+
if (getMode() === 'ssr') {
|
|
70
72
|
serverRouter.addResult(
|
|
71
73
|
requestHash,
|
|
72
74
|
JSON.parse(JSON.stringify(restResult)),
|
|
73
|
-
)
|
|
75
|
+
)
|
|
74
76
|
}
|
|
75
|
-
if (restResult.result ===
|
|
76
|
-
eventBus.emit(
|
|
77
|
-
eventBus.emit(
|
|
78
|
-
return Promise.reject(restResult)
|
|
77
|
+
if (restResult.result === 'error') {
|
|
78
|
+
eventBus.emit('main-loading', false)
|
|
79
|
+
eventBus.emit('rest-error', restResult)
|
|
80
|
+
return Promise.reject(restResult)
|
|
79
81
|
}
|
|
80
|
-
return Promise.resolve(restResult)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
return Promise.resolve(restResult)
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const restError: ResultType = error as ResultType
|
|
86
|
+
if (getMode() === 'ssr') {
|
|
87
|
+
serverRouter.addResult(requestHash, restError)
|
|
85
88
|
}
|
|
86
|
-
eventBus.emit(
|
|
87
|
-
eventBus.emit(
|
|
88
|
-
return Promise.resolve(restError)
|
|
89
|
+
eventBus.emit('main-loading', false)
|
|
90
|
+
eventBus.emit('rest-error', restError)
|
|
91
|
+
return Promise.resolve(restError)
|
|
89
92
|
}
|
|
90
|
-
}
|
|
93
|
+
}
|
|
91
94
|
}
|