niuma-ui 1.0.0
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/CHANGELOG.md +29 -0
- package/LICENSE +202 -0
- package/NOTICE +25 -0
- package/README.en.md +99 -0
- package/README.md +161 -0
- package/package.json +106 -0
- package/src/__tests__/RsBadge.spec.ts +40 -0
- package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
- package/src/__tests__/RsButton.spec.ts +108 -0
- package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
- package/src/__tests__/RsCard.spec.ts +97 -0
- package/src/__tests__/RsCheckbox.spec.ts +36 -0
- package/src/__tests__/RsCodeEditor.spec.ts +111 -0
- package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
- package/src/__tests__/RsContainer.spec.ts +89 -0
- package/src/__tests__/RsContextMenu.spec.ts +118 -0
- package/src/__tests__/RsDatePicker.spec.ts +88 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
- package/src/__tests__/RsDialog.spec.ts +404 -0
- package/src/__tests__/RsDrawer.spec.ts +145 -0
- package/src/__tests__/RsDropdown.spec.ts +135 -0
- package/src/__tests__/RsEmpty.spec.ts +99 -0
- package/src/__tests__/RsForm.spec.ts +173 -0
- package/src/__tests__/RsIcon.spec.ts +244 -0
- package/src/__tests__/RsInput.spec.ts +269 -0
- package/src/__tests__/RsInputNumber.spec.ts +118 -0
- package/src/__tests__/RsLabel.spec.ts +106 -0
- package/src/__tests__/RsLink.spec.ts +49 -0
- package/src/__tests__/RsLoading.spec.ts +84 -0
- package/src/__tests__/RsMarkdown.spec.ts +143 -0
- package/src/__tests__/RsMenu.spec.ts +119 -0
- package/src/__tests__/RsNewComponents.spec.ts +17 -0
- package/src/__tests__/RsPagination.spec.ts +158 -0
- package/src/__tests__/RsPopover.spec.ts +126 -0
- package/src/__tests__/RsScrollbar.spec.ts +53 -0
- package/src/__tests__/RsSelect.spec.ts +291 -0
- package/src/__tests__/RsSidebar.spec.ts +119 -0
- package/src/__tests__/RsSplitPane.spec.ts +177 -0
- package/src/__tests__/RsStatCard.spec.ts +79 -0
- package/src/__tests__/RsSteps.spec.ts +125 -0
- package/src/__tests__/RsTable.perf.spec.ts +103 -0
- package/src/__tests__/RsTable.spec.ts +1135 -0
- package/src/__tests__/RsTabs.spec.ts +328 -0
- package/src/__tests__/RsTerminal.spec.ts +218 -0
- package/src/__tests__/RsTimePicker.spec.ts +77 -0
- package/src/__tests__/RsToaster.spec.ts +95 -0
- package/src/__tests__/RsToolbar.spec.ts +75 -0
- package/src/__tests__/RsTooltip.spec.ts +111 -0
- package/src/__tests__/RsTree.spec.ts +572 -0
- package/src/__tests__/RsUpload.spec.ts +160 -0
- package/src/__tests__/RsVirtualList.spec.ts +77 -0
- package/src/__tests__/date-picker-utils.spec.ts +70 -0
- package/src/__tests__/dialog-viewport.spec.ts +38 -0
- package/src/__tests__/iso-local-datetime.spec.ts +58 -0
- package/src/__tests__/rs-clipboard.spec.ts +142 -0
- package/src/__tests__/setup.ts +12 -0
- package/src/__tests__/split-pane-utils.spec.ts +136 -0
- package/src/__tests__/table-drag.spec.ts +35 -0
- package/src/__tests__/table-edit-utils.spec.ts +194 -0
- package/src/__tests__/table-utils.spec.ts +286 -0
- package/src/__tests__/theme.spec.ts +22 -0
- package/src/__tests__/tree-utils.spec.ts +234 -0
- package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
- package/src/components/RsAvatar.vue +115 -0
- package/src/components/RsBadge.vue +59 -0
- package/src/components/RsBreadcrumb.vue +65 -0
- package/src/components/RsBreadcrumbItems.vue +36 -0
- package/src/components/RsButton.vue +393 -0
- package/src/components/RsCalendarGrid.vue +284 -0
- package/src/components/RsCard.vue +237 -0
- package/src/components/RsCheckbox.vue +189 -0
- package/src/components/RsCodeBlock.vue +278 -0
- package/src/components/RsCodeEditor.vue +597 -0
- package/src/components/RsConfigProvider.vue +92 -0
- package/src/components/RsConfirmDialog.vue +178 -0
- package/src/components/RsContainer.vue +235 -0
- package/src/components/RsContextMenu.vue +238 -0
- package/src/components/RsContextMenuItems.vue +77 -0
- package/src/components/RsDatePicker.vue +756 -0
- package/src/components/RsDateTimePicker.vue +42 -0
- package/src/components/RsDialog.vue +486 -0
- package/src/components/RsDrawer.vue +175 -0
- package/src/components/RsDropdown.vue +252 -0
- package/src/components/RsDropdownItems.vue +90 -0
- package/src/components/RsEmpty.vue +107 -0
- package/src/components/RsForm.vue +131 -0
- package/src/components/RsIcon.vue +104 -0
- package/src/components/RsInput.vue +1101 -0
- package/src/components/RsInputNumber.vue +532 -0
- package/src/components/RsLabel.vue +54 -0
- package/src/components/RsLink.vue +106 -0
- package/src/components/RsLoading.vue +171 -0
- package/src/components/RsMarkdown.vue +329 -0
- package/src/components/RsMenu.vue +256 -0
- package/src/components/RsMenuItems.vue +125 -0
- package/src/components/RsMonacoEditor.vue +541 -0
- package/src/components/RsPagination.vue +184 -0
- package/src/components/RsPopover.vue +77 -0
- package/src/components/RsProseEditor.vue +160 -0
- package/src/components/RsScrollbar.vue +171 -0
- package/src/components/RsSelect.vue +1045 -0
- package/src/components/RsSidebar.vue +121 -0
- package/src/components/RsSidebarGroup.vue +32 -0
- package/src/components/RsSidebarItem.vue +72 -0
- package/src/components/RsSplitPane.vue +619 -0
- package/src/components/RsStatCard.vue +85 -0
- package/src/components/RsSteps.vue +138 -0
- package/src/components/RsTable.vue +2664 -0
- package/src/components/RsTableColgroup.vue +17 -0
- package/src/components/RsTableHeaderRow.vue +30 -0
- package/src/components/RsTabs.vue +951 -0
- package/src/components/RsTerminal.vue +697 -0
- package/src/components/RsTimePicker.vue +554 -0
- package/src/components/RsTimePickerColumns.vue +226 -0
- package/src/components/RsToaster.vue +396 -0
- package/src/components/RsToolbar.vue +135 -0
- package/src/components/RsTooltip.vue +142 -0
- package/src/components/RsTooltipProvider.vue +20 -0
- package/src/components/RsTree.vue +1248 -0
- package/src/components/RsUpload.vue +279 -0
- package/src/components/RsVirtualList.vue +217 -0
- package/src/components/avatar-utils.ts +19 -0
- package/src/components/breadcrumb-utils.ts +19 -0
- package/src/components/class-utils.ts +29 -0
- package/src/components/code-editor-utils.ts +106 -0
- package/src/components/code-mirror-completion.ts +40 -0
- package/src/components/code-mirror-diagnostics.ts +52 -0
- package/src/components/code-mirror-ghost.ts +158 -0
- package/src/components/code-mirror-inline-edit.ts +55 -0
- package/src/components/code-mirror-intel.ts +81 -0
- package/src/components/code-mirror-lang.ts +128 -0
- package/src/components/context-menu-utils.ts +20 -0
- package/src/components/date-picker-utils.ts +284 -0
- package/src/components/date-range-validation.ts +29 -0
- package/src/components/date-validation.ts +38 -0
- package/src/components/dialog-viewport.ts +48 -0
- package/src/components/dialog-window.ts +253 -0
- package/src/components/dropdown-utils.ts +31 -0
- package/src/components/form-utils.ts +77 -0
- package/src/components/input-number-utils.ts +128 -0
- package/src/components/input-rules.ts +84 -0
- package/src/components/loading-utils.ts +8 -0
- package/src/components/markdown-utils.ts +126 -0
- package/src/components/menu-utils.ts +48 -0
- package/src/components/overlay-utils.ts +47 -0
- package/src/components/pagination-utils.ts +38 -0
- package/src/components/popover-utils.ts +8 -0
- package/src/components/reka.ts +106 -0
- package/src/components/resolve-radius.ts +22 -0
- package/src/components/resolve-size.ts +29 -0
- package/src/components/scrollbar-utils.ts +10 -0
- package/src/components/select-utils.ts +72 -0
- package/src/components/split-pane-utils.ts +187 -0
- package/src/components/steps-utils.ts +22 -0
- package/src/components/table/RsTableBodyRow.vue +287 -0
- package/src/components/table/RsTableCell.vue +239 -0
- package/src/components/table/RsTableCellContent.ts +18 -0
- package/src/components/table/RsTableCellEditor.vue +351 -0
- package/src/components/table/RsTableEditGutter.vue +54 -0
- package/src/components/table/RsTableHeaderFilter.vue +107 -0
- package/src/components/table/RsTableRowStatus.vue +69 -0
- package/src/components/table/rs-table.css +1141 -0
- package/src/components/table/table-context-menu.ts +53 -0
- package/src/components/table/table-edit-utils.ts +496 -0
- package/src/components/table/table-keys.ts +12 -0
- package/src/components/table-drag.ts +215 -0
- package/src/components/table-utils.ts +950 -0
- package/src/components/tabs-utils.ts +107 -0
- package/src/components/terminal-utils.ts +301 -0
- package/src/components/terminal-wheel.ts +49 -0
- package/src/components/time-picker-utils.ts +167 -0
- package/src/components/time-range-validation.ts +23 -0
- package/src/components/time-validation.ts +20 -0
- package/src/components/tree-utils.ts +518 -0
- package/src/components/upload-utils.ts +55 -0
- package/src/components/virtual-list-utils.ts +17 -0
- package/src/composables/useClickOutside.ts +22 -0
- package/src/composables/useRsConfig.ts +91 -0
- package/src/composables/useRsI18n.ts +22 -0
- package/src/composables/useRsTabsNav.ts +187 -0
- package/src/composables/useRsToast.ts +48 -0
- package/src/composables/useTableCellTooltip.ts +239 -0
- package/src/composables/useTableEdit.ts +271 -0
- package/src/composables/useTableRowHighlight.ts +50 -0
- package/src/dev/vite-codemirror-deps.ts +20 -0
- package/src/icons/custom/api.ts +54 -0
- package/src/icons/custom/clickhouse.ts +52 -0
- package/src/icons/custom/dameng.ts +83 -0
- package/src/icons/custom/ftp.ts +61 -0
- package/src/icons/custom/kingbase.ts +52 -0
- package/src/icons/custom/mongodb.ts +50 -0
- package/src/icons/custom/mysql.ts +51 -0
- package/src/icons/custom/oracle.ts +52 -0
- package/src/icons/custom/redis.ts +51 -0
- package/src/icons/custom/sqlite.ts +50 -0
- package/src/icons/custom/sqlserver.ts +52 -0
- package/src/icons/custom/vastbase.ts +57 -0
- package/src/icons/lucide.ts +78 -0
- package/src/icons/registry.ts +35 -0
- package/src/index.ts +425 -0
- package/src/lib/iso-local-datetime.ts +152 -0
- package/src/lib/rs-dayjs.ts +56 -0
- package/src/locale/messages.ts +362 -0
- package/src/locale/types.ts +5 -0
- package/src/monaco/debug-decorations.css +20 -0
- package/src/monaco/debug-decorations.ts +70 -0
- package/src/monaco/index.ts +22 -0
- package/src/monaco/languages/index.ts +21 -0
- package/src/monaco/languages/mongodb-shell.ts +125 -0
- package/src/monaco/languages/types.ts +55 -0
- package/src/monaco/setup.ts +101 -0
- package/src/styles.css +1040 -0
- package/src/theme/apply.ts +12 -0
- package/src/theme/brand.example.css +37 -0
- package/src/theme/presets.ts +69 -0
- package/src/theme/types.ts +81 -0
- package/src/utils/rs-clipboard.ts +272 -0
- package/src/vite-env.d.ts +7 -0
- package/vite-plugins/monaco-zh-nls.ts +101 -0
- package/vite-plugins/silence-antlr-parse-console.ts +29 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RsThemeMode } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 切换明暗主题:仅设置 data-rs-theme,色值由 styles.css 中的 CSS 变量提供。
|
|
5
|
+
* 业务定制请写 CSS 覆盖 --rs-*,见 theme/brand.example.css
|
|
6
|
+
*/
|
|
7
|
+
export function applyTheme(
|
|
8
|
+
mode: RsThemeMode,
|
|
9
|
+
el: HTMLElement = document.documentElement,
|
|
10
|
+
) {
|
|
11
|
+
el.setAttribute('data-rs-theme', mode)
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 业务品牌色覆盖示例
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* 1. 复制到 apps/web/src/theme/brand.css
|
|
6
|
+
* 2. 在 main.ts 中于 niuma-ui/styles.css 之后 import
|
|
7
|
+
*
|
|
8
|
+
* 原理:CSS 变量级联,后加载的同名 --rs-* 覆盖公共 preset
|
|
9
|
+
*
|
|
10
|
+
* 表格专用 token(--rs-table-*)可单独覆盖,色值参考 VS Code Dark/Light Modern
|
|
11
|
+
* 终端专用 token(--rs-terminal-*)可单独覆盖 xterm 调色板(默认 macOS Terminal.app 风格)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
[data-rs-theme='light'] {
|
|
15
|
+
--rs-primary: #6366f1;
|
|
16
|
+
--rs-primary-hover: #4f46e5;
|
|
17
|
+
--rs-primary-container: #eef2ff;
|
|
18
|
+
--rs-on-primary-container: #312e81;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
[data-rs-theme='dark'] {
|
|
22
|
+
--rs-primary: #818cf8;
|
|
23
|
+
--rs-primary-hover: #a5b4fc;
|
|
24
|
+
--rs-primary-container: #1e1b4b;
|
|
25
|
+
--rs-on-primary-container: #c7d2fe;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* 子树隔离:把 data-rs-theme 挂在业务根节点时同样生效 */
|
|
29
|
+
.rs-app-web[data-rs-theme='light'] {
|
|
30
|
+
--rs-primary: #6366f1;
|
|
31
|
+
--rs-primary-hover: #4f46e5;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.rs-app-web[data-rs-theme='dark'] {
|
|
35
|
+
--rs-primary: #818cf8;
|
|
36
|
+
--rs-primary-hover: #a5b4fc;
|
|
37
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { RsThemeMode, RsThemeTokens } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 色板参考值(与 styles.css 中的 --rs-* 保持同步,勿单独改此处)
|
|
5
|
+
* 暗色:VS Code Dark Modern 灰阶 + macOS 系统色(primary / danger / success 等)
|
|
6
|
+
* 亮色:macOS systemBackground / label + VS Code Light Modern 表格语义
|
|
7
|
+
*/
|
|
8
|
+
export const themePresets: Record<RsThemeMode, RsThemeTokens> = {
|
|
9
|
+
light: {
|
|
10
|
+
primary: '#007aff',
|
|
11
|
+
primaryHover: '#0071eb',
|
|
12
|
+
primaryForeground: '#ffffff',
|
|
13
|
+
primaryContainer: '#e5f1ff',
|
|
14
|
+
onPrimaryContainer: '#003d99',
|
|
15
|
+
bg: '#f5f5f7',
|
|
16
|
+
surface: '#ffffff',
|
|
17
|
+
surfaceElevated: '#ffffff',
|
|
18
|
+
surfaceHover: '#e8e8ed',
|
|
19
|
+
inputBg: '#ffffff',
|
|
20
|
+
border: '#8c8c8c',
|
|
21
|
+
borderSubtle: 'rgb(60 60 67 / 0.20)',
|
|
22
|
+
text: '#1d1d1f',
|
|
23
|
+
muted: '#6e6e73',
|
|
24
|
+
placeholder: '#909090',
|
|
25
|
+
danger: '#ff3b30',
|
|
26
|
+
dangerContainer: '#ffe5e3',
|
|
27
|
+
onDangerContainer: '#c41e16',
|
|
28
|
+
success: '#34c759',
|
|
29
|
+
successContainer: '#e4f8e9',
|
|
30
|
+
onSuccessContainer: '#1a7f37',
|
|
31
|
+
warning: '#ff9500',
|
|
32
|
+
warningContainer: '#fff3e0',
|
|
33
|
+
onWarningContainer: '#b26500',
|
|
34
|
+
info: '#007aff',
|
|
35
|
+
infoContainer: '#e5f1ff',
|
|
36
|
+
onInfoContainer: '#003d99',
|
|
37
|
+
focusRing: 'color-mix(in srgb, #007aff 22%, transparent)',
|
|
38
|
+
},
|
|
39
|
+
dark: {
|
|
40
|
+
primary: '#0a84ff',
|
|
41
|
+
primaryHover: '#409cff',
|
|
42
|
+
primaryForeground: '#ffffff',
|
|
43
|
+
primaryContainer: '#0a2540',
|
|
44
|
+
onPrimaryContainer: '#6cb4ff',
|
|
45
|
+
bg: '#181818',
|
|
46
|
+
surface: '#1f1f1f',
|
|
47
|
+
surfaceElevated: '#262626',
|
|
48
|
+
surfaceHover: '#2e2e2e',
|
|
49
|
+
inputBg: '#2b2b2b',
|
|
50
|
+
border: '#6e6e6e',
|
|
51
|
+
borderSubtle: '#3a3a3a',
|
|
52
|
+
text: '#cccccc',
|
|
53
|
+
muted: '#9d9d9d',
|
|
54
|
+
placeholder: '#919191',
|
|
55
|
+
danger: '#ff453a',
|
|
56
|
+
dangerContainer: '#3b1210',
|
|
57
|
+
onDangerContainer: '#ff8a84',
|
|
58
|
+
success: '#30d158',
|
|
59
|
+
successContainer: '#0d2818',
|
|
60
|
+
onSuccessContainer: '#6ee79a',
|
|
61
|
+
warning: '#ff9f0a',
|
|
62
|
+
warningContainer: '#2e1f08',
|
|
63
|
+
onWarningContainer: '#ffc46b',
|
|
64
|
+
info: '#0a84ff',
|
|
65
|
+
infoContainer: '#0a2540',
|
|
66
|
+
onInfoContainer: '#6cb4ff',
|
|
67
|
+
focusRing: 'color-mix(in srgb, #0a84ff 28%, transparent)',
|
|
68
|
+
},
|
|
69
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type RsThemeMode = 'dark' | 'light'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 控件尺寸(由小到大):
|
|
5
|
+
* - ssm:极小
|
|
6
|
+
* - sm:小号
|
|
7
|
+
* - md:中号(默认)
|
|
8
|
+
* - lg:大号
|
|
9
|
+
*
|
|
10
|
+
* 高度由 `--rs-control-height-*` 统一控制,业务侧勿用 :deep 覆盖。
|
|
11
|
+
*/
|
|
12
|
+
export type RsComponentSize = 'ssm' | 'sm' | 'md' | 'lg'
|
|
13
|
+
|
|
14
|
+
export const RS_COMPONENT_SIZES: readonly RsComponentSize[] = ['ssm', 'sm', 'md', 'lg']
|
|
15
|
+
|
|
16
|
+
/** 各档控件图标默认像素边长。 */
|
|
17
|
+
export const RS_COMPONENT_SIZE_ICON_PX: Record<RsComponentSize, number> = {
|
|
18
|
+
ssm: 12,
|
|
19
|
+
sm: 14,
|
|
20
|
+
md: 16,
|
|
21
|
+
lg: 18,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 圆角档位(由小到大):
|
|
26
|
+
* - none:直角(无圆角)
|
|
27
|
+
* - xs / sm / md / lg:对应 `--rs-radius-*`
|
|
28
|
+
* - full:胶囊 / 圆形
|
|
29
|
+
*
|
|
30
|
+
* 业务侧用组件 `radius` / `iconRadius` 等 prop,勿 :deep 改 border-radius。
|
|
31
|
+
*/
|
|
32
|
+
export type RsRadius = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'full'
|
|
33
|
+
|
|
34
|
+
export const RS_RADII: readonly RsRadius[] = ['none', 'xs', 'sm', 'md', 'lg', 'full']
|
|
35
|
+
|
|
36
|
+
/** 各档圆角对应的 CSS 值(可直接赋给 border-radius / CSS 变量)。 */
|
|
37
|
+
export const RS_RADIUS_CSS: Record<RsRadius, string> = {
|
|
38
|
+
none: '0',
|
|
39
|
+
xs: 'var(--rs-radius-xs)',
|
|
40
|
+
sm: 'var(--rs-radius-sm)',
|
|
41
|
+
md: 'var(--rs-radius)',
|
|
42
|
+
lg: 'var(--rs-radius-lg)',
|
|
43
|
+
full: 'var(--rs-radius-full)',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 设计 token
|
|
48
|
+
* 色彩语义:Google MD3 容器色 + 字节 Arco 中性色 + 国际 SaaS 功能色
|
|
49
|
+
*/
|
|
50
|
+
export interface RsThemeTokens {
|
|
51
|
+
primary: string
|
|
52
|
+
primaryHover: string
|
|
53
|
+
primaryForeground: string
|
|
54
|
+
primaryContainer: string
|
|
55
|
+
onPrimaryContainer: string
|
|
56
|
+
bg: string
|
|
57
|
+
surface: string
|
|
58
|
+
surfaceElevated: string
|
|
59
|
+
surfaceHover: string
|
|
60
|
+
inputBg: string
|
|
61
|
+
border: string
|
|
62
|
+
borderSubtle: string
|
|
63
|
+
text: string
|
|
64
|
+
muted: string
|
|
65
|
+
placeholder: string
|
|
66
|
+
danger: string
|
|
67
|
+
dangerContainer: string
|
|
68
|
+
onDangerContainer: string
|
|
69
|
+
success: string
|
|
70
|
+
successContainer: string
|
|
71
|
+
onSuccessContainer: string
|
|
72
|
+
warning: string
|
|
73
|
+
warningContainer: string
|
|
74
|
+
onWarningContainer: string
|
|
75
|
+
info: string
|
|
76
|
+
infoContainer: string
|
|
77
|
+
onInfoContainer: string
|
|
78
|
+
focusRing: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const themeAttribute = 'data-rs-theme'
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/** 剪贴板读写封装:浏览器 Clipboard API 优先,CEF Shell Bridge 最后兜底。
|
|
2
|
+
* CEF 壳层通过 CefPermissionHandler 自动放行 CEF_PERMISSION_TYPE_CLIPBOARD,
|
|
3
|
+
* 避免 Alloy 默认 IGNORE;Shell Bridge 仅在 API 仍失败时使用。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type CefQueryFn = (req: {
|
|
7
|
+
request: string
|
|
8
|
+
onSuccess: (response: string) => void
|
|
9
|
+
onFailure: (code: number, message: string) => void
|
|
10
|
+
}) => void
|
|
11
|
+
|
|
12
|
+
function bridgeRequestId(): string {
|
|
13
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
14
|
+
return crypto.randomUUID()
|
|
15
|
+
}
|
|
16
|
+
return `clip-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getCefQuery(): CefQueryFn | null {
|
|
20
|
+
const fn = (globalThis as { cefQuery?: CefQueryFn }).cefQuery
|
|
21
|
+
return typeof fn === 'function' ? fn : null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function bridgeInvokeClipboard<T>(method: string, params: Record<string, unknown> = {}): Promise<T | null> {
|
|
25
|
+
const cefQuery = getCefQuery()
|
|
26
|
+
if (!cefQuery) {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
cefQuery({
|
|
31
|
+
request: JSON.stringify({ method, params, id: bridgeRequestId() }),
|
|
32
|
+
onSuccess: (response) => {
|
|
33
|
+
try {
|
|
34
|
+
resolve(JSON.parse(response) as T)
|
|
35
|
+
} catch {
|
|
36
|
+
resolve(null)
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
onFailure: () => resolve(null),
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function readClipboardViaShell(): Promise<string | null> {
|
|
45
|
+
const result = await bridgeInvokeClipboard<{ text?: string }>('shell.clipboard.readText')
|
|
46
|
+
const text = result?.text
|
|
47
|
+
return text ? text : null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function writeClipboardViaShell(text: string): Promise<boolean> {
|
|
51
|
+
const result = await bridgeInvokeClipboard<{ written?: boolean }>('shell.clipboard.writeText', { text })
|
|
52
|
+
return Boolean(result?.written)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let prefetchedText: string | null = null
|
|
56
|
+
let prefetchAt = 0
|
|
57
|
+
let prefetchPromise: Promise<void> | null = null
|
|
58
|
+
let readApiBlocked = false
|
|
59
|
+
let writeApiBlocked = false
|
|
60
|
+
let permissionStateProbed = false
|
|
61
|
+
|
|
62
|
+
const PREFETCH_TTL_MS = 10_000
|
|
63
|
+
|
|
64
|
+
function isClipboardPermissionDeniedError(err: unknown): boolean {
|
|
65
|
+
return err instanceof DOMException && (err.name === 'NotAllowedError' || err.name === 'SecurityError')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function markReadApiBlocked(err?: unknown): void {
|
|
69
|
+
if (err === undefined || isClipboardPermissionDeniedError(err)) {
|
|
70
|
+
readApiBlocked = true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function markWriteApiBlocked(err?: unknown): void {
|
|
75
|
+
if (err === undefined || isClipboardPermissionDeniedError(err)) {
|
|
76
|
+
writeApiBlocked = true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function syncClipboardPermissionState(): Promise<void> {
|
|
81
|
+
if (permissionStateProbed) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
permissionStateProbed = true
|
|
85
|
+
if (!navigator.permissions?.query) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const read = await navigator.permissions.query({ name: 'clipboard-read' as PermissionName })
|
|
90
|
+
if (read.state === 'denied') {
|
|
91
|
+
readApiBlocked = true
|
|
92
|
+
}
|
|
93
|
+
read.onchange = () => {
|
|
94
|
+
readApiBlocked = read.state === 'denied'
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// clipboard-read permission query 并非所有环境都支持
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const write = await navigator.permissions.query({ name: 'clipboard-write' as PermissionName })
|
|
101
|
+
if (write.state === 'denied') {
|
|
102
|
+
writeApiBlocked = true
|
|
103
|
+
}
|
|
104
|
+
write.onchange = () => {
|
|
105
|
+
writeApiBlocked = write.state === 'denied'
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// ignore
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 在 contextmenu 等仍持有用户手势时预读剪贴板,供稍后菜单项点击使用 */
|
|
113
|
+
export async function prefetchClipboardText(): Promise<void> {
|
|
114
|
+
prefetchedText = null
|
|
115
|
+
prefetchAt = Date.now()
|
|
116
|
+
await syncClipboardPermissionState()
|
|
117
|
+
if (!readApiBlocked) {
|
|
118
|
+
const fromReadText = await readClipboardViaReadText()
|
|
119
|
+
if (fromReadText) {
|
|
120
|
+
prefetchedText = fromReadText
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const fromItems = await readClipboardViaItems()
|
|
124
|
+
if (fromItems) {
|
|
125
|
+
prefetchedText = fromItems
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const fromShell = await readClipboardViaShell()
|
|
130
|
+
if (fromShell) {
|
|
131
|
+
prefetchedText = fromShell
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 启动预读并返回 promise,菜单点击粘贴前应 await */
|
|
136
|
+
export function beginClipboardPrefetch(): Promise<void> {
|
|
137
|
+
prefetchPromise = prefetchClipboardText().finally(() => {
|
|
138
|
+
prefetchPromise = null
|
|
139
|
+
})
|
|
140
|
+
return prefetchPromise
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function waitClipboardPrefetch(): Promise<void> {
|
|
144
|
+
if (prefetchPromise) {
|
|
145
|
+
await prefetchPromise
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function takePrefetchedText(): string | null {
|
|
150
|
+
if (!prefetchedText || Date.now() - prefetchAt > PREFETCH_TTL_MS) {
|
|
151
|
+
prefetchedText = null
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
const text = prefetchedText
|
|
155
|
+
prefetchedText = null
|
|
156
|
+
return text
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function readClipboardText(): Promise<string | null> {
|
|
160
|
+
await waitClipboardPrefetch()
|
|
161
|
+
const cached = takePrefetchedText()
|
|
162
|
+
if (cached) {
|
|
163
|
+
return cached
|
|
164
|
+
}
|
|
165
|
+
await syncClipboardPermissionState()
|
|
166
|
+
if (!readApiBlocked) {
|
|
167
|
+
const fromReadText = await readClipboardViaReadText()
|
|
168
|
+
if (fromReadText) {
|
|
169
|
+
return fromReadText
|
|
170
|
+
}
|
|
171
|
+
const fromItems = await readClipboardViaItems()
|
|
172
|
+
if (fromItems) {
|
|
173
|
+
return fromItems
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return readClipboardViaShell()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function readClipboardViaReadText(): Promise<string | null> {
|
|
180
|
+
if (readApiBlocked || !navigator.clipboard?.readText) {
|
|
181
|
+
return null
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const text = await navigator.clipboard.readText()
|
|
185
|
+
return text || null
|
|
186
|
+
} catch (err) {
|
|
187
|
+
markReadApiBlocked(err)
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** readText 失败时尝试 clipboard.read() 取 text/plain(CEF 部分场景更稳) */
|
|
193
|
+
async function readClipboardViaItems(): Promise<string | null> {
|
|
194
|
+
if (readApiBlocked || !navigator.clipboard?.read) {
|
|
195
|
+
return null
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const items = await navigator.clipboard.read()
|
|
199
|
+
for (const item of items) {
|
|
200
|
+
if (!item.types.includes('text/plain')) {
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
const blob = await item.getType('text/plain')
|
|
204
|
+
const text = await blob.text()
|
|
205
|
+
if (text) {
|
|
206
|
+
return text
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
markReadApiBlocked(err)
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function writeClipboardText(text: string): Promise<boolean> {
|
|
217
|
+
if (!text || writeApiBlocked || !navigator.clipboard?.writeText) {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
await navigator.clipboard.writeText(text)
|
|
222
|
+
return true
|
|
223
|
+
} catch (err) {
|
|
224
|
+
markWriteApiBlocked(err)
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** execCommand 兜底:CEF / 无 Clipboard API 权限时菜单复制仍可用 */
|
|
230
|
+
export function copyTextWithExecCommand(text: string): boolean {
|
|
231
|
+
if (!text || typeof document === 'undefined') {
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const textarea = document.createElement('textarea')
|
|
236
|
+
textarea.value = text
|
|
237
|
+
textarea.setAttribute('readonly', '')
|
|
238
|
+
textarea.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0'
|
|
239
|
+
document.body.appendChild(textarea)
|
|
240
|
+
textarea.select()
|
|
241
|
+
const ok = document.execCommand('copy')
|
|
242
|
+
textarea.remove()
|
|
243
|
+
return ok
|
|
244
|
+
} catch {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** 写入系统剪贴板(API 优先,execCommand 兜底) */
|
|
250
|
+
export async function copyTextToClipboard(text: string): Promise<boolean> {
|
|
251
|
+
if (!text) {
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
await syncClipboardPermissionState()
|
|
255
|
+
if (!writeApiBlocked && (await writeClipboardText(text))) {
|
|
256
|
+
return true
|
|
257
|
+
}
|
|
258
|
+
if (copyTextWithExecCommand(text)) {
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
return writeClipboardViaShell(text)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** @internal vitest 用 */
|
|
265
|
+
export function __resetClipboardStateForTests(): void {
|
|
266
|
+
prefetchedText = null
|
|
267
|
+
prefetchAt = 0
|
|
268
|
+
prefetchPromise = null
|
|
269
|
+
readApiBlocked = false
|
|
270
|
+
writeApiBlocked = false
|
|
271
|
+
permissionStateProbed = false
|
|
272
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite 插件:将 Monaco Editor 0.55 内置的官方中文 NLS 消息注入到
|
|
3
|
+
* `nls.messages.js` 模块,使编辑器右键菜单、命令面板等 UI 显示中文。
|
|
4
|
+
*
|
|
5
|
+
* Monaco 0.55 ESM 编译产物中 localize() 使用数字索引,需要设置
|
|
6
|
+
* globalThis._VSCODE_NLS_MESSAGES 数组才能生效。
|
|
7
|
+
* Monaco 自带 min/vs/nls.messages.zh-cn.js.js,提供完整的官方中文翻译。
|
|
8
|
+
*
|
|
9
|
+
* 两阶段拦截:
|
|
10
|
+
* 1. config hook + rolldown plugin → 覆盖 Vite 8 optimizeDeps 预构建阶段
|
|
11
|
+
* 2. Rollup load hook → 覆盖生产构建 / 非预构建 dev 场景
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from 'fs'
|
|
14
|
+
import { createRequire } from 'module'
|
|
15
|
+
import type { Plugin } from 'vite'
|
|
16
|
+
|
|
17
|
+
function extractZhCnMessages(): string {
|
|
18
|
+
try {
|
|
19
|
+
const require = createRequire(import.meta.url)
|
|
20
|
+
const zhCnPath = require.resolve('monaco-editor/min/vs/nls.messages.zh-cn.js.js')
|
|
21
|
+
const raw = readFileSync(zhCnPath, 'utf8')
|
|
22
|
+
|
|
23
|
+
const start = raw.indexOf('_VSCODE_NLS_MESSAGES=[')
|
|
24
|
+
if (start === -1) return ''
|
|
25
|
+
|
|
26
|
+
const arrStart = raw.indexOf('[', start)
|
|
27
|
+
let depth = 0
|
|
28
|
+
let arrEnd = -1
|
|
29
|
+
for (let i = arrStart; i < raw.length; i++) {
|
|
30
|
+
if (raw[i] === '[') depth++
|
|
31
|
+
else if (raw[i] === ']') {
|
|
32
|
+
depth--
|
|
33
|
+
if (depth === 0) {
|
|
34
|
+
arrEnd = i
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return arrEnd !== -1 ? raw.slice(arrStart, arrEnd + 1) : ''
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.warn('[monaco-zh-nls] 无法加载 Monaco 官方中文 NLS:', e)
|
|
42
|
+
return ''
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function monacoZhNlsPlugin(): Plugin {
|
|
47
|
+
const messagesCode = extractZhCnMessages()
|
|
48
|
+
|
|
49
|
+
if (!messagesCode) {
|
|
50
|
+
console.warn('[monaco-zh-nls] 中文 NLS 数组提取失败,跳过插件')
|
|
51
|
+
return { name: 'monaco-zh-nls' }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const nlsModuleCode = `
|
|
55
|
+
/* monaco-zh-nls: Monaco 0.55 官方中文翻译 */
|
|
56
|
+
globalThis._VSCODE_NLS_MESSAGES = ${messagesCode};
|
|
57
|
+
globalThis._VSCODE_NLS_LANGUAGE = 'zh-cn';
|
|
58
|
+
export function getNLSMessages() { return globalThis._VSCODE_NLS_MESSAGES; }
|
|
59
|
+
export function getNLSLanguage() { return globalThis._VSCODE_NLS_LANGUAGE; }
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
const nlsFileFilter = /monaco-editor[/\\]esm[/\\]vs[/\\]nls\.messages\.js$/
|
|
63
|
+
|
|
64
|
+
/** rolldown plugin —— 用于 Vite 8 optimizeDeps 预构建阶段 */
|
|
65
|
+
const rolldownPlugin = {
|
|
66
|
+
name: 'monaco-zh-nls-rolldown',
|
|
67
|
+
load(id: string) {
|
|
68
|
+
if (nlsFileFilter.test(id)) {
|
|
69
|
+
return nlsModuleCode
|
|
70
|
+
}
|
|
71
|
+
return undefined
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
name: 'monaco-zh-nls',
|
|
77
|
+
enforce: 'pre',
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 将 rolldown plugin 注入到 optimizeDeps,覆盖 Vite 8 开发模式预构建阶段。
|
|
81
|
+
* Monaco 没有 "type":"module",Vite 会用 rolldown 预构建它,
|
|
82
|
+
* 此时需要通过 rolldownOptions.plugins 拦截。
|
|
83
|
+
*/
|
|
84
|
+
config() {
|
|
85
|
+
return {
|
|
86
|
+
optimizeDeps: {
|
|
87
|
+
rolldownOptions: {
|
|
88
|
+
plugins: [rolldownPlugin],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/** Rollup load hook —— 用于生产构建 / SSR / 非预构建 dev 场景 */
|
|
95
|
+
load(id: string) {
|
|
96
|
+
if (nlsFileFilter.test(id)) {
|
|
97
|
+
return nlsModuleCode
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 静默 antlr4ng ConsoleErrorListener 的 parse 刷屏。
|
|
5
|
+
*
|
|
6
|
+
* dt-sql-parser 在补全路径 `createParser(input)` 未传 errorListener 时,
|
|
7
|
+
* 不会 `removeErrorListeners()`,半成品 SQL 的 syntaxError 会打满 DevTools。
|
|
8
|
+
* 真实诊断改走 Monaco markers(pgsql diagnostics)。
|
|
9
|
+
*/
|
|
10
|
+
export function silenceAntlrParseConsole(): Plugin {
|
|
11
|
+
return {
|
|
12
|
+
name: 'silence-antlr-parse-console',
|
|
13
|
+
enforce: 'pre',
|
|
14
|
+
transform(code, id) {
|
|
15
|
+
const normalized = id.replace(/\\/g, '/')
|
|
16
|
+
if (!normalized.includes('antlr4ng')) return null
|
|
17
|
+
if (!code.includes('ConsoleErrorListener')) return null
|
|
18
|
+
if (!code.includes('console.error("line "')) return null
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
code: code.replace(
|
|
22
|
+
/console\.error\("line " \+ line \+ ":" \+ charPositionInLine \+ " " \+ msg\);/g,
|
|
23
|
+
'/* parse → Monaco markers; skip console spam */',
|
|
24
|
+
),
|
|
25
|
+
map: null,
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
}
|