@lobehub/editor 1.17.1 → 1.18.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.
@@ -4,3 +4,4 @@ export { default as ReactMarkdownPlugin } from './react';
4
4
  export type { MARKDOWN_READER_LEVEL } from './service/shortcut';
5
5
  export { IMarkdownShortCutService, MARKDOWN_READER_LEVEL_HIGH, MARKDOWN_READER_LEVEL_NORMAL, MARKDOWN_WRITER_LEVEL_MAX, } from './service/shortcut';
6
6
  export { isPunctuationChar } from './utils';
7
+ export { detectCodeLanguage, detectLanguage } from './utils/detectLanguage';
@@ -2,4 +2,5 @@ export { INSERT_MARKDOWN_COMMAND } from "./command";
2
2
  export { MarkdownPlugin } from "./plugin";
3
3
  export { default as ReactMarkdownPlugin } from "./react";
4
4
  export { IMarkdownShortCutService, MARKDOWN_READER_LEVEL_HIGH, MARKDOWN_READER_LEVEL_NORMAL, MARKDOWN_WRITER_LEVEL_MAX } from "./service/shortcut";
5
- export { isPunctuationChar } from "./utils";
5
+ export { isPunctuationChar } from "./utils";
6
+ export { detectCodeLanguage, detectLanguage } from "./utils/detectLanguage";
@@ -13,7 +13,7 @@ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.g
13
13
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
14
14
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
15
15
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
16
- import { $isCodeNode } from '@lexical/code';
16
+ import { $createCodeNode, $isCodeNode } from '@lexical/code';
17
17
  import { $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, HISTORIC_TAG, KEY_ENTER_COMMAND, PASTE_COMMAND } from 'lexical';
18
18
  import { KernelPlugin } from "../../../editor-kernel/plugin";
19
19
  import { createDebugLogger } from "../../../utils/debug";
@@ -21,6 +21,7 @@ import { registerMarkdownCommand } from "../command";
21
21
  import MarkdownDataSource from "../data-source/markdown-data-source";
22
22
  import { IMarkdownShortCutService, MarkdownShortCutService } from "../service/shortcut";
23
23
  import { canContainTransformableMarkdown } from "../utils";
24
+ import { detectCodeLanguage, detectLanguage } from "../utils/detectLanguage";
24
25
  export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
25
26
  _inherits(MarkdownPlugin, _KernelPlugin);
26
27
  var _super = _createSuper(MarkdownPlugin);
@@ -130,32 +131,64 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
130
131
 
131
132
  // If there's no text content, let Lexical handle it
132
133
  if (!text) return false;
133
-
134
- // Check if markdown paste formatting is enabled (default: true)
135
- var enablePasteMarkdown = (_this2$config$enableP = (_this2$config = _this2.config) === null || _this2$config === void 0 ? void 0 : _this2$config.enablePasteMarkdown) !== null && _this2$config$enableP !== void 0 ? _this2$config$enableP : true;
136
134
  _this2.logger.debug('paste content analysis:', {
137
- enablePasteMarkdown: enablePasteMarkdown,
135
+ clipboardTypes: Array.from(clipboardData.types || []),
138
136
  hasHTML: !!(html && html.trim()),
139
- text: text.slice(0, 100) + (text.length > 100 ? '...' : '')
140
- });
141
-
142
- // Always force plain text paste first (prevents HTML formatting issues)
143
- event.preventDefault();
144
- event.stopPropagation();
145
- editor.update(function () {
146
- var selection = $getSelection();
147
- if (!$isRangeSelection(selection)) return;
148
-
149
- // Insert plain text
150
- selection.insertText(text);
137
+ htmlLength: (html === null || html === void 0 ? void 0 : html.length) || 0,
138
+ textLength: text.length
151
139
  });
140
+ var enablePasteMarkdown = (_this2$config$enableP = (_this2$config = _this2.config) === null || _this2$config === void 0 ? void 0 : _this2$config.enablePasteMarkdown) !== null && _this2$config$enableP !== void 0 ? _this2$config$enableP : true;
152
141
 
153
142
  // If markdown formatting is disabled, we're done
154
143
  if (!enablePasteMarkdown) {
144
+ // Force plain text paste for external content
145
+ event.preventDefault();
146
+ event.stopPropagation();
147
+ editor.update(function () {
148
+ var selection = $getSelection();
149
+ if (!$isRangeSelection(selection)) return;
150
+
151
+ // Insert plain text
152
+ selection.insertText(text);
153
+ });
155
154
  _this2.logger.debug('markdown formatting disabled, plain text inserted');
156
155
  return true;
157
156
  }
