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.
Files changed (220) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +25 -0
  4. package/README.en.md +99 -0
  5. package/README.md +161 -0
  6. package/package.json +106 -0
  7. package/src/__tests__/RsBadge.spec.ts +40 -0
  8. package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
  9. package/src/__tests__/RsButton.spec.ts +108 -0
  10. package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
  11. package/src/__tests__/RsCard.spec.ts +97 -0
  12. package/src/__tests__/RsCheckbox.spec.ts +36 -0
  13. package/src/__tests__/RsCodeEditor.spec.ts +111 -0
  14. package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
  15. package/src/__tests__/RsContainer.spec.ts +89 -0
  16. package/src/__tests__/RsContextMenu.spec.ts +118 -0
  17. package/src/__tests__/RsDatePicker.spec.ts +88 -0
  18. package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
  19. package/src/__tests__/RsDialog.spec.ts +404 -0
  20. package/src/__tests__/RsDrawer.spec.ts +145 -0
  21. package/src/__tests__/RsDropdown.spec.ts +135 -0
  22. package/src/__tests__/RsEmpty.spec.ts +99 -0
  23. package/src/__tests__/RsForm.spec.ts +173 -0
  24. package/src/__tests__/RsIcon.spec.ts +244 -0
  25. package/src/__tests__/RsInput.spec.ts +269 -0
  26. package/src/__tests__/RsInputNumber.spec.ts +118 -0
  27. package/src/__tests__/RsLabel.spec.ts +106 -0
  28. package/src/__tests__/RsLink.spec.ts +49 -0
  29. package/src/__tests__/RsLoading.spec.ts +84 -0
  30. package/src/__tests__/RsMarkdown.spec.ts +143 -0
  31. package/src/__tests__/RsMenu.spec.ts +119 -0
  32. package/src/__tests__/RsNewComponents.spec.ts +17 -0
  33. package/src/__tests__/RsPagination.spec.ts +158 -0
  34. package/src/__tests__/RsPopover.spec.ts +126 -0
  35. package/src/__tests__/RsScrollbar.spec.ts +53 -0
  36. package/src/__tests__/RsSelect.spec.ts +291 -0
  37. package/src/__tests__/RsSidebar.spec.ts +119 -0
  38. package/src/__tests__/RsSplitPane.spec.ts +177 -0
  39. package/src/__tests__/RsStatCard.spec.ts +79 -0
  40. package/src/__tests__/RsSteps.spec.ts +125 -0
  41. package/src/__tests__/RsTable.perf.spec.ts +103 -0
  42. package/src/__tests__/RsTable.spec.ts +1135 -0
  43. package/src/__tests__/RsTabs.spec.ts +328 -0
  44. package/src/__tests__/RsTerminal.spec.ts +218 -0
  45. package/src/__tests__/RsTimePicker.spec.ts +77 -0
  46. package/src/__tests__/RsToaster.spec.ts +95 -0
  47. package/src/__tests__/RsToolbar.spec.ts +75 -0
  48. package/src/__tests__/RsTooltip.spec.ts +111 -0
  49. package/src/__tests__/RsTree.spec.ts +572 -0
  50. package/src/__tests__/RsUpload.spec.ts +160 -0
  51. package/src/__tests__/RsVirtualList.spec.ts +77 -0
  52. package/src/__tests__/date-picker-utils.spec.ts +70 -0
  53. package/src/__tests__/dialog-viewport.spec.ts +38 -0
  54. package/src/__tests__/iso-local-datetime.spec.ts +58 -0
  55. package/src/__tests__/rs-clipboard.spec.ts +142 -0
  56. package/src/__tests__/setup.ts +12 -0
  57. package/src/__tests__/split-pane-utils.spec.ts +136 -0
  58. package/src/__tests__/table-drag.spec.ts +35 -0
  59. package/src/__tests__/table-edit-utils.spec.ts +194 -0
  60. package/src/__tests__/table-utils.spec.ts +286 -0
  61. package/src/__tests__/theme.spec.ts +22 -0
  62. package/src/__tests__/tree-utils.spec.ts +234 -0
  63. package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
  64. package/src/components/RsAvatar.vue +115 -0
  65. package/src/components/RsBadge.vue +59 -0
  66. package/src/components/RsBreadcrumb.vue +65 -0
  67. package/src/components/RsBreadcrumbItems.vue +36 -0
  68. package/src/components/RsButton.vue +393 -0
  69. package/src/components/RsCalendarGrid.vue +284 -0
  70. package/src/components/RsCard.vue +237 -0
  71. package/src/components/RsCheckbox.vue +189 -0
  72. package/src/components/RsCodeBlock.vue +278 -0
  73. package/src/components/RsCodeEditor.vue +597 -0
  74. package/src/components/RsConfigProvider.vue +92 -0
  75. package/src/components/RsConfirmDialog.vue +178 -0
  76. package/src/components/RsContainer.vue +235 -0
  77. package/src/components/RsContextMenu.vue +238 -0
  78. package/src/components/RsContextMenuItems.vue +77 -0
  79. package/src/components/RsDatePicker.vue +756 -0
  80. package/src/components/RsDateTimePicker.vue +42 -0
  81. package/src/components/RsDialog.vue +486 -0
  82. package/src/components/RsDrawer.vue +175 -0
  83. package/src/components/RsDropdown.vue +252 -0
  84. package/src/components/RsDropdownItems.vue +90 -0
  85. package/src/components/RsEmpty.vue +107 -0
  86. package/src/components/RsForm.vue +131 -0
  87. package/src/components/RsIcon.vue +104 -0
  88. package/src/components/RsInput.vue +1101 -0
  89. package/src/components/RsInputNumber.vue +532 -0
  90. package/src/components/RsLabel.vue +54 -0
  91. package/src/components/RsLink.vue +106 -0
  92. package/src/components/RsLoading.vue +171 -0
  93. package/src/components/RsMarkdown.vue +329 -0
  94. package/src/components/RsMenu.vue +256 -0
  95. package/src/components/RsMenuItems.vue +125 -0
  96. package/src/components/RsMonacoEditor.vue +541 -0
  97. package/src/components/RsPagination.vue +184 -0
  98. package/src/components/RsPopover.vue +77 -0
  99. package/src/components/RsProseEditor.vue +160 -0
  100. package/src/components/RsScrollbar.vue +171 -0
  101. package/src/components/RsSelect.vue +1045 -0
  102. package/src/components/RsSidebar.vue +121 -0
  103. package/src/components/RsSidebarGroup.vue +32 -0
  104. package/src/components/RsSidebarItem.vue +72 -0
  105. package/src/components/RsSplitPane.vue +619 -0
  106. package/src/components/RsStatCard.vue +85 -0
  107. package/src/components/RsSteps.vue +138 -0
  108. package/src/components/RsTable.vue +2664 -0
  109. package/src/components/RsTableColgroup.vue +17 -0
  110. package/src/components/RsTableHeaderRow.vue +30 -0
  111. package/src/components/RsTabs.vue +951 -0
  112. package/src/components/RsTerminal.vue +697 -0
  113. package/src/components/RsTimePicker.vue +554 -0
  114. package/src/components/RsTimePickerColumns.vue +226 -0
  115. package/src/components/RsToaster.vue +396 -0
  116. package/src/components/RsToolbar.vue +135 -0
  117. package/src/components/RsTooltip.vue +142 -0
  118. package/src/components/RsTooltipProvider.vue +20 -0
  119. package/src/components/RsTree.vue +1248 -0
  120. package/src/components/RsUpload.vue +279 -0
  121. package/src/components/RsVirtualList.vue +217 -0
  122. package/src/components/avatar-utils.ts +19 -0
  123. package/src/components/breadcrumb-utils.ts +19 -0
  124. package/src/components/class-utils.ts +29 -0
  125. package/src/components/code-editor-utils.ts +106 -0
  126. package/src/components/code-mirror-completion.ts +40 -0
  127. package/src/components/code-mirror-diagnostics.ts +52 -0
  128. package/src/components/code-mirror-ghost.ts +158 -0
  129. package/src/components/code-mirror-inline-edit.ts +55 -0
  130. package/src/components/code-mirror-intel.ts +81 -0
  131. package/src/components/code-mirror-lang.ts +128 -0
  132. package/src/components/context-menu-utils.ts +20 -0
  133. package/src/components/date-picker-utils.ts +284 -0
  134. package/src/components/date-range-validation.ts +29 -0
  135. package/src/components/date-validation.ts +38 -0
  136. package/src/components/dialog-viewport.ts +48 -0
  137. package/src/components/dialog-window.ts +253 -0
  138. package/src/components/dropdown-utils.ts +31 -0
  139. package/src/components/form-utils.ts +77 -0
  140. package/src/components/input-number-utils.ts +128 -0
  141. package/src/components/input-rules.ts +84 -0
  142. package/src/components/loading-utils.ts +8 -0
  143. package/src/components/markdown-utils.ts +126 -0
  144. package/src/components/menu-utils.ts +48 -0
  145. package/src/components/overlay-utils.ts +47 -0
  146. package/src/components/pagination-utils.ts +38 -0
  147. package/src/components/popover-utils.ts +8 -0
  148. package/src/components/reka.ts +106 -0
  149. package/src/components/resolve-radius.ts +22 -0
  150. package/src/components/resolve-size.ts +29 -0
  151. package/src/components/scrollbar-utils.ts +10 -0
  152. package/src/components/select-utils.ts +72 -0
  153. package/src/components/split-pane-utils.ts +187 -0
  154. package/src/components/steps-utils.ts +22 -0
  155. package/src/components/table/RsTableBodyRow.vue +287 -0
  156. package/src/components/table/RsTableCell.vue +239 -0
  157. package/src/components/table/RsTableCellContent.ts +18 -0
  158. package/src/components/table/RsTableCellEditor.vue +351 -0
  159. package/src/components/table/RsTableEditGutter.vue +54 -0
  160. package/src/components/table/RsTableHeaderFilter.vue +107 -0
  161. package/src/components/table/RsTableRowStatus.vue +69 -0
  162. package/src/components/table/rs-table.css +1141 -0
  163. package/src/components/table/table-context-menu.ts +53 -0
  164. package/src/components/table/table-edit-utils.ts +496 -0
  165. package/src/components/table/table-keys.ts +12 -0
  166. package/src/components/table-drag.ts +215 -0
  167. package/src/components/table-utils.ts +950 -0
  168. package/src/components/tabs-utils.ts +107 -0
  169. package/src/components/terminal-utils.ts +301 -0
  170. package/src/components/terminal-wheel.ts +49 -0
  171. package/src/components/time-picker-utils.ts +167 -0
  172. package/src/components/time-range-validation.ts +23 -0
  173. package/src/components/time-validation.ts +20 -0
  174. package/src/components/tree-utils.ts +518 -0
  175. package/src/components/upload-utils.ts +55 -0
  176. package/src/components/virtual-list-utils.ts +17 -0
  177. package/src/composables/useClickOutside.ts +22 -0
  178. package/src/composables/useRsConfig.ts +91 -0
  179. package/src/composables/useRsI18n.ts +22 -0
  180. package/src/composables/useRsTabsNav.ts +187 -0
  181. package/src/composables/useRsToast.ts +48 -0
  182. package/src/composables/useTableCellTooltip.ts +239 -0
  183. package/src/composables/useTableEdit.ts +271 -0
  184. package/src/composables/useTableRowHighlight.ts +50 -0
  185. package/src/dev/vite-codemirror-deps.ts +20 -0
  186. package/src/icons/custom/api.ts +54 -0
  187. package/src/icons/custom/clickhouse.ts +52 -0
  188. package/src/icons/custom/dameng.ts +83 -0
  189. package/src/icons/custom/ftp.ts +61 -0
  190. package/src/icons/custom/kingbase.ts +52 -0
  191. package/src/icons/custom/mongodb.ts +50 -0
  192. package/src/icons/custom/mysql.ts +51 -0
  193. package/src/icons/custom/oracle.ts +52 -0
  194. package/src/icons/custom/redis.ts +51 -0
  195. package/src/icons/custom/sqlite.ts +50 -0
  196. package/src/icons/custom/sqlserver.ts +52 -0
  197. package/src/icons/custom/vastbase.ts +57 -0
  198. package/src/icons/lucide.ts +78 -0
  199. package/src/icons/registry.ts +35 -0
  200. package/src/index.ts +425 -0
  201. package/src/lib/iso-local-datetime.ts +152 -0
  202. package/src/lib/rs-dayjs.ts +56 -0
  203. package/src/locale/messages.ts +362 -0
  204. package/src/locale/types.ts +5 -0
  205. package/src/monaco/debug-decorations.css +20 -0
  206. package/src/monaco/debug-decorations.ts +70 -0
  207. package/src/monaco/index.ts +22 -0
  208. package/src/monaco/languages/index.ts +21 -0
  209. package/src/monaco/languages/mongodb-shell.ts +125 -0
  210. package/src/monaco/languages/types.ts +55 -0
  211. package/src/monaco/setup.ts +101 -0
  212. package/src/styles.css +1040 -0
  213. package/src/theme/apply.ts +12 -0
  214. package/src/theme/brand.example.css +37 -0
  215. package/src/theme/presets.ts +69 -0
  216. package/src/theme/types.ts +81 -0
  217. package/src/utils/rs-clipboard.ts +272 -0
  218. package/src/vite-env.d.ts +7 -0
  219. package/vite-plugins/monaco-zh-nls.ts +101 -0
  220. 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,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue'
5
+ const component: DefineComponent<object, object, unknown>
6
+ export default component
7
+ }
@@ -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
+ }