@8btc/mditor 0.0.8 → 0.0.10

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.
@@ -0,0 +1,148 @@
1
+ @selection-popover-radius: 8px;
2
+ @selection-popover-padding: 4px;
3
+ @selection-popover-gap: 0px;
4
+ @selection-popover-shadow: var(--panel-shadow);
5
+ @selection-popover-bg: var(--panel-background-color);
6
+ @selection-popover-color: var(--textarea-text-color);
7
+ @selection-popover-hover-color: var(--toolbar-icon-hover-color);
8
+
9
+ .vditor-selection-popover {
10
+ position: absolute;
11
+ z-index: 4;
12
+ display: none;
13
+ padding: 0;
14
+ border-radius: @selection-popover-radius;
15
+ background-color: @selection-popover-bg;
16
+ box-shadow: @selection-popover-shadow;
17
+ border: 1px solid var(--border-color);
18
+ user-select: none;
19
+ white-space: nowrap;
20
+ transform-origin: top left;
21
+ opacity: 0;
22
+ transition: opacity 150ms cubic-bezier(0.2, 0, 0.13, 1.5),
23
+ transform 150ms cubic-bezier(0.2, 0, 0.13, 1.5);
24
+ flex-direction: column;
25
+ flex-wrap: nowrap;
26
+
27
+ &__actions {
28
+ display: flex;
29
+ flex-direction: row;
30
+ align-items: center;
31
+ width: 100%;
32
+ border-bottom: 1px solid var(--border-color);
33
+ }
34
+
35
+ &__input {
36
+ display: flex;
37
+ flex-direction: column;
38
+ width: 327px;
39
+ padding: 12px;
40
+ gap: 8px;
41
+ align-items: flex-end;
42
+ box-sizing: border-box;
43
+
44
+ textarea {
45
+ flex: 1;
46
+ width: 100%;
47
+ height: 60px;
48
+ background: transparent;
49
+ border: none;
50
+ resize: none;
51
+ outline: none;
52
+ color: @selection-popover-color;
53
+ font-size: 14px;
54
+ line-height: 20px;
55
+ padding: 0;
56
+ margin: 0;
57
+
58
+ &::placeholder {
59
+ color: #585a5a;
60
+ }
61
+ }
62
+
63
+ .vditor-selection-popover__send {
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ width: 32px;
68
+ height: 32px;
69
+ border: none;
70
+ background: var(--toolbar-icon-hover-color);
71
+ border-radius: 8px;
72
+ color: #fff;
73
+ cursor: pointer;
74
+ padding: 0;
75
+ transition: opacity 0.2s;
76
+
77
+ &:hover {
78
+ opacity: 0.8;
79
+ }
80
+
81
+ &[disabled] {
82
+ opacity: 0.5;
83
+ cursor: not-allowed;
84
+ pointer-events: none;
85
+ }
86
+
87
+ &--loading {
88
+ pointer-events: none;
89
+ opacity: 0.8;
90
+
91
+ svg {
92
+ animation: vditor-rotate 1s linear infinite;
93
+ }
94
+ }
95
+
96
+ svg {
97
+ width: 16px;
98
+ height: 16px;
99
+ }
100
+ }
101
+ }
102
+
103
+ @keyframes vditor-rotate {
104
+ from {
105
+ transform: rotate(0deg);
106
+ }
107
+
108
+ to {
109
+ transform: rotate(360deg);
110
+ }
111
+ }
112
+
113
+ &__btn {
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ height: 32px;
118
+ width: 109px;
119
+ flex: 0 0 109px;
120
+ box-sizing: border-box;
121
+ white-space: nowrap;
122
+ margin: 0 @selection-popover-gap;
123
+ border: none;
124
+ background: transparent;
125
+ color: @selection-popover-color;
126
+ cursor: pointer;
127
+ outline: none;
128
+ font-size: 14px;
129
+ font-weight: 500;
130
+ transition: @transition;
131
+
132
+ &:hover,
133
+ &:focus {
134
+ color: @selection-popover-hover-color;
135
+ background-color: transparent;
136
+ }
137
+
138
+ &[disabled] {
139
+ opacity: 0.9;
140
+ cursor: not-allowed;
141
+ }
142
+ }
143
+
144
+ &__btn+&__btn {
145
+ border-left: 1px solid var(--border-color);
146
+
147
+ }
148
+ }
@@ -0,0 +1,38 @@
1
+ // Selection tag styles - 参考代码块样式
2
+ .vditor-reset .vditor-selection-tag {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ gap: 0.31rem;
6
+ padding: 0.25rem 0.5rem;
7
+ background-color: #f6f8fa;
8
+ border: 1px solid #d1d5da;
9
+ border-radius: 0.25rem;
10
+ font-size: 0.875rem;
11
+ line-height: 1.25rem;
12
+ color: @primaryColor;
13
+ font-family: @font-family-code;
14
+ margin: 0.25rem 0.25rem 0.25rem 0;
15
+
16
+ &__file {
17
+ color: @primaryColor;
18
+ font-weight: 500;
19
+ }
20
+
21
+ &__lines {
22
+ color: #586069;
23
+ }
24
+ }
25
+
26
+ // Dark mode
27
+ .vditor--dark .vditor-reset .vditor-selection-tag {
28
+ background-color: #2f363d;
29
+ border-color: #141414;
30
+
31
+ .vditor-selection-tag__file {
32
+ color: #fafafa;
33
+ }
34
+
35
+ .vditor-selection-tag__lines {
36
+ color: #b9b9b9;
37
+ }
38
+ }
@@ -88,6 +88,8 @@
88
88
  @import "_sv";