158
157
 
158
+ // Check if this is likely a rich-text paste from the editor or other rich editor
159
+ // Rich text pastes typically have HTML that's more complex than just wrapping text
160
+ if (clipboardData.types.includes('application/x-lexical-editor')) {
161
+ _this2.logger.debug('rich content detected, letting Lexical handle paste');
162
+ return false;
163
+ }
164
+
165
+ // Check if markdown paste formatting is enabled (default: true)
166
+
167
+ // Check if content is code (JSON, SQL, etc.) and should be inserted as code block
168
+ var codeInfo = _this2.detectCodeContent(text);
169
+ if (codeInfo) {
170
+ // Code detected - insert as code block
171
+ _this2.logger.debug("code detected (".concat(codeInfo.language, "), inserting as code block"));
172
+ event.preventDefault();
173
+ event.stopPropagation();
174
+ editor.update(function () {
175
+ var selection = $getSelection();
176
+ if (!$isRangeSelection(selection)) return;
177
+
178
+ // Create code block node with detected language
179
+ var codeNode = $createCodeNode(codeInfo.language);
180
+ selection.insertNodes([codeNode]);
181
+
182
+ // Insert the code text into the code block
183
+ codeNode.select();
184
+ var codeSelection = $getSelection();
185
+ if ($isRangeSelection(codeSelection)) {
186
+ codeSelection.insertText(text);
187
+ }
188
+ });
189
+ return true; // Command handled
190
+ }
191
+
159
192
  // Check if the pasted plain text contains markdown patterns
160
193
  var hasMarkdownContent = _this2.detectMarkdownContent(text);
161
194
  if (hasMarkdownContent) {
@@ -171,52 +204,87 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
171
204
  // No markdown detected - plain text is already inserted
172
205
  _this2.logger.debug('no markdown patterns detected, keeping as plain text');
173
206
  }
207
+ if (clipboardData.types.includes('text/html') && clipboardData.types.includes('text/rtf')) {
208
+ // Code detected - insert as code block
209
+ _this2.logger.debug("code like, inserting as code block");
210
+ event.preventDefault();
211
+ event.stopPropagation();
212
+ editor.update(function () {
213
+ var selection = $getSelection();
214
+ if (!$isRangeSelection(selection)) return;
215
+
216
+ // Create code block node with detected language
217
+ var codeNode = $createCodeNode('plaintext');
218
+ selection.insertNodes([codeNode]);
219
+
220
+ // Insert the code text into the code block
221
+ codeNode.select();
222
+ var codeSelection = $getSelection();
223
+ if ($isRangeSelection(codeSelection)) {
224
+ codeSelection.insertText(text);
225
+ }
226
+ });
227
+ return true; // Command handled
228
+ }
229
+
230
+ // Force plain text paste for external content
231
+ event.preventDefault();
232
+ event.stopPropagation();
233
+ editor.update(function () {
234
+ var selection = $getSelection();
235
+ if (!$isRangeSelection(selection)) return;
236
+
237
+ // Insert plain text
238
+ selection.insertText(text);
239
+ });
174
240
  return true; // Command handled
175
241
  }, COMMAND_PRIORITY_CRITICAL));
176
242
  this.register(registerMarkdownCommand(editor, this.service));
177
243
  }
178
244
 
179
245
  /**
180
- * Detect if text contains markdown patterns
181
- * Returns false if content is likely code (JSON, HTML, SQL, etc.)
246
+ * Detect if content is code and should be inserted as code block
247
+ * Uses advanced language detection with pattern matching
248
+ * Excludes markdown as it should be handled by markdown formatting dialog
182
249
  */
