yh-codemirror 1.0.8 → 1.0.9

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.0.10
4
+
5
+ - 2026-10-09 15:51 feature: `language` 新增 `json`;未知语言回落 `javascript`
6
+ - slw 的图表配置编辑器要用 JSON 语法高亮,之前只能靠宿主额外传 extensions
7
+ - `getCodeLanguagePlugin` 原来直接 `plugins[language]()`,未知语言(含 undefined)返回 undefined,
8
+ CM6 会抛 `Unrecognized extension value in extension set`,改为回落 javascript
9
+
3
10
  ## 1.0.7
4
11
 
5
12
  - 2026-10-08 15:06 feature: 新增 `aiWindowId` 入参与 `ai-activate` 事件
package/CodeMirror.vue CHANGED
@@ -101,6 +101,12 @@
101
101
  class="ai-history-q"
102
102
  @click="toggleHistoryItem(item.id)">
103
103
  <span class="ai-history-time">{{ item.time }}</span>
104
+ <span
105
+ v-if="item.location"
106
+ class="ai-history-loc"
107
+ :title="item.location"
108
+ >{{ item.location }}</span
109
+ >
104
110
  <span class="ai-history-text">{{ item.prompt }}</span>
105
111
  <span class="ai-history-toggle">{{ expandedHistoryId === item.id ? '收起' : '展开' }}</span>
106
112
  </div>
@@ -138,10 +144,10 @@
138
144
  @click="openAi()"
139
145
  >AI</span
140
146
  >
141
- <i
147
+ <!-- <i
142
148
  title="全屏"
143
149
  class="iconfont icon-fullscreen"
144
- @click="fullscreen"></i>
150
+ @click="fullscreen"></i> -->
145
151
  <i
146
152
  title="美化"
147
153
  class="iconfont icon-magic-stick"
@@ -218,8 +224,7 @@ const props = withDefaults(
218
224
  /** 编辑器主题:dark(默认)或 light */
219
225
  theme?: 'dark' | 'light';
220
226
  /**
221
- * AI 面板尺寸档位。编辑区被放大时(如 window-form 的「放大」抽屉)传 'large',
222
- * 否则 12px 字号 + 2 行输入框在大面板里会显得很局促。
227
+ * AI 面板尺寸档位。编辑区被放大时(如 window-form 的「放大」抽屉)传 'large'
223
228
  */
224
229
  aiPanelSize?: 'normal' | 'large';
225
230
  /**
@@ -228,6 +233,11 @@ const props = withDefaults(
228
233
  * 由宿主去拉配置;不传就不触发该事件。
229
234
  */
230
235
  aiWindowId?: string | number;
236
+ /**
237
+ * 这条提问来自哪里(宿主传入,如「区域名称 · 数据初始化」)。
238
+ * 组件只负责原样记进历史并在问题前显示,不解析它的含义
239
+ */
240
+ aiLocation?: string;
231
241
  }>(),
232
242
  {
233
243
  modelValue: '',
@@ -239,6 +249,7 @@ const props = withDefaults(
239
249
  theme: 'dark',
240
250
  aiPanelSize: 'normal',
241
251
  aiWindowId: '',
252
+ aiLocation: '',
242
253
  }
243
254
  );
244
255
 