89
89
  @import "_line-number";
90
90
  @import "_math-menu";
91
+ @import "_selection-popover";
92
+ @import "_selection-tag";
91
93
 
92
94
  // details 默认样式调整:隐藏展开/折叠指示器
93
95
  details>summary {
package/src/index.ts CHANGED
@@ -203,8 +203,9 @@ class Vditor extends VditorMethod {
203
203
  * - 依赖配置 `lineNumber: true`,关闭时直接跳过以提升性能
204
204
  * @param immediate 为 true 时立即更新;默认为节流更新以避免频繁调用
205
205
  */
206
+ /** 手动更新行号 */
206
207
  public updateLineNumbers(immediate = false) {
207
- if (!this.vditor.options.lineNumber) {
208
+ if (!this.vditor.options.lineNumber?.enable) {
208
209
  return;
209
210
  }
210
211
  const text = getMarkdown(this.vditor);
@@ -222,6 +223,7 @@ class Vditor extends VditorMethod {
222
223
  * - 应用高亮样式并在约 500ms 后自动恢复
223
224
  * @param numbers 要高亮的行号数组
224
225
  */
226
+ /** 高亮指定行号,时长受 `options.lineNumber.highlightTimer` 控制 */
225
227
  public setLinehightNumbers(numbers: number[]) {
226
228
  if (!Array.isArray(numbers)) {
227
229
  return;
@@ -240,7 +242,10 @@ class Vditor extends VditorMethod {
240
242
  if (nodeList.length === 0) {
241
243
  return;
242
244
  }
243
- const duration = 1500;
245
+ const duration = Math.max(
246
+ 0,
247
+ Number(this.vditor.options.lineNumber?.highlightTimer) || 1500
248
+ );
244
249
  const apply = () => {
245
250
  nodeList.forEach((el) => {
246
251
  const old = lineHighlightTimers.get(el);
@@ -458,7 +463,7 @@ class Vditor extends VditorMethod {
458
463
  public setValue(markdown: string, clearStack = false) {
459
464
  if (this.vditor.currentMode === "sv") {
460
465
  this.vditor.sv.element.innerHTML = `<div data-block='0'>${this.vditor.lute.SpinVditorSVDOM(markdown)}</div>`;
461
- if (this.vditor.options.lineNumber) {
466
+ if (this.vditor.options.lineNumber?.enable) {
462
467
  try {
463
468
  attachLineNumbersToBlocks(
464
469
  this.vditor.sv.element,
@@ -485,7 +490,7 @@ class Vditor extends VditorMethod {
485
490
  .forEach((item: HTMLElement) => {
486
491
  processCodeRender(item, this.vditor);
487
492
  });
488
- if (this.vditor.options.lineNumber) {
493
+ if (this.vditor.options.lineNumber?.enable) {
489
494
  try {
490
495
  attachLineNumbersToBlocks(
491
496
  this.vditor.ir.element,
package/src/method.ts CHANGED
@@ -77,7 +77,7 @@ class Vditor {
77
77
  public static updateLineNumbers(instance: any, immediate = false) {
78
78
  try {
79
79
  const v = instance?.vditor ?? instance;
80
- if (!v?.options?.lineNumber) return;
80
+ if (!v?.options?.lineNumber?.enable) return;
81
81
  const text = getMarkdown(v);
82
82
  const root = v[v.currentMode].element as HTMLElement;
83
83
  if (immediate) {
@@ -33,7 +33,7 @@ class IR {
33
33
  constructor(vditor: IVditor) {
34
34
  const divElement = document.createElement("div");
35
35
  divElement.className = "vditor-ir";
36
- if (vditor.options.lineNumber) {
36
+ if (vditor.options.lineNumber?.enable) {
37
37
  divElement.classList.add("vditor--linenumber");
38
38
  }
39
39
 
@@ -105,7 +105,7 @@ export const processAfterRender = (
105
105
  }
106
106
 
107
107
  try {
108
- if (vditor.options.lineNumber) {
108
+ if (vditor.options.lineNumber?.enable) {
109
109
  attachLineNumbersToBlocksThrottled(vditor.ir.element, text);
110
110
  }
111
111
  } catch {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 渲染 selection 代码块为文件选择标签
3
+ */
4
+ export const selectionRender = (element: HTMLElement) => {
5
+ element.querySelectorAll("pre > code.language-selection").forEach((codeElement: HTMLElement) => {
6
+ const preElement = codeElement.parentElement as HTMLElement;
7
+ const content = codeElement.textContent.trim();
8
+ const lines = content.split('\n');
9
+
10
+ const tags = lines.map(line => {
11
+ const match = line.trim().match(/^(.+?)\s+(\d+-\d+)$/);
12
+ if (match) {
13
+ const [, filename, lineRange] = match;
14
+ return `<div class="vditor-selection-tag">
15
+ <span class="vditor-selection-tag__file">${filename}</span>
16
+ <span class="vditor-selection-tag__lines">${lineRange}</span>
17
+ </div>`;
18
+ }
19
+ return '';
20
+ }).filter(tag => tag).join('');
21
+
22
+ if (tags) {
23
+ const container = document.createElement('div');
24
+ container.innerHTML = tags;
25
+ preElement.parentNode.replaceChild(container, preElement);
26
+ }
27
+ });
28
+ };
@@ -12,11 +12,9 @@ import { SMILESRender } from "../markdown/SMILESRender";
12
12
  import { markmapRender } from "../markdown/markmapRender";
13
13
  import { mindmapRender } from "../markdown/mindmapRender";
14
14
  import { plantumlRender } from "../markdown/plantumlRender";
15
- import {
16
- hasClosestByClassName,
17
- hasClosestByMatchTag,
18
- } from "../util/hasClosest";
15
+ import { hasClosestByClassName, hasClosestByMatchTag } from "../util/hasClosest";
19
16
  import { setSelectionFocus } from "../util/selection";
17
+ import { selectionRender } from "../markdown/selectionRender";
20
18
  import { previewImage } from "./image";
21
19
 
22
20
  export class Preview {
@@ -247,8 +245,11 @@ export class Preview {
247
245
  if (vditor.options.preview.render.media.enable) {
248
246
  mediaRender(vditor.preview.previewElement);
249
247
  }
248
+ selectionRender(vditor.preview.previewElement);
250
249
  vditor.options.customRenders.forEach((item) => {
251
- item.render(vditor.preview.previewElement, vditor);
250
+ {
251
+ item.render(vditor.preview.previewElement, vditor);
252
+ }
252
253
  });
253
254
  // toc render
254
255
  const editorElement = vditor.preview.element;
@@ -24,7 +24,7 @@ class Editor {
24
24
  constructor(vditor: IVditor) {
25
25
  this.element = document.createElement("pre");
26
26
  this.element.className = "vditor-sv vditor-reset";
27
- if (vditor.options.lineNumber) {
27
+ if (vditor.options.lineNumber?.enable) {
28
28
  this.element.classList.add("vditor--linenumber");
29
29
  }
30
30
  this.element.setAttribute("placeholder", vditor.options.placeholder);
@@ -178,7 +178,7 @@ export const processAfterRender = (
178
178
  vditor.undo.addToUndoStack(vditor);
179
179
  }
180
180
  try {
181
- if (vditor.options.lineNumber) {
181
+ if (vditor.options.lineNumber?.enable) {
182
182
  attachLineNumbersToBlocksThrottled(vditor.sv.element, text);
183
183
  }
184
184
  } catch {
@@ -121,7 +121,8 @@ export const setPadding = (vditor: IVditor) => {
121
121
  vditor.options.preview.maxWidth) /
122
122
  2;
123
123
  const basePad = Math.max(minPadding, padding);
124
- const leftExtra = vditor.options.lineNumber && basePad < 40 ? 20 : 0;
124
+ const leftExtra =
125
+ vditor.options.lineNumber?.enable && basePad < 40 ? 20 : 0;
125
126
  vditor.wysiwyg.element.style.padding = `10px ${basePad}px 10px ${basePad + leftExtra}px`;
126
127
  }
127
128
 
@@ -131,7 +132,8 @@ export const setPadding = (vditor: IVditor) => {
131
132
  vditor.options.preview.maxWidth) /
132
133
  2;
133
134
  const basePad = Math.max(minPadding, padding);
134
- const leftExtra = vditor.options.lineNumber && basePad < 40 ? 20 : 0;
135
+ const leftExtra =
136
+ vditor.options.lineNumber?.enable && basePad < 40 ? 20 : 0;
135
137
  vditor.ir.element.style.padding = `10px ${basePad}px 10px ${basePad + leftExtra}px`;
136
138
  }
137
139
 
@@ -9,6 +9,10 @@ export class Options {
9
9
  inlinePopover: {
10
10
  enable: false,
11
11
  },
12
+ selectionPopover: {
13
+ enable: false,
14
+ debounceDelay: 150,
15
+ },
12
16
  cache: {
13
17
  enable: true,
14
18
  },
@@ -118,7 +122,10 @@ export class Options {
118
122
  pin: false,
119
123
  },
120
124
  typewriterMode: false,
121
- lineNumber: false,
125
+ lineNumber: {
126
+ enable: false,
127
+ highlightTimer: 1500,
128
+ },
122
129
  undoDelay: 800,
123
130
  upload: {
124
131
  extraData: {},