@opendesign-plus-test/components 0.0.1-rc.45 → 0.0.1-rc.47
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/dist/chunk-OElCookieNotice.cjs.js +1 -1
- package/dist/chunk-OElCookieNotice.es.js +46 -66
- package/dist/components/OHeaderSearch.vue.d.ts +534 -812
- package/dist/components.cjs.js +41 -41
- package/dist/components.css +1 -1
- package/dist/components.es.js +10253 -11228
- package/dist/index.d.ts +0 -1
- package/package.json +2 -2
- package/src/assets/svg-icons/icon-delete.svg +1 -5
- package/src/components/OHeaderSearch.vue +425 -407
- package/src/i18n/en.ts +0 -10
- package/src/i18n/zh.ts +0 -10
- package/src/index.ts +0 -1
- package/dist/components/search/OSearchInput.vue.d.ts +0 -1003
- package/dist/components/search/composables/useImageSearch.d.ts +0 -48
- package/dist/components/search/composables/useKeywordHighlight.d.ts +0 -2
- package/dist/components/search/composables/useSearchHistory.d.ts +0 -14
- package/dist/components/search/index.d.ts +0 -590
- package/dist/components/search/internal/HighlightText.vue.d.ts +0 -9
- package/dist/components/search/internal/SearchImageInput.vue.d.ts +0 -716
- package/dist/components/search/internal/SearchPanel.vue.d.ts +0 -100
- package/dist/components/search/types.d.ts +0 -20
- package/src/assets/svg-icons/icon-delete-hover.svg +0 -4
- package/src/assets/svg-icons/icon-image-close.svg +0 -4
- package/src/assets/svg-icons/icon-image-upload.svg +0 -3
- package/src/assets/svg-icons/icon-image-zoomin.svg +0 -3
- package/src/assets/svg-icons/icon-refresh.svg +0 -3
- package/src/components/search/OSearchInput.vue +0 -463
- package/src/components/search/composables/useImageSearch.ts +0 -157
- package/src/components/search/composables/useKeywordHighlight.ts +0 -30
- package/src/components/search/composables/useSearchHistory.ts +0 -75
- package/src/components/search/index.ts +0 -23
- package/src/components/search/internal/HighlightText.vue +0 -37
- package/src/components/search/internal/SearchImageInput.vue +0 -488
- package/src/components/search/internal/SearchPanel.vue +0 -430
- package/src/components/search/types.ts +0 -25
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { ref, onBeforeUnmount, type Ref } from 'vue';
|
|
2
|
-
import type { OSearchUploadImageFn } from '../types';
|
|
3
|
-
|
|
4
|
-
export interface UseImageSearchOptions {
|
|
5
|
-
uploadImage?: Ref<OSearchUploadImageFn | undefined>;
|
|
6
|
-
maxImageSize: Ref<number>;
|
|
7
|
-
allowedImageTypes?: Ref<string[]>;
|
|
8
|
-
onUploadStart?: (file: File) => void;
|
|
9
|
-
onUploadSuccess?: (url: string, file: File) => void;
|
|
10
|
-
onUploadError?: (error: unknown, file: File) => void;
|
|
11
|
-
onValidationError?: (reason: 'size' | 'type', file: File) => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function useImageSearch(options: UseImageSearchOptions) {
|
|
15
|
-
const previewUrl = ref('');
|
|
16
|
-
const file = ref<File | null>(null);
|
|
17
|
-
const uploadedUrl = ref('');
|
|
18
|
-
const isUploading = ref(false);
|
|
19
|
-
let uploadPromise: Promise<void> | null = null;
|
|
20
|
-
|
|
21
|
-
const validate = (f: File) => {
|
|
22
|
-
const allowed = options.allowedImageTypes?.value;
|
|
23
|
-
const typeOk = allowed && allowed.length > 0 ? allowed.includes(f.type) : f.type?.startsWith('image/');
|
|
24
|
-
if (!f.type || !typeOk) {
|
|
25
|
-
options.onValidationError?.('type', f);
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
if (options.maxImageSize.value > 0 && f.size > options.maxImageSize.value) {
|
|
29
|
-
options.onValidationError?.('size', f);
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const reset = () => {
|
|
36
|
-
if (previewUrl.value) {
|
|
37
|
-
try {
|
|
38
|
-
URL.revokeObjectURL(previewUrl.value);
|
|
39
|
-
} catch {
|
|
40
|
-
// ignore
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
previewUrl.value = '';
|
|
44
|
-
file.value = null;
|
|
45
|
-
uploadedUrl.value = '';
|
|
46
|
-
isUploading.value = false;
|
|
47
|
-
uploadPromise = null;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const setExternalUrl = (url: string) => {
|
|
51
|
-
if (!url) {
|
|
52
|
-
if (!file.value) reset();
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (uploadedUrl.value === url) return;
|
|
56
|
-
if (previewUrl.value && previewUrl.value.startsWith('blob:')) {
|
|
57
|
-
try {
|
|
58
|
-
URL.revokeObjectURL(previewUrl.value);
|
|
59
|
-
} catch {
|
|
60
|
-
// ignore
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
uploadedUrl.value = url;
|
|
64
|
-
previewUrl.value = url;
|
|
65
|
-
file.value = null;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const handleImageFile = (f: File) => {
|
|
69
|
-
if (!validate(f)) return;
|
|
70
|
-
|
|
71
|
-
reset();
|
|
72
|
-
file.value = f;
|
|
73
|
-
previewUrl.value = URL.createObjectURL(f);
|
|
74
|
-
options.onUploadStart?.(f);
|
|
75
|
-
|
|
76
|
-
const fn = options.uploadImage?.value;
|
|
77
|
-
if (!fn) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
isUploading.value = true;
|
|
82
|
-
uploadPromise = Promise.resolve()
|
|
83
|
-
.then(() => fn(f))
|
|
84
|
-
.then((url) => {
|
|
85
|
-
if (url && file.value === f) {
|
|
86
|
-
uploadedUrl.value = url;
|
|
87
|
-
options.onUploadSuccess?.(url, f);
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
.catch((err) => {
|
|
91
|
-
options.onUploadError?.(err, f);
|
|
92
|
-
})
|
|
93
|
-
.finally(() => {
|
|
94
|
-
isUploading.value = false;
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const onPaste = (event: ClipboardEvent) => {
|
|
99
|
-
const items = event.clipboardData?.items;
|
|
100
|
-
if (!items) return;
|
|
101
|
-
for (let i = 0; i < items.length; i++) {
|
|
102
|
-
const item = items[i];
|
|
103
|
-
if (item.type.indexOf('image') !== -1) {
|
|
104
|
-
const f = item.getAsFile();
|
|
105
|
-
if (f) handleImageFile(f);
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const onDragOver = (event: DragEvent) => {
|
|
112
|
-
event.preventDefault();
|
|
113
|
-
event.stopPropagation();
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const onDrop = (event: DragEvent) => {
|
|
117
|
-
event.preventDefault();
|
|
118
|
-
event.stopPropagation();
|
|
119
|
-
const f = event.dataTransfer?.files?.[0];
|
|
120
|
-
if (f && f.type.indexOf('image') !== -1) {
|
|
121
|
-
handleImageFile(f);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const onFileChange = (event: Event) => {
|
|
126
|
-
const target = event.target as HTMLInputElement;
|
|
127
|
-
const f = target.files?.[0];
|
|
128
|
-
if (f) handleImageFile(f);
|
|
129
|
-
if (target) target.value = '';
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const pickFile = (input: HTMLInputElement | undefined) => {
|
|
133
|
-
if (!input) return;
|
|
134
|
-
input.value = '';
|
|
135
|
-
input.click();
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const awaitUpload = () => uploadPromise ?? Promise.resolve();
|
|
139
|
-
|
|
140
|
-
onBeforeUnmount(reset);
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
previewUrl,
|
|
144
|
-
file,
|
|
145
|
-
uploadedUrl,
|
|
146
|
-
isUploading,
|
|
147
|
-
onPaste,
|
|
148
|
-
onDrop,
|
|
149
|
-
onDragOver,
|
|
150
|
-
onFileChange,
|
|
151
|
-
pickFile,
|
|
152
|
-
handleImageFile,
|
|
153
|
-
reset,
|
|
154
|
-
setExternalUrl,
|
|
155
|
-
awaitUpload,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { OSearchHighlightPart } from '../types';
|
|
2
|
-
|
|
3
|
-
const ESCAPE_RE = /[.*+?^${}()|[\]\\]/g;
|
|
4
|
-
|
|
5
|
-
export function highlightKeywordParts(text: string, keyword: string): OSearchHighlightPart[] {
|
|
6
|
-
const k = (keyword || '').trim();
|
|
7
|
-
if (!k) return [{ text, match: false }];
|
|
8
|
-
|
|
9
|
-
const escaped = k.replace(ESCAPE_RE, '\\$&');
|
|
10
|
-
const re = new RegExp(escaped, 'gi');
|
|
11
|
-
const parts: OSearchHighlightPart[] = [];
|
|
12
|
-
let lastIndex = 0;
|
|
13
|
-
let m: RegExpExecArray | null;
|
|
14
|
-
|
|
15
|
-
while ((m = re.exec(text)) !== null) {
|
|
16
|
-
if (m.index === re.lastIndex) {
|
|
17
|
-
re.lastIndex++;
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
if (m.index > lastIndex) {
|
|
21
|
-
parts.push({ text: text.slice(lastIndex, m.index), match: false });
|
|
22
|
-
}
|
|
23
|
-
parts.push({ text: m[0], match: true });
|
|
24
|
-
lastIndex = m.index + m[0].length;
|
|
25
|
-
}
|
|
26
|
-
if (lastIndex < text.length) {
|
|
27
|
-
parts.push({ text: text.slice(lastIndex), match: false });
|
|
28
|
-
}
|
|
29
|
-
return parts;
|
|
30
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { ref, watch, onMounted, type Ref } from 'vue';
|
|
2
|
-
|
|
3
|
-
export interface UseSearchHistoryOptions {
|
|
4
|
-
initial: Ref<string[]>;
|
|
5
|
-
storageKey: Ref<string>;
|
|
6
|
-
storeHistory: Ref<boolean>;
|
|
7
|
-
maxHistoryCount: Ref<number>;
|
|
8
|
-
onChange?: (items: string[]) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function useSearchHistory(options: UseSearchHistoryOptions) {
|
|
12
|
-
const { initial, storageKey, storeHistory, maxHistoryCount, onChange } = options;
|
|
13
|
-
const items = ref<string[]>([...initial.value]);
|
|
14
|
-
|
|
15
|
-
const persist = () => {
|
|
16
|
-
if (!storeHistory.value || typeof localStorage === 'undefined') return;
|
|
17
|
-
try {
|
|
18
|
-
if (items.value.length) {
|
|
19
|
-
localStorage.setItem(storageKey.value, JSON.stringify(items.value));
|
|
20
|
-
} else {
|
|
21
|
-
localStorage.removeItem(storageKey.value);
|
|
22
|
-
}
|
|
23
|
-
} catch {
|
|
24
|
-
// ignore
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const setItems = (next: string[]) => {
|
|
29
|
-
items.value = next;
|
|
30
|
-
persist();
|
|
31
|
-
onChange?.(items.value);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const loadFromStorage = () => {
|
|
35
|
-
if (!storeHistory.value || typeof localStorage === 'undefined') return;
|
|
36
|
-
try {
|
|
37
|
-
const raw = localStorage.getItem(storageKey.value);
|
|
38
|
-
if (!raw) return;
|
|
39
|
-
const parsed = JSON.parse(raw);
|
|
40
|
-
if (Array.isArray(parsed) && parsed.length) {
|
|
41
|
-
const merged = Array.from(new Set<string>([...parsed, ...items.value]));
|
|
42
|
-
items.value = merged.slice(0, maxHistoryCount.value);
|
|
43
|
-
onChange?.(items.value);
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
// ignore
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const push = (val: string) => {
|
|
51
|
-
const v = val?.trim();
|
|
52
|
-
if (!v) return;
|
|
53
|
-
const next = [v, ...items.value.filter((i) => i !== v)];
|
|
54
|
-
if (next.length > maxHistoryCount.value) next.length = maxHistoryCount.value;
|
|
55
|
-
setItems(next);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const remove = (val: string) => {
|
|
59
|
-
setItems(items.value.filter((i) => i !== val));
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const clearAll = () => {
|
|
63
|
-
setItems([]);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
watch(initial, (val) => {
|
|
67
|
-
if (val !== items.value) {
|
|
68
|
-
items.value = [...val];
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
onMounted(loadFromStorage);
|
|
73
|
-
|
|
74
|
-
return { items, push, remove, clearAll };
|
|
75
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import _OSearchInput from './OSearchInput.vue';
|
|
2
|
-
import type { App } from 'vue';
|
|
3
|
-
|
|
4
|
-
const OSearchInput = Object.assign(_OSearchInput, {
|
|
5
|
-
install(app: App) {
|
|
6
|
-
app.component('OSearchInput', _OSearchInput);
|
|
7
|
-
},
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
export { OSearchInput };
|
|
11
|
-
|
|
12
|
-
export type {
|
|
13
|
-
OSearchRecommendItem,
|
|
14
|
-
OSearchPayload,
|
|
15
|
-
OSearchImageUploadErrorPayload,
|
|
16
|
-
OSearchUploadImageFn,
|
|
17
|
-
OSearchItemClickType,
|
|
18
|
-
OSearchHighlightPart,
|
|
19
|
-
} from './types';
|
|
20
|
-
|
|
21
|
-
// Public prop type aliases (re-exported from .vue SFCs).
|
|
22
|
-
// Vue SFC type shims don't always re-export named types, so consumers should rely on
|
|
23
|
-
// ComponentProps<typeof Component> if they need props typing.
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
3
|
-
import { highlightKeywordParts } from '../composables/useKeywordHighlight';
|
|
4
|
-
|
|
5
|
-
const props = withDefaults(
|
|
6
|
-
defineProps<{
|
|
7
|
-
text: string;
|
|
8
|
-
keyword: string;
|
|
9
|
-
enabled?: boolean;
|
|
10
|
-
}>(),
|
|
11
|
-
{
|
|
12
|
-
enabled: true,
|
|
13
|
-
}
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
const parts = computed(() => {
|
|
17
|
-
if (!props.enabled || !props.keyword) {
|
|
18
|
-
return [{ text: props.text, match: false }];
|
|
19
|
-
}
|
|
20
|
-
return highlightKeywordParts(props.text, props.keyword);
|
|
21
|
-
});
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<span class="o-search-highlight-text">
|
|
26
|
-
<span v-for="(p, idx) in parts" :key="idx" :class="{ 'is-match': p.match }">{{ p.text }}</span>
|
|
27
|
-
</span>
|
|
28
|
-
</template>
|
|
29
|
-
|
|
30
|
-
<style lang="scss" scoped>
|
|
31
|
-
.o-search-highlight-text {
|
|
32
|
-
.is-match {
|
|
33
|
-
color: var(--o-color-primary1);
|
|
34
|
-
font-weight: 600;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
</style>
|