@@ -487,7 +498,7 @@ const submitAi = async () => {
487
498
  view.focus();
488
499
  const appliedText = view.state.sliceDoc(range.from, range.to);
489
500
  // 记录历史问答(含 AI 最终写入编辑器的完整文本,撤销了也能在这里找回原文)
490
- pushAiHistory({prompt, scope: aiScopeText.value, answer: appliedText});
501
+ pushAiHistory({prompt, scope: aiScopeText.value, location: props.aiLocation, answer: appliedText});
491
502
  emits('ai-apply', {prompt, text: appliedText});
492
503
  ElMessage({
493
504
  message: 'AI 修改已应用',
@@ -662,19 +673,30 @@ defineExpose({
662
673
  &.is-light {
663
674
  background-color: #fff;
664
675
  .cm-editor {
676
+ border: 1px solid var(--el-border-color-light);
677
+ border-radius: 8px;
678
+ cursor: text;
679
+ width: 100%;
680
+ overflow: auto;
681
+ .cm-gutters.cm-gutters-before {
682
+ background-color: #fafcff;
683
+ }
665
684
  .cm-line.cm-activeLine::before {
666
- background-color: #f0f2f5;
685
+ background-color: #f1f7ff;
667
686
  }
668
687
  .cm-activeLineGutter {
669
- background-color: #f0f2f5;
688
+ background-color: #f1f7ff;
670
689
  }
671
690
  .cm-selectionBackground {
672
691
  background-color: #d4e7f9 !important;
673
692
  }
674
- &.cm-focused .cm-selectionBackground {
675
- background-color: #b3d8ff !important;
693
+ &.cm-focused {
694
+ outline: none;
695
+ box-shadow: 1px 1px 8px #b1c7db;
696
+ .cm-selectionBackground {
697
+ background-color: #b3d8ff !important;
698
+ }
676
699
  }
677
- // 双击选词后其它同名 occurrence(highlightSelectionMatches):用暖色,和蓝色选区、中性当前行都区分开
678
700
  .cm-selectionMatch {
679
701
  background-color: #ffe9c2;
680
702
  }
@@ -745,6 +767,10 @@ defineExpose({
745
767
  .ai-history-q {
746
768
  color: #333;
747
769
  }
770
+ .ai-history-loc {
771
+ background: rgba(64, 158, 255, 0.14);
772
+ color: #1a6fc4;
773
+ }
748
774
  .ai-history-label .ai-history-scope {
749
775
  color: #606266;
750
776
  }
@@ -770,16 +796,6 @@ defineExpose({
770
796
  line-height: 25px;
771
797
  font-family: 'Fira Code', 'Roboto Mono', Consolas, 'Microsoft Yahei';
772
798
  }
773
- /* 当前行高亮 vs 选中区,必须一眼能区分 —— 这里真正的坑不是配色而是**绘制层级**:
774
- CM6 的选区画在 .cm-selectionLayer 上(drawSelection 内部 above:false,z-index 取 -1-pos,是负数),
775
- 它和 .cm-content 同在 .cm-scroller 这个层叠上下文里;而 .cm-line 是普通 in-flow 块,
776
- 按 CSS 绘制顺序「负 z-index 后代 → in-flow 块 → 行内内容」,**in-flow 块画在选区层之上**。
777
- 所以给 .cm-line 设不透明背景会把选区整块盖掉 —— baseTheme 自带的
778
- &light .cm-activeLine: #cceeff44 / &dark: #99eeff33 是半透明的,才不会盖住,
779
- 之前改成了不透明色,选区就被糊掉了。
780
- 解法:把当前行高亮挪到 ::before 上并压到 z-index:-100,绘制顺序变成
781
- 当前行底(-100) → 选区层(-2) → 文字
782
- 选中区永远压在当前行高亮之上,同时仍然在文字之下。 */
783
799
  .cm-line.cm-activeLine::before {
784
800
  content: '';
785
801
  position: absolute;
@@ -791,19 +807,12 @@ defineExpose({
791
807
  pointer-events: none;
792
808
  background-color: #173541;
793
809
  }
794
- // 当前行高亮改由上面的 ::before 承载;这里必须把主题自带的**不透明**底色清掉,
795
- // 否则它作为 in-flow 块的背景仍然会盖住选区层(solarizedDark 的 .cm-activeLine 就是 #173541,
796
- // 和它的 selection 同色,这正是最初「选中区看不出来」的根源)。
797
810
  .cm-activeLine {
798
811
  background-color: transparent;
799
812
  }
800
813
  .cm-activeLineGutter {
801
814
  background-color: #173541;
802
815
  }
803
- /* 选区色必须带 !important:CM 6.39 的 baseTheme 把默认色写成了
804
- "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground"
805
- 这种「5 个类 + 子代选择器」,权重高于本组件、也高于 solarizedDark 自带的主题,
806
- 光堆类名盖不住;用 !important 也不用跟它的 DOM 结构绑死。 */
807
816
  .cm-selectionBackground {
808
817
  /* 失焦:压暗一档 */
809
818
  background-color: #204a5a !important;
@@ -1023,6 +1032,19 @@ defineExpose({
1023
1032
  font-size: 11px;
1024
1033
  opacity: 0.55;
1025
1034
  }
1035
+ .ai-history-loc {
1036
+ flex: none;
1037
+ max-width: 45%;
1038
+ padding: 0 5px;
1039
+ border-radius: 2px;
1040
+ background: rgba(64, 158, 255, 0.22);
1041
+ color: #3070af;
1042
+ font-size: 11px;
1043
+ line-height: 16px;
1044
+ overflow: hidden;
1045
+ white-space: nowrap;
1046
+ text-overflow: ellipsis;
1047
+ }
1026
1048
  .ai-history-text {
1027
1049
  flex: 1;
1028
1050
  overflow: hidden;
@@ -1068,9 +1090,6 @@ defineExpose({
1068
1090
  word-break: break-all;
1069
1091
  }
1070
1092
  }
1071
- /* 放大场景(window-form 的「放大」抽屉):编辑区撑满整个抽屉,AI 面板仍是 12px / 2 行会显得很局促,
1072
- 这里整体提一档字号、加两行输入高度、面板内边距与历史面板高度同步放大。
1073
- 选择器带 .is-ai-large 是为了权重高于上面 .is-light 里的同名规则,不依赖书写顺序。 */
1074
1093
  &.is-ai-large {
1075
1094
  .ai-prompt-panel,
1076
1095
  .ai-history-panel {
package/README.md CHANGED
@@ -13,6 +13,10 @@ Vue3 的 CodeMirror6 封装,在全屏 / 美化 / 全选 / 复制 / 清空 等
13
13
  </yh-codemirror>
14
14
  ```
15
15
 
16
+ `language` 支持 `javascript / sql / css / java / html / json`,可传数组;不在列表内会取不到语言插件。
17
+ `autocomplete` 只对 `javascript` 生效(按语言名取,如 `{javascript: myCompletionSource}`)。
18
+ 不传 `aiRequest` 就是一个纯编辑器,不会出现任何 AI 入口。
19
+
16
20
  ## AI 提示编辑
17
21
 
18
22
  组件本身**不内置任何 AI 服务**(保持零业务依赖),由宿主通过 `aiRequest` 注入请求实现。传入 `aiRequest` 即自动启用:
@@ -24,11 +28,15 @@ Vue3 的 CodeMirror6 封装,在全屏 / 美化 / 全选 / 复制 / 清空 等
24
28
  - 支持流式:调用 `onChunk` 逐段返回,编辑器实时写入;非流式则直接返回完整替换文本
25
29
  - 自动剥离模型常见的 ` ```lang ... ``` ` 包裹
26
30
  - 通过 dispatch 写入,保留 CodeMirror 撤销栈,可 `Ctrl+Z` 回退
27
- - **历史问答**:输入框右上角「历史」可查看本轮会话问过的问题与 AI 的完整回答(可展开、可复制、可清空)
31
+ - **历史问答**:输入框右上角「历史」可查看本轮会话问过的问题与 AI 的完整回答(可展开、可复制、可清空),
32
+ 每条前面会显示宿主传入的 `aiLocation`,建议格式「区域名称 · <按钮/查询条件/字段名称 + 类型> · 编辑器字段名」
33
+ (如「区域信息 · 事件按钮 · 点击事件」「区域信息 · 名称字段 · 字段值更改」;
34
+ 直接在区域事件里提问时没有中间层,就是「区域信息 · 数据初始化」)
28
35
 
29
36
  历史记录存在 `ai-history.js` 的模块级单例里(上限 50 条,最新在前)。之所以不放组件内:
30
37
  配置面板里的编辑器会随切换配置项反复卸载重建,组件内的 ref 一卸载就没了。
31
- 代价是同一页面所有编辑器共用一份(未做分组),所以每条都带时间与问题原文;需要按字段分组时再给 `pushAiHistory` 加 key 维度。
38
+ 代价是同一页面所有编辑器共用一份(未做分组),所以每条都带时间、问题原文和 `aiLocation`——
39
+ 没有归属信息时,一堆「把名称标红」的提问根本分不清是哪块配置。
32
40
 
33
41
  ### Props
34
42
 
@@ -40,6 +48,7 @@ Vue3 的 CodeMirror6 封装,在全屏 / 美化 / 全选 / 复制 / 清空 等
40
48
  | `aiScope` | `'document' \| 'cursor'` | `'document'` | 无选区时的作用范围 |
41
49
  | `aiPanelSize` | `'normal' \| 'large'` | `'normal'` | AI 输入框 / 历史面板的尺寸档位。编辑区被放大时(如 window-form 的「放大」抽屉)传 `large` |
42
50
  | `aiWindowId` | `string \| number` | `''` | AI 上下文用的「所属窗口 ID」。组件不认识任何业务接口,只在 AI 被唤起时通过 `ai-activate` 把它抛给宿主,由宿主去取窗口配置 |
51
+ | `aiLocation` | `string` | `''` | 这条提问来自哪里(建议「区域名 · 按钮/查询条件/字段名 + 类型 · 编辑器字段名」,如「区域信息 · 事件按钮 · 点击事件」,名称一律取 NAME)。组件只原样记进历史并显示在问题前,不解析其含义 |
43
52
 
44
53
  ### Events
45
54
 
package/ai-history.js CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * AI 问答历史(模块级单例)。
3
3
  *
4
- * 代价是同一页面里所有编辑器共用一份历史(未做分组),所以每条都带上时间与问题原文,
5
- * 靠问题内容本身区分是哪个字段的提问。需要按字段分组时再给 pushAiHistory 加 key 维度。
4
+ * 同一页面里所有编辑器共用一份历史(未做分组),所以每条都要带上:
5
+ * - location:这条提问来自哪里(宿主传入,如「区域名称 · 数据初始化」)
6
+ * - time / prompt:时间与问题原文。
6
7
  */
7
8
  import {ref} from 'vue';
8
9
 
@@ -10,7 +11,7 @@ import {ref} from 'vue';
10
11
  const MAX_HISTORY = 50;
11
12
 
12
13
  /**
13
- * @type {import('vue').Ref<Array<{id:number, time:string, prompt:string, scope:string, answer:string}>>}
14
+ * @type {import('vue').Ref<Array<{id:number, time:string, prompt:string, scope:string, location:string, answer:string}>>}
14
15
  * 最新的排在最前面
15
16
  */
16
17
  export const aiHistory = ref([]);
@@ -25,13 +26,13 @@ const nowText = () => {
25
26
  )}`;
26
27
  };
27
28
 
28
- /** 追加一条:{prompt, scope, answer} */
29
- export const pushAiHistory = ({prompt, scope = '', answer = ''}) => {
29
+ /** 追加一条:{prompt, scope, location, answer} */
30
+ export const pushAiHistory = ({prompt, scope = '', location = '', answer = ''}) => {
30
31
  if (!prompt && !answer) return;
31
- aiHistory.value = [{id: ++seed, time: nowText(), prompt, scope, answer}, ...aiHistory.value].slice(
32
- 0,
33
- MAX_HISTORY
34
- );
32
+ aiHistory.value = [
33
+ {id: ++seed, time: nowText(), prompt, scope, location, answer},
34
+ ...aiHistory.value,
35
+ ].slice(0, MAX_HISTORY);
35
36
  };
36
37
 
37
38
  export const clearAiHistory = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yh-codemirror",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "wraper codemirror with vue3",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,6 +17,7 @@
17
17
  "@codemirror/lang-css": "^6.2.1",
18
18
  "@codemirror/lang-java": "^6.0.1",
19
19
  "@codemirror/lang-javascript": "^6.2.2",
20
+ "@codemirror/lang-json": "^6.0.1",
20
21
  "@codemirror/lang-sql": "^6.7.0",
21
22
  "@codemirror/lang-html": "^6.4.9",
22
23
  "@codemirror/language": "^6.10.2",
package/util.ts CHANGED
@@ -3,6 +3,7 @@ import { java } from "@codemirror/lang-java";
3
3
  import { css } from "@codemirror/lang-css";
4
4
  import { sql } from "@codemirror/lang-sql";
5
5
  import { html } from "@codemirror/lang-html";
6
+ import { json } from "@codemirror/lang-json";
6
7
 
7
8
  const plugins = {
8
9
  javascript: () => javascript(),
@@ -10,6 +11,7 @@ const plugins = {
10
11
  css: () => css(),
11
12
  java: () => java(),
12
13
  html: () => html(),
14
+ json: () => json(),
13
15
  };
14
16
 
15
17
  /**
@@ -39,5 +41,6 @@ export type AiCodeEditRequest = (
39
41
  ) => Promise<string | void | {text?: string; code?: string; content?: string}>;
40
42
 
41
43
  export const getCodeLanguagePlugin = function (language) {
42
- return plugins[language]();
44
+ // 未知语言(含 undefined)回落 javascript:直接取会拿到 undefined,CM6 会抛 extension 错误
45
+ return (plugins[language] || plugins.javascript)();
43
46
  };