@smartos-lib/components 1.7.0-beta.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.eslintrc +12 -0
- package/.eslintrc-auto-import.json +332 -0
- package/Components.code-workspace +143 -0
- package/LICENSE +21 -0
- package/dist/smart-docx-editor/index.d.ts +2 -0
- package/dist/smart-docx-editor/index.js +68 -0
- package/dist/smart-file-preview/index.d.ts +18 -0
- package/dist/smart-file-preview/index.js +37 -0
- package/dist/smart-upload/index.d.ts +2 -0
- package/dist/smart-upload/index.js +800 -0
- package/index.html +16 -0
- package/package.json +23 -0
- package/public/favicon.svg +6 -0
- package/scripts/components.vite.config.ts +96 -0
- package/scripts/shared.ts +9 -0
- package/src/App.vue +28 -0
- package/src/components/Logo/index.vue +15 -0
- package/src/components-private/.gitkeep +0 -0
- package/src/composables/useElementStyle.ts +23 -0
- package/src/composables/useNaiveStyle.ts +43 -0
- package/src/composables/useNaiveTheme.ts +71 -0
- package/src/composables/useSmart.ts +36 -0
- package/src/layouts/default.vue +3 -0
- package/src/main.ts +33 -0
- package/src/modules/pinia/index.ts +8 -0
- package/src/modules/progress/index.ts +12 -0
- package/src/modules/router/install.ts +9 -0
- package/src/modules/router/routes.ts +40 -0
- package/src/pages/[...all].vue +21 -0
- package/src/pages/frame/component/[name].vue +14 -0
- package/src/pages/frame/index.vue +81 -0
- package/src/pages/index/composables/useTabsManage.ts +46 -0
- package/src/pages/index/index.vue +111 -0
- package/src/pages/index/type.ts +13 -0
- package/src/pages/index/utils/index.ts +41 -0
- package/src/settings.ts +9 -0
- package/src/shared/components.ts +52 -0
- package/src/shared/env.ts +11 -0
- package/src/shared/unocss.theme.ts +1600 -0
- package/src/stores/theme.ts +29 -0
- package/src/styles/element.scss +3 -0
- package/src/styles/styles.scss +21 -0
- package/src/types.ts +20 -0
- package/src/utils/callCustomElementExposed.ts +6 -0
- package/src/utils/deepCloneESModule.ts +10 -0
- package/src/utils/defineCustomElements.ts +18 -0
- package/src/utils/formatComponentsGlob.ts +16 -0
- package/src/utils/getFileMD5.ts +31 -0
- package/src/utils/getFileNameAndExt.ts +11 -0
- package/src/utils/isFileEqual.ts +13 -0
- package/src/utils/jsonToFormData.ts +8 -0
- package/src/web-components/smart-docx-drive-page/App.vue +37 -0
- package/src/web-components/smart-docx-drive-page/apis/doc.ts +85 -0
- package/src/web-components/smart-docx-drive-page/apis/file.ts +278 -0
- package/src/web-components/smart-docx-drive-page/apis/folder.ts +72 -0
- package/src/web-components/smart-docx-drive-page/children/Home.vue +8 -0
- package/src/web-components/smart-docx-drive-page/children/Me.vue +47 -0
- package/src/web-components/smart-docx-drive-page/components/CustomImage.vue +26 -0
- package/src/web-components/smart-docx-drive-page/components/CustomPopover.vue +62 -0
- package/src/web-components/smart-docx-drive-page/components/DocxDir.vue +99 -0
- package/src/web-components/smart-docx-drive-page/components/DocxDoc.vue +132 -0
- package/src/web-components/smart-docx-drive-page/components/DocxDownloadPopoverItem.vue +41 -0
- package/src/web-components/smart-docx-drive-page/components/DocxFileList.vue +156 -0
- package/src/web-components/smart-docx-drive-page/components/DocxPreview.vue +33 -0
- package/src/web-components/smart-docx-drive-page/components/DocxUpload.vue +164 -0
- package/src/web-components/smart-docx-drive-page/components/FileIcon.vue +62 -0
- package/src/web-components/smart-docx-drive-page/components-private/Header.vue +65 -0
- package/src/web-components/smart-docx-drive-page/components-private/Logo.vue +15 -0
- package/src/web-components/smart-docx-drive-page/components-private/Menu.vue +34 -0
- package/src/web-components/smart-docx-drive-page/components-private/Navbar.vue +36 -0
- package/src/web-components/smart-docx-drive-page/composables/useFullscreenElDialog.ts +41 -0
- package/src/web-components/smart-docx-drive-page/composables/usePrompt.ts +73 -0
- package/src/web-components/smart-docx-drive-page/data.ts +10 -0
- package/src/web-components/smart-docx-drive-page/external-style/custom-popover.sass +8 -0
- package/src/web-components/smart-docx-drive-page/external-style/index.sass +1 -0
- package/src/web-components/smart-docx-drive-page/index.ts +20 -0
- package/src/web-components/smart-docx-drive-page/index.vue +39 -0
- package/src/web-components/smart-docx-drive-page/info.ts +2 -0
- package/src/web-components/smart-docx-drive-page/stores/menu.ts +60 -0
- package/src/web-components/smart-docx-drive-page/types.ts +51 -0
- package/src/web-components/smart-docx-drive-page/utils/file-actions.ts +63 -0
- package/src/web-components/smart-docx-drive-page/utils/file.ts +31 -0
- package/src/web-components/smart-docx-editor/App.vue +32 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/Markdown.vue +202 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/Menu.vue +100 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/types.ts +6 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/Markdown.tsx +71 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/MarkdownElement.tsx +81 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Blockquote/index.sass +6 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Blockquote/index.tsx +12 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Heading/index.sass +14 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Heading/index.tsx +17 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/List/index.scss +16 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/List/index.tsx +39 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/types/custom-types.d.ts +69 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/composables/useTextSelection.ts +50 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/index.sass +19 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/index.vue +21 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/shared/const.ts +23 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/utils/slateHelpers.ts +23 -0
- package/src/web-components/smart-docx-editor/data.ts +38 -0
- package/src/web-components/smart-docx-editor/demo.vue +11 -0
- package/src/web-components/smart-docx-editor/index.md +3 -0
- package/src/web-components/smart-docx-editor/index.ts +5 -0
- package/src/web-components/smart-docx-editor/index.vue +12 -0
- package/src/web-components/smart-docx-editor/info.ts +2 -0
- package/src/web-components/smart-file-preview/category/Code.vue +171 -0
- package/src/web-components/smart-file-preview/category/Image.vue +49 -0
- package/src/web-components/smart-file-preview/category/Pdf.vue +14 -0
- package/src/web-components/smart-file-preview/category/Video.vue +27 -0
- package/src/web-components/smart-file-preview/demo.vue +34 -0
- package/src/web-components/smart-file-preview/index.md +5 -0
- package/src/web-components/smart-file-preview/index.ts +29 -0
- package/src/web-components/smart-file-preview/index.vue +56 -0
- package/src/web-components/smart-file-preview/info.ts +2 -0
- package/src/web-components/smart-file-preview/shared/const.ts +4 -0
- package/src/web-components/smart-file-preview/types.ts +38 -0
- package/src/web-components/smart-upload/index.ts +5 -0
- package/src/web-components/smart-upload/index.vue +101 -0
- package/src/web-components/smart-upload/info.ts +2 -0
- package/src/web-components/smart-upload/types.ts +28 -0
- package/tsconfig.json +15 -0
- package/types/auto-imports.d.ts +975 -0
- package/types/components.d.ts +14 -0
- package/types/env.d.ts +8 -0
- package/types/shims.d.ts +6 -0
- package/unocss.config.ts +23 -0
- package/vite.config.ts +60 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
import type { RenderElementProps } from 'slate-react';
|
2
|
+
import type { HeadingElement } from '../../types/custom-types';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* 标题
|
6
|
+
*/
|
7
|
+
export function Heading(props: RenderElementProps) {
|
8
|
+
const element = props.element as HeadingElement;
|
9
|
+
|
10
|
+
const Tag = `docx-h${element.level}`;
|
11
|
+
|
12
|
+
return (
|
13
|
+
<Tag {...props.attributes}>
|
14
|
+
{props.children}
|
15
|
+
</Tag>
|
16
|
+
);
|
17
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
docx-ul, docx-ol, docx-li {
|
2
|
+
@apply block;
|
3
|
+
}
|
4
|
+
|
5
|
+
docx-li {
|
6
|
+
@apply list-none;
|
7
|
+
|
8
|
+
&:not(.ordered) {
|
9
|
+
@apply relative lh-6 pl-6;
|
10
|
+
@apply before-(
|
11
|
+
content-[''] size-6 i-ci-dot-01-xs pointer-events-none
|
12
|
+
flex justify-left items-center
|
13
|
+
absolute top-0 left-0 -translate-x-1/4
|
14
|
+
)
|
15
|
+
}
|
16
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import type { RenderElementProps } from 'slate-react';
|
2
|
+
import { createContext } from 'react';
|
3
|
+
import type { ListElement } from '../../types/custom-types';
|
4
|
+
|
5
|
+
const { Provider, Consumer } = createContext(false);
|
6
|
+
|
7
|
+
/**
|
8
|
+
* 列表
|
9
|
+
*/
|
10
|
+
export function List(props: RenderElementProps) {
|
11
|
+
const element = props.element as ListElement;
|
12
|
+
|
13
|
+
const Tag = `docx-${element.ordered ? 'o' : 'u'}l`;
|
14
|
+
|
15
|
+
return (
|
16
|
+
<Provider value={!!element.ordered}>
|
17
|
+
<Tag {...props.attributes}>
|
18
|
+
{props.children}
|
19
|
+
</Tag>
|
20
|
+
</Provider>
|
21
|
+
);
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* 列表项
|
26
|
+
*/
|
27
|
+
export function ListItem(props: RenderElementProps) {
|
28
|
+
return (
|
29
|
+
<Consumer>
|
30
|
+
{(ordered) => {
|
31
|
+
return (
|
32
|
+
<docx-li className={ordered ? 'ordered' : ''}>
|
33
|
+
{props.children}
|
34
|
+
</docx-li>
|
35
|
+
);
|
36
|
+
}}
|
37
|
+
</Consumer>
|
38
|
+
);
|
39
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import { BaseEditor, BaseRange, Descendant } from "slate"
|
2
|
+
import { ReactEditor } from "slate-react"
|
3
|
+
import { HistoryEditor } from 'slate-history'
|
4
|
+
|
5
|
+
/** 段落节点 */
|
6
|
+
export type ParagraphElement = {
|
7
|
+
type: 'paragraph'
|
8
|
+
align?: string
|
9
|
+
children: Descendant[]
|
10
|
+
}
|
11
|
+
|
12
|
+
/** 标题节点 - H1 ~ H6 */
|
13
|
+
export type HeadingElement = {
|
14
|
+
type: 'heading'
|
15
|
+
level: string
|
16
|
+
children: Descendant[]
|
17
|
+
}
|
18
|
+
|
19
|
+
/** 引用节点 */
|
20
|
+
export type BlockquoteElement = {
|
21
|
+
type: 'blockquote'
|
22
|
+
children: Descendant[]
|
23
|
+
}
|
24
|
+
|
25
|
+
/** 列表节点 */
|
26
|
+
export type ListElement = {
|
27
|
+
type: 'list'
|
28
|
+
/** 是否为有序列表 */
|
29
|
+
ordered?: boolean
|
30
|
+
children: Descendant[]
|
31
|
+
}
|
32
|
+
/** 列表项节点 */
|
33
|
+
export type ListItemElement = {
|
34
|
+
type: 'list-item'
|
35
|
+
children: Descendant[]
|
36
|
+
}
|
37
|
+
|
38
|
+
/** 所有自定义节点 */
|
39
|
+
type CustomElement = ParagraphElement |
|
40
|
+
HeadingElement |
|
41
|
+
BlockquoteElement |
|
42
|
+
ListElement |
|
43
|
+
ListItemElement;
|
44
|
+
|
45
|
+
/** 文本节点 */
|
46
|
+
type CustomText = {
|
47
|
+
bold?: boolean
|
48
|
+
italic?: boolean
|
49
|
+
del?: boolean
|
50
|
+
underline?: boolean
|
51
|
+
code?: boolean
|
52
|
+
text: string
|
53
|
+
}
|
54
|
+
|
55
|
+
/** 扩展的编辑器类型 */
|
56
|
+
type CustomEditor = BaseEditor & ReactEditor & HistoryEditor & {
|
57
|
+
nodeToDecorations?: Map<Element, Range[]>
|
58
|
+
}
|
59
|
+
|
60
|
+
declare module 'slate' {
|
61
|
+
interface CustomTypes {
|
62
|
+
Editor: CustomEditor
|
63
|
+
Element: CustomElement
|
64
|
+
Text: CustomText
|
65
|
+
Range: BaseRange & {
|
66
|
+
[key: string]: unknown
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
package/src/web-components/smart-docx-editor/MarkdownShortcuts/composables/useTextSelection.ts
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
import { shadowRoot } from '../shared/const';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* 获取文档选区
|
5
|
+
*/
|
6
|
+
export const useTextSelection = createSharedComposable(() => {
|
7
|
+
const selection = ref<Selection | null>(null);
|
8
|
+
const text = computed(() => selection.value?.toString() ?? '');
|
9
|
+
|
10
|
+
const isValidSelection = computed(() => text.value && selection.value!.rangeCount > 0);
|
11
|
+
|
12
|
+
const range = computed(() => {
|
13
|
+
if (isValidSelection.value) {
|
14
|
+
const range = selection.value!.getRangeAt(0).cloneRange();
|
15
|
+
|
16
|
+
// 使选区在鼠标终点处折叠
|
17
|
+
range.collapse(range.startContainer === selection.value!.focusNode);
|
18
|
+
|
19
|
+
// 修复选区在文本开头时,无法获取到正确的位置的问题
|
20
|
+
if (range.startOffset === 0) {
|
21
|
+
const newRange = document.createRange();
|
22
|
+
newRange.setStart(range.startContainer, 0);
|
23
|
+
newRange.setEnd(range.startContainer, 1);
|
24
|
+
return newRange;
|
25
|
+
}
|
26
|
+
|
27
|
+
return range;
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
const onValidSelectionUpdate = createEventHook<Selection>();
|
32
|
+
|
33
|
+
wheneverEffectScopeImmediate(shadowRoot, () => {
|
34
|
+
useEventListener(window.document, 'selectionchange', () => {
|
35
|
+
selection.value = null;
|
36
|
+
selection.value = shadowRoot.value.getSelection();
|
37
|
+
|
38
|
+
if (isValidSelection.value)
|
39
|
+
onValidSelectionUpdate.trigger(selection.value!);
|
40
|
+
});
|
41
|
+
});
|
42
|
+
|
43
|
+
return {
|
44
|
+
text,
|
45
|
+
isValidSelection,
|
46
|
+
range,
|
47
|
+
selection,
|
48
|
+
onValidSelectionUpdate: onValidSelectionUpdate.on,
|
49
|
+
};
|
50
|
+
});
|
@@ -0,0 +1,19 @@
|
|
1
|
+
/* Markdown 样式 */
|
2
|
+
|
3
|
+
// 代码转换层
|
4
|
+
[data-use-vue-component-wrap], [__use_react_component_wrap]
|
5
|
+
@apply contents
|
6
|
+
|
7
|
+
// 根元素
|
8
|
+
.smart-docx-editor
|
9
|
+
@apply root size-full outline-none
|
10
|
+
|
11
|
+
> :first-child
|
12
|
+
@apply important:mt-0
|
13
|
+
|
14
|
+
// 标题
|
15
|
+
@import "./components-react/elements/Heading/index.sass"
|
16
|
+
// 引用
|
17
|
+
@import "./components-react/elements/Blockquote/index.sass"
|
18
|
+
// 列表 | 列表项
|
19
|
+
@import "./components-react/elements/List/index.scss"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<!-- Markdown 功能组装层 / 样式层 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<!-- Markdown -->
|
5
|
+
<Markdown v-bind="props" />
|
6
|
+
|
7
|
+
<!-- 菜单栏 -->
|
8
|
+
<Menu />
|
9
|
+
</template>
|
10
|
+
|
11
|
+
<script lang="ts" setup>
|
12
|
+
import Menu from './components/Menu.vue';
|
13
|
+
import Markdown from './components/Markdown.vue';
|
14
|
+
import type { MarkdownProps } from './components/types';
|
15
|
+
|
16
|
+
const props = defineProps<Pick<MarkdownProps, 'initialValue'>>();
|
17
|
+
</script>
|
18
|
+
|
19
|
+
<style lang="sass">
|
20
|
+
@import url("./index.sass")
|
21
|
+
</style>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import type { BaseSelection, Descendant, Editor } from 'slate';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* 编辑器实例
|
5
|
+
*/
|
6
|
+
export const editor = ref() as Ref<Editor>;
|
7
|
+
|
8
|
+
/**
|
9
|
+
* 编辑器 ShadowRoot
|
10
|
+
*/
|
11
|
+
export const shadowRoot = ref() as Ref<ShadowRoot & {
|
12
|
+
getSelection: Window['getSelection']
|
13
|
+
}>;
|
14
|
+
|
15
|
+
/**
|
16
|
+
* 选区变化的事件回调
|
17
|
+
*/
|
18
|
+
export const selectionChange = createEventHook<BaseSelection>();
|
19
|
+
|
20
|
+
/**
|
21
|
+
* 值变化的事件回调
|
22
|
+
*/
|
23
|
+
export const valueChange = createEventHook<Descendant[]>();
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Editor } from 'slate';
|
2
|
+
import { editor } from '../shared/const';
|
3
|
+
import type { CustomText } from '../components-react/types/custom-types';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* 判断当前选区是否是某个格式
|
7
|
+
*/
|
8
|
+
export function isMarkActive(format: keyof Omit<CustomText, 'text'>) {
|
9
|
+
const marks = Editor.marks(editor.value);
|
10
|
+
return marks ? marks[format] === true : false;
|
11
|
+
}
|
12
|
+
|
13
|
+
/**
|
14
|
+
* 切换选区的格式
|
15
|
+
*/
|
16
|
+
export function toggleMark(format: keyof Omit<CustomText, 'text'>) {
|
17
|
+
const isActive = isMarkActive(format);
|
18
|
+
|
19
|
+
if (isActive)
|
20
|
+
Editor.removeMark(editor.value, format);
|
21
|
+
else
|
22
|
+
Editor.addMark(editor.value, format, true);
|
23
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
export const 所有支持的节点 = {
|
2
|
+
initialValue: [
|
3
|
+
{
|
4
|
+
type: 'paragraph',
|
5
|
+
children: [
|
6
|
+
{ text: '段落, 可以是' },
|
7
|
+
{ text: '粗体, ', bold: true },
|
8
|
+
{ text: '也可以' },
|
9
|
+
{ text: '是斜体', italic: true },
|
10
|
+
{ text: '和包含' },
|
11
|
+
{ text: '删除线', del: true },
|
12
|
+
{ text: '及' },
|
13
|
+
{ text: '下划线', underline: true },
|
14
|
+
{ text: '的文本' },
|
15
|
+
],
|
16
|
+
},
|
17
|
+
{ type: 'heading', level: '1', children: [{ text: 'H1 标题' }] },
|
18
|
+
{ type: 'heading', level: '2', children: [{ text: 'H2 标题' }] },
|
19
|
+
{ type: 'heading', level: '3', children: [{ text: 'H3 标题' }] },
|
20
|
+
{ type: 'heading', level: '4', children: [{ text: 'H4 标题' }] },
|
21
|
+
{ type: 'heading', level: '5', children: [{ text: 'H5 标题' }] },
|
22
|
+
{ type: 'heading', level: '6', children: [{ text: 'H6 标题' }] },
|
23
|
+
{
|
24
|
+
type: 'blockquote',
|
25
|
+
children: [
|
26
|
+
{ type: 'paragraph', children: [{ text: '引用' }] },
|
27
|
+
{ type: 'paragraph', children: [{ text: '节点' }] },
|
28
|
+
],
|
29
|
+
},
|
30
|
+
{
|
31
|
+
type: 'list',
|
32
|
+
children: [
|
33
|
+
{ type: 'list-item', children: [{ text: '无序' }] },
|
34
|
+
{ type: 'list-item', children: [{ text: '列表' }] },
|
35
|
+
],
|
36
|
+
},
|
37
|
+
],
|
38
|
+
};
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<template>
|
2
|
+
<smart-docx-editor
|
3
|
+
:initialValue.prop="所有支持的节点.initialValue"
|
4
|
+
/>
|
5
|
+
</template>
|
6
|
+
|
7
|
+
<script lang="ts" setup>
|
8
|
+
import '@smartos-lib/core/dist/componentsProvider';
|
9
|
+
import '@@/dist/smart-docx-editor/index';
|
10
|
+
import { 所有支持的节点 } from './data';
|
11
|
+
</script>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<!-- Markdown 预留层 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<MarkdownShortcuts v-bind="props" />
|
5
|
+
</template>
|
6
|
+
|
7
|
+
<script lang="ts" setup>
|
8
|
+
import type { MarkdownProps } from './MarkdownShortcuts/components/types';
|
9
|
+
import MarkdownShortcuts from './MarkdownShortcuts/index.vue';
|
10
|
+
|
11
|
+
const props = defineProps<Pick<MarkdownProps, 'initialValue'>>();
|
12
|
+
</script>
|
@@ -0,0 +1,171 @@
|
|
1
|
+
<!-- 代码 / 文件预览 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<div size-full flex="~ justify-center items-center" pos="absolute top-0 left-0" c-neutral bg-dark>
|
5
|
+
<!-- 加载中 -->
|
6
|
+
<div v-if="isFileReading || isEditorLoading" flex="~ col gap-2 items-center">
|
7
|
+
<i-svg-spinners-blocks-shuffle-3 />
|
8
|
+
<div v-if="isFileReading" text-sm>文件读取中, 当前进度 {{ fileReadPercent }}%</div>
|
9
|
+
</div>
|
10
|
+
<!-- 不支持的文件 -->
|
11
|
+
<div v-else-if="isUnsupportedLanguage" flex="~ col justify-center items-center gap-3" c-gray>
|
12
|
+
<i-codicon-warning class="text-(5xl yellow)" />
|
13
|
+
此文件类型暂不支持预览
|
14
|
+
<el-button class="rounded-sm" color="#3177d3" @click="initEditor()">仍然打开</el-button>
|
15
|
+
</div>
|
16
|
+
<!-- 编辑器 -->
|
17
|
+
<div v-else ref="editorRef" size-full />
|
18
|
+
</div>
|
19
|
+
</template>
|
20
|
+
|
21
|
+
<script lang="ts" setup>
|
22
|
+
import type * as Monaco from 'monaco-editor';
|
23
|
+
import loader from '@monaco-editor/loader';
|
24
|
+
import { getFastestCDN } from '@mixte/snippets/getFastestCDN';
|
25
|
+
import { shadowRoot } from '../shared/const';
|
26
|
+
import { devDependencies } from '../../../../../../package.json';
|
27
|
+
|
28
|
+
const props = defineProps<{ file: Blob; fileExt: string }>();
|
29
|
+
|
30
|
+
const editorRef = ref();
|
31
|
+
const { width, height } = useElementSize(editorRef);
|
32
|
+
|
33
|
+
const isFileReading = ref(false);
|
34
|
+
const isEditorLoading = ref(false);
|
35
|
+
const isUnsupportedLanguage = ref(false);
|
36
|
+
|
37
|
+
const monaco = shallowRef<typeof Monaco>();
|
38
|
+
const editor = shallowRef<Monaco.editor.IStandaloneCodeEditor>();
|
39
|
+
const languages = shallowRef<Monaco.languages.ILanguageExtensionPoint[]>();
|
40
|
+
const language = ref<string>();
|
41
|
+
const value = ref<string>();
|
42
|
+
const fileReadPercent = ref(0);
|
43
|
+
|
44
|
+
const fastestCDN = useRequestReactive(
|
45
|
+
() => getFastestCDN('monaco-editor', {
|
46
|
+
version: devDependencies['monaco-editor'].replace(/^\^/g, ''),
|
47
|
+
file: `/min/vs/basic-languages/css/css.js`,
|
48
|
+
}),
|
49
|
+
{ immediate: true },
|
50
|
+
);
|
51
|
+
|
52
|
+
/**
|
53
|
+
* 创建 monaco 编辑器
|
54
|
+
*/
|
55
|
+
async function createEditor() {
|
56
|
+
editor.value?.dispose();
|
57
|
+
isEditorLoading.value = true;
|
58
|
+
isUnsupportedLanguage.value = false;
|
59
|
+
|
60
|
+
await until(() => fastestCDN.isSuccess).toBeTruthy();
|
61
|
+
|
62
|
+
loader.config({
|
63
|
+
"vs/nls": {
|
64
|
+
availableLanguages: {
|
65
|
+
'*': 'zh-cn',
|
66
|
+
},
|
67
|
+
},
|
68
|
+
"paths": {
|
69
|
+
vs: `${fastestCDN.response}/min/vs`,
|
70
|
+
},
|
71
|
+
});
|
72
|
+
|
73
|
+
try {
|
74
|
+
monaco.value = await loader.init();
|
75
|
+
languages.value ??= monaco.value.languages.getLanguages();
|
76
|
+
language.value = languages.value.find(lang => lang.extensions?.includes(`.${props.fileExt}`))?.id;
|
77
|
+
isEditorLoading.value = false;
|
78
|
+
|
79
|
+
if (!(isUnsupportedLanguage.value = !language.value))
|
80
|
+
initEditor();
|
81
|
+
}
|
82
|
+
catch (error) {
|
83
|
+
isEditorLoading.value = false;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* 初始化 monaco 编辑器
|
89
|
+
*/
|
90
|
+
async function initEditor() {
|
91
|
+
isUnsupportedLanguage.value = false;
|
92
|
+
await nextTick();
|
93
|
+
|
94
|
+
editor.value = monaco.value!.editor.create(editorRef.value!, {
|
95
|
+
// 值
|
96
|
+
value: value.value,
|
97
|
+
// 编辑器语言
|
98
|
+
language: language.value || 'plaintext',
|
99
|
+
// 一个制表符等于的空格数
|
100
|
+
tabSize: 2,
|
101
|
+
// 在通过鼠标添加多个光标时使用的修改键
|
102
|
+
multiCursorModifier: 'ctrlCmd',
|
103
|
+
// 代码缩略地图
|
104
|
+
minimap: {
|
105
|
+
showSlider: 'always',
|
106
|
+
},
|
107
|
+
// 代码编辑器边距
|
108
|
+
padding: {
|
109
|
+
top: 10,
|
110
|
+
},
|
111
|
+
// 自动换行
|
112
|
+
wordWrap: 'on',
|
113
|
+
// 主题
|
114
|
+
theme: 'vs-dark',
|
115
|
+
// 只读
|
116
|
+
readOnly: true,
|
117
|
+
});
|
118
|
+
}
|
119
|
+
|
120
|
+
// 读取文件内容
|
121
|
+
wheneverEffectScopeImmediate(() => props.file, (file, _, onCleanup) => {
|
122
|
+
isFileReading.value = true;
|
123
|
+
|
124
|
+
const reader = new FileReader();
|
125
|
+
|
126
|
+
reader.onprogress = (event) => {
|
127
|
+
fileReadPercent.value = Math.round((event.loaded / event.total) * 100);
|
128
|
+
};
|
129
|
+
reader.onload = async (event) => {
|
130
|
+
isFileReading.value = false;
|
131
|
+
value.value = event.target?.result as string;
|
132
|
+
createEditor();
|
133
|
+
};
|
134
|
+
|
135
|
+
reader.readAsText(file);
|
136
|
+
|
137
|
+
onCleanup(() => {
|
138
|
+
reader.abort();
|
139
|
+
});
|
140
|
+
});
|
141
|
+
|
142
|
+
// 挂载 monaco 编辑器样式至 web components 组件中
|
143
|
+
wheneverEffectScopeImmediate(shadowRoot, (shadowRoot) => {
|
144
|
+
const selectors = `[data-name^="vs/editor"]`;
|
145
|
+
|
146
|
+
function insertStyle() {
|
147
|
+
shadowRoot!.querySelectorAll(selectors).forEach((link) => {
|
148
|
+
shadowRoot!.removeChild(link);
|
149
|
+
});
|
150
|
+
|
151
|
+
Array.from(document.querySelectorAll(selectors)).forEach((link) => {
|
152
|
+
shadowRoot.appendChild(link.cloneNode(true));
|
153
|
+
});
|
154
|
+
}
|
155
|
+
|
156
|
+
useMutationObserver(document.head, insertStyle, {
|
157
|
+
childList: true,
|
158
|
+
});
|
159
|
+
|
160
|
+
insertStyle();
|
161
|
+
});
|
162
|
+
|
163
|
+
// 动态调整编辑器大小
|
164
|
+
watchDeep(() => ({ width: width.value, height: height.value }), (size) => {
|
165
|
+
editor.value?.layout(size);
|
166
|
+
});
|
167
|
+
|
168
|
+
onUnmounted(() => {
|
169
|
+
editor.value?.dispose();
|
170
|
+
});
|
171
|
+
</script>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<!-- 图片预览 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<div size-full>
|
5
|
+
<div ref="rootRef" size-full>
|
6
|
+
<img v-if="url" :src="url" hidden>
|
7
|
+
</div>
|
8
|
+
</div>
|
9
|
+
</template>
|
10
|
+
|
11
|
+
<script lang="ts" setup>
|
12
|
+
import Viewer from 'viewerjs';
|
13
|
+
|
14
|
+
const props = defineProps<{ file: Blob }>();
|
15
|
+
|
16
|
+
const rootRef = ref();
|
17
|
+
|
18
|
+
const url = useObjectUrl(() => props.file);
|
19
|
+
|
20
|
+
wheneverEffectScopeImmediate(() => rootRef.value && url.value, (_, __, onCleanup) => {
|
21
|
+
const viewer = new Viewer(rootRef.value, {
|
22
|
+
inline: true,
|
23
|
+
navbar: false,
|
24
|
+
title: false,
|
25
|
+
transition: false,
|
26
|
+
toolbar: {
|
27
|
+
reset: true,
|
28
|
+
rotateLeft: true,
|
29
|
+
rotateRight: true,
|
30
|
+
zoomIn: true,
|
31
|
+
zoomOut: true,
|
32
|
+
flipHorizontal: true,
|
33
|
+
flipVertical: true,
|
34
|
+
},
|
35
|
+
});
|
36
|
+
|
37
|
+
onCleanup(() => {
|
38
|
+
viewer.destroy();
|
39
|
+
});
|
40
|
+
});
|
41
|
+
</script>
|
42
|
+
|
43
|
+
<style lang="scss">
|
44
|
+
@import "viewerjs/dist/viewer.css";
|
45
|
+
|
46
|
+
.viewer-button.viewer-fullscreen {
|
47
|
+
display: none;
|
48
|
+
}
|
49
|
+
</style>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<!-- 视频预览 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<video
|
5
|
+
ref="videoRef"
|
6
|
+
class="size-full absolute top-0 left-0"
|
7
|
+
controls
|
8
|
+
/>
|
9
|
+
</template>
|
10
|
+
|
11
|
+
<script lang="ts" setup>
|
12
|
+
const props = defineProps<{ file: Blob }>();
|
13
|
+
|
14
|
+
const videoRef = ref();
|
15
|
+
|
16
|
+
const url = useObjectUrl(() => props.file);
|
17
|
+
|
18
|
+
wheneverEffectScopeImmediate(url, () => {
|
19
|
+
const { playing } = useMediaControls(videoRef, {
|
20
|
+
src: () => url.value!,
|
21
|
+
});
|
22
|
+
|
23
|
+
nextTick(() => {
|
24
|
+
playing.value = true;
|
25
|
+
});
|
26
|
+
});
|
27
|
+
</script>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<template>
|
2
|
+
<div mb2>
|
3
|
+
<el-button @click="open()">点击选择文件进行预览</el-button>
|
4
|
+
</div>
|
5
|
+
|
6
|
+
<div flex="~ justify-center items-center gap2" text="xs teal-5">
|
7
|
+
<i-ic-twotone-keyboard-arrow-down class="size-6" />
|
8
|
+
文件预览组件
|
9
|
+
<i-ic-twotone-keyboard-arrow-down class="size-6" />
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div b="1 solid neutral-2 dark:neutral-6" aspect-ratio-video>
|
13
|
+
<smart-file-preview ref="smartFilePreviewRef" v-bind="attrs" />
|
14
|
+
</div>
|
15
|
+
</template>
|
16
|
+
|
17
|
+
<script lang="ts" setup>
|
18
|
+
import type { SmartFilePreviewElement } from '@@/dist/smart-file-preview/index';
|
19
|
+
import '@smartos-lib/core/dist/componentsProvider';
|
20
|
+
import '@@/dist/smart-file-preview/index';
|
21
|
+
|
22
|
+
const attrs = useAttrs();
|
23
|
+
|
24
|
+
const smartFilePreviewRef = ref<SmartFilePreviewElement>();
|
25
|
+
|
26
|
+
const { files, open } = useFileDialog({
|
27
|
+
multiple: false,
|
28
|
+
});
|
29
|
+
|
30
|
+
watch(() => files.value?.[0], (file) => {
|
31
|
+
if (file)
|
32
|
+
smartFilePreviewRef.value!.previewFile(file.name, file);
|
33
|
+
});
|
34
|
+
</script>
|