183
250
  }, {
184
- key: "detectMarkdownContent",
185
- value: function detectMarkdownContent(text) {
186
- var trimmed = text.trim();
187
-
188
- // Check if content is JSON
189
- if (trimmed.startsWith('{') && trimmed.endsWith('}') || trimmed.startsWith('[') && trimmed.endsWith(']')) {
190
- try {
191
- JSON.parse(trimmed);
192
- this.logger.debug('content is valid JSON, not treating as markdown');
193
- return false;
194
- } catch (_unused) {
195
- // Not valid JSON, continue checking
251
+ key: "detectCodeContent",
252
+ value: function detectCodeContent(text) {
253
+ // Use the advanced language detector
254
+ var detected = detectLanguage(text);
255
+ if (detected && detected.confidence > 50) {
256
+ // Don't insert markdown as code block - it should trigger the formatting dialog
257
+ if (detected.language === 'markdown') {
258
+ return null;
196
259
  }
260
+ this.logger.debug('language detected:', detected);
261
+ return detected;
197
262
  }
198
263
 
199
- // Check if content has significant HTML structure
200
- var htmlTagPattern = /<[a-z][\S\s]*?>/gi;
201
- var htmlMatches = text.match(htmlTagPattern);
202
- if (htmlMatches && htmlMatches.length > 5) {
203
- // More than 5 HTML tags suggests this is HTML content, not markdown
204
- this.logger.debug('content has significant HTML structure, not treating as markdown');
205
- return false;
264
+ // Fallback to fast detection for common formats
265
+ var fastDetected = detectCodeLanguage(text);
266
+ if (fastDetected) {
267
+ // Don't insert markdown as code block
268
+ if (fastDetected === 'markdown') {
269
+ return null;
270
+ }
271
+ return {
272
+ confidence: 80,
273
+ language: fastDetected
274
+ };
206
275
  }
276
+ return null;
277
+ }
207
278
 
208
- // Check if content looks like code (SQL, XML, etc.)
209
- // Common patterns: SQL keywords, XML declarations, file paths
210
- var codePatterns = [/^\s*(select|insert|update|delete|create|alter|drop)\s+/im,
211
- // SQL
212
- /^\s*<\?xml/i,
213
- // XML declaration
214
- /^[a-z]:\\|^\/[a-z]/im // File paths (Windows/Unix)
215
- ];
216
- if (codePatterns.some(function (pattern) {
217
- return pattern.test(text);
218
- })) {
219
- this.logger.debug('content looks like code, not treating as markdown');
279
+ /**
280
+ * Detect if text contains markdown patterns
281
+ * Returns false if content is likely code (will be handled by detectCodeContent)
282
+ */
283
+ }, {
284
+ key: "detectMarkdownContent",
285
+ value: function detectMarkdownContent(text) {
286
+ // If code is detected, don't treat as markdown
287
+ if (this.detectCodeContent(text)) {
220
288
  return false;
221
289
  }
222
290
  var markdownPatterns = [
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Language detection utility for code snippets
3
+ * Uses pattern matching and heuristics to identify programming languages
4
+ */
5
+ interface LanguageDetectionResult {
6
+ confidence: number;
7
+ language: string;
8
+ }
9
+ /**
10
+ * Detect the programming language of a code snippet
11
+ * @param code - The code snippet to analyze
12
+ * @returns Language detection result with confidence score
13
+ */
14
+ export declare function detectLanguage(code: string): LanguageDetectionResult | null;
15
+ /**
16
+ * Simple detection for common formats with high confidence
17
+ * Falls back to detectLanguage for more complex detection
18
+ */
19
+ export declare function detectCodeLanguage(code: string): string | null;
20
+ export {};
@@ -0,0 +1,267 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
5
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
+ /**
8
+ * Language detection utility for code snippets
9
+ * Uses pattern matching and heuristics to identify programming languages
10
+ */
11
+
12
+ var LANGUAGE_PATTERNS = {
13
+ // Shell/Config
14
+ bash: {
15
+ optional: [/\|\||&&/, /echo\s+/, /if\s+\[/],
16
+ required: [/^#!\/bin\/(ba)?sh/, /\${?\w+}?/],
17
+ weight: 7
18
+ },
19
+ csharp: {
20
+ optional: [/\[.*]/, /async\s+Task/, /var\s+\w+\s*=/],
21
+ required: [/(public|private|protected)\s+(class|interface|namespace)/, /using\s+\w+/],
22
+ weight: 8
23
+ },
24
+ css: {
25
+ optional: [/@media|@import|@keyframes/, /!important/],
26
+ required: [/[#.]?[\w-]+\s*{/, /:\s*[\w#%-]+;/],
27
+ weight: 8
28
+ },
29
+ dockerfile: {
30
+ required: [/^FROM\s+/, /^(RUN|CMD|COPY|ADD|WORKDIR|ENV|EXPOSE)\s+/m],
31
+ weight: 10
32
+ },
33
+ go: {
34
+ optional: [/import\s+\(/, /:=/, /defer|goroutine/],
35
+ required: [/package\s+\w+/, /func\s+\w+/],
36
+ weight: 8
37
+ },
38
+ graphql: {
39
+ optional: [/fragment\s+/, /on\s+\w+/],
40
+ required: [/(query|mutation|subscription)\s+/, /{[\S\s]*}/],
41
+ weight: 8
42
+ },
43
+ // Web languages
44
+ html: {
45
+ optional: [/<\/\w+>/, /class=|id=/],
46
+ required: [/<(html|head|body|div|span|p|a|img|script|style|link|meta)/i],
47
+ weight: 9
48
+ },
49
+ ini: {
50
+ exclude: [/^\s*</, /{.*}/],
51
+ required: [/^\[.*]$/m, /\w+\s*=\s*/],
52
+ weight: 6
53
+ },
54
+ java: {
55
+ optional: [/@Override|@Autowired/, /extends|implements/, /System\.out/],
56
+ required: [/(public|private|protected)\s+(class|interface|static)/, /\w+\s+\w+\s*\([^)]*\)\s*{/],
57
+ weight: 8
58
+ },
59
+ // JavaScript family
60
+ javascript: {
61
+ exclude: [/^\s*</, /interface\s+\w+/, /:\s*\w+\s*[;=]/],
62
+ optional: [/=>\s*{/, /console\.(log|error|warn)/, /\.then\(/, /require\(/],
63
+ required: [/(function|const|let|var|class|import|export|async|await)\s/],
64
+ weight: 7
65
+ },
66
+ // Data formats
67
+ json: {
68
+ optional: [/"[^"]*"\s*:/, /:\s*["'[{]/],
69
+ required: [/^\s*[[{]/, /[\]}]\s*$/],
70
+ weight: 10
71
+ },
72
+ jsx: {
73
+ exclude: [/^\s*<!DOCTYPE/, /^\s*<html/],
74
+ optional: [/className=/, /{.*}/, /import.*from/],
75
+ required: [/<\w+[^>]*>/, /(const|function|class)\s+\w+/],
76
+ weight: 8
77
+ },
78
+ makefile: {
79
+ optional: [/\$\(.*\)/, /\.PHONY/],
80
+ required: [/^[\w-]+:\s*/, /^\t/m],
81
+ weight: 7
82
+ },
83
+ // Other common languages
84
+ markdown: {
85
+ optional: [/```/, /\*\*.*\*\*/, /^\s*[*+-]\s+/m],
86
+ required: [/^#{1,6}\s+/, /\[.*]\(.*\)/],
87
+ weight: 6
88
+ },
89
+ php: {
90
+ optional: [/function\s+\w+/, /class\s+\w+/, /echo|print/],
91
+ required: [/<\?php/, /\$\w+/],
92
+ weight: 10
93
+ },
94
+ powershell: {
95
+ optional: [/\|\s*Where/, /\|\s*Select/, /param\(/],
96
+ required: [/\$\w+/, /(Get|Set|New|Remove)-\w+/],
97
+ weight: 8
98
+ },
99
+ // Backend languages
100
+ python: {
101
+ optional: [/if __name__|print\(/, /self\.|lambda/, /@\w+\s*$/m],
102
+ required: [/(def|class|import|from)\s+\w+/, /:\s*$/m],
103
+ weight: 8
104
+ },
105
+ ruby: {
106
+ optional: [/do\s*\|.*\|/, /puts|require/, /:\w+\s*=>/],
107
+ required: [/(def|class|module|end)\s/, /@\w+/],
108
+ weight: 7
109
+ },
110
+ rust: {
111
+ optional: [/::\w+/, /&mut|&str/, /#\[derive\(/],
112
+ required: [/(fn|struct|impl|trait|use)\s+/, /let\s+(mut\s+)?\w+/],
113
+ weight: 8
114
+ },
115
+ scss: {
116
+ optional: [/@mixin|@include|@extend/, /#{.*}/],
117
+ required: [/\$[\w-]+\s*:/, /[&@]\w+/],
118
+ weight: 8
119
+ },
120
+ // Database
121
+ sql: {
122
+ optional: [/from|where|join|group by|order by/i, /\*/],
123
+ required: [/(select|insert|update|delete|create|alter|drop)\s+/i],
124
+ weight: 9
125
+ },
126
+ // Config formats
127
+ toml: {
128
+ optional: [/\[\[.*]]/, /"""[\S\s]*"""/],
129
+ required: [/^\[.*]$/m, /\w+\s*=\s*[\w"']/],
130
+ weight: 8
131
+ },
132
+ tsx: {
133
+ optional: [/:\s*React\./, /:\s*FC</, /useState|useEffect/],
134
+ required: [/<\w+[^>]*>/, /interface|type\s+\w+/],
135
+ weight: 9
136
+ },
137
+ typescript: {
138
+ optional: [/<\w+>/, /as\s+\w+/, /implements|extends/],
139
+ required: [/(interface|type|enum)\s+\w+/, /:\s*\w+(\[]|<.+>)?\s*[);=]/],
140
+ weight: 8
141
+ },
142
+ xml: {
143
+ optional: [/<\/\w+>/, /xmlns/],
144
+ required: [/<\?xml|<\w+[^>]*>/],
145
+ weight: 9
146
+ },
147
+ yaml: {
148
+ exclude: [/^\s*[[\]{]/],
149
+ required: [/^[\s-]*\w+:\s*/, /^[\s-]*-\s+/m],
150
+ weight: 8
151
+ }
152
+ };
153
+
154
+ /**
155
+ * Detect the programming language of a code snippet
156
+ * @param code - The code snippet to analyze
157
+ * @returns Language detection result with confidence score
158
+ */
159
+ export function detectLanguage(code) {
160
+ if (!code || code.trim().length === 0) {
161
+ return null;
162
+ }
163
+ var trimmed = code.trim();
164
+ var scores = {};
165
+
166
+ // Test each language pattern
167
+ for (var _i = 0, _Object$entries = Object.entries(LANGUAGE_PATTERNS); _i < _Object$entries.length; _i++) {
168
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
169
+ language = _Object$entries$_i[0],
170
+ pattern = _Object$entries$_i[1];
171
+ var score = 0;
172
+ var baseWeight = pattern.weight || 5;
173
+
174
+ // Check exclude patterns first
175
+ if (pattern.exclude) {
176
+ var hasExclude = pattern.exclude.some(function (regex) {
177
+ return regex.test(trimmed);
178
+ });
179
+ if (hasExclude) {
180
+ continue; // Skip this language
181
+ }
182
+ }
183
+
184
+ // Check required patterns
185
+ if (pattern.required) {
186
+ var allRequired = pattern.required.every(function (regex) {
187
+ return regex.test(trimmed);
188
+ });
189
+ if (!allRequired) {
190
+ continue; // All required patterns must match
191
+ }
192
+ score += baseWeight * 2; // Strong indicator
193
+ }
194
+
195
+ // Check optional patterns
196
+ if (pattern.optional) {
197
+ var matchedOptional = pattern.optional.filter(function (regex) {
198
+ return regex.test(trimmed);
199
+ }).length;
200
+ score += matchedOptional * baseWeight;
201
+ }
202
+ if (score > 0) {
203
+ scores[language] = score;
204
+ }
205
+ }
206
+
207
+ // Find language with highest score
208
+ var entries = Object.entries(scores);
209
+ if (entries.length === 0) {
210
+ return null;
211
+ }
212
+ entries.sort(function (a, b) {
213
+ return b[1] - a[1];
214
+ });
215
+ var _entries$ = _slicedToArray(entries[0], 2),
216
+ topLanguage = _entries$[0],
217
+ topScore = _entries$[1];
218
+
219
+ // Calculate confidence (0-100)
220
+ var maxPossibleScore = (LANGUAGE_PATTERNS[topLanguage].weight || 5) * 5;
221
+ var confidence = Math.min(100, Math.round(topScore / maxPossibleScore * 100));
222
+
223
+ // Only return if confidence is high enough
224
+ if (confidence < 30) {
225
+ return null;
226
+ }
227
+ return {
228
+ confidence: confidence,
229
+ language: topLanguage
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Simple detection for common formats with high confidence
235
+ * Falls back to detectLanguage for more complex detection
236
+ */
237
+ export function detectCodeLanguage(code) {
238
+ var trimmed = code.trim();
239
+
240
+ // Fast path for JSON
241
+ if (trimmed.startsWith('{') && trimmed.endsWith('}') || trimmed.startsWith('[') && trimmed.endsWith(']')) {
242
+ try {
243
+ JSON.parse(trimmed);
244
+ return 'json';
245
+ } catch (_unused) {
246
+ // Not valid JSON
247
+ }
248
+ }
249
+
250
+ // Fast path for common patterns
251
+ if (/^\s*(select|insert|update|delete|create|alter|drop)\s+/i.test(code)) {
252
+ return 'sql';
253
+ }
254
+ if (/^<\?xml/i.test(code)) {
255
+ return 'xml';
256
+ }
257
+ if (/^FROM\s+|^RUN\s+|^CMD\s+/m.test(code)) {
258
+ return 'dockerfile';
259
+ }
260
+ if (/<\?php/.test(code)) {
261
+ return 'php';
262
+ }
263
+
264
+ // Use pattern matching for complex detection
265
+ var result = detectLanguage(code);
266
+ return result && result.confidence > 50 ? result.language : null;
267
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/editor",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "description": "A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.",
5
5
  "keywords": [
6
6
  "lobehub",