@lionrapid/core 0.1.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 (302) hide show
  1. package/README.md +106 -0
  2. package/dist/base/event-emitter.d.ts +91 -0
  3. package/dist/base/event-emitter.d.ts.map +1 -0
  4. package/dist/base/event-emitter.js +330 -0
  5. package/dist/base/event-emitter.js.map +1 -0
  6. package/dist/base/index.d.ts +5 -0
  7. package/dist/base/index.d.ts.map +1 -0
  8. package/dist/base/index.js +5 -0
  9. package/dist/base/index.js.map +1 -0
  10. package/dist/browser/auto-init.d.ts +57 -0
  11. package/dist/browser/auto-init.d.ts.map +1 -0
  12. package/dist/browser/auto-init.js +304 -0
  13. package/dist/browser/auto-init.js.map +1 -0
  14. package/dist/browser/auto-translator.d.ts +98 -0
  15. package/dist/browser/auto-translator.d.ts.map +1 -0
  16. package/dist/browser/auto-translator.js +218 -0
  17. package/dist/browser/auto-translator.js.map +1 -0
  18. package/dist/browser/block-extractor.d.ts +26 -0
  19. package/dist/browser/block-extractor.d.ts.map +1 -0
  20. package/dist/browser/block-extractor.js +143 -0
  21. package/dist/browser/block-extractor.js.map +1 -0
  22. package/dist/browser/dom-observer.d.ts +42 -0
  23. package/dist/browser/dom-observer.d.ts.map +1 -0
  24. package/dist/browser/dom-observer.js +109 -0
  25. package/dist/browser/dom-observer.js.map +1 -0
  26. package/dist/browser/editor/editor-api.d.ts +67 -0
  27. package/dist/browser/editor/editor-api.d.ts.map +1 -0
  28. package/dist/browser/editor/editor-api.js +233 -0
  29. package/dist/browser/editor/editor-api.js.map +1 -0
  30. package/dist/browser/editor/editor-init.d.ts +11 -0
  31. package/dist/browser/editor/editor-init.d.ts.map +1 -0
  32. package/dist/browser/editor/editor-init.js +181 -0
  33. package/dist/browser/editor/editor-init.js.map +1 -0
  34. package/dist/browser/editor/editor-styles.d.ts +6 -0
  35. package/dist/browser/editor/editor-styles.d.ts.map +1 -0
  36. package/dist/browser/editor/editor-styles.js +419 -0
  37. package/dist/browser/editor/editor-styles.js.map +1 -0
  38. package/dist/browser/editor/inspector.d.ts +22 -0
  39. package/dist/browser/editor/inspector.d.ts.map +1 -0
  40. package/dist/browser/editor/inspector.js +125 -0
  41. package/dist/browser/editor/inspector.js.map +1 -0
  42. package/dist/browser/editor/key-resolver.d.ts +19 -0
  43. package/dist/browser/editor/key-resolver.d.ts.map +1 -0
  44. package/dist/browser/editor/key-resolver.js +49 -0
  45. package/dist/browser/editor/key-resolver.js.map +1 -0
  46. package/dist/browser/editor/live-preview.d.ts +10 -0
  47. package/dist/browser/editor/live-preview.d.ts.map +1 -0
  48. package/dist/browser/editor/live-preview.js +57 -0
  49. package/dist/browser/editor/live-preview.js.map +1 -0
  50. package/dist/browser/editor/sidebar.d.ts +50 -0
  51. package/dist/browser/editor/sidebar.d.ts.map +1 -0
  52. package/dist/browser/editor/sidebar.js +353 -0
  53. package/dist/browser/editor/sidebar.js.map +1 -0
  54. package/dist/browser/editor/toolbar.d.ts +29 -0
  55. package/dist/browser/editor/toolbar.d.ts.map +1 -0
  56. package/dist/browser/editor/toolbar.js +131 -0
  57. package/dist/browser/editor/toolbar.js.map +1 -0
  58. package/dist/browser/exclude-filter.d.ts +21 -0
  59. package/dist/browser/exclude-filter.d.ts.map +1 -0
  60. package/dist/browser/exclude-filter.js +51 -0
  61. package/dist/browser/exclude-filter.js.map +1 -0
  62. package/dist/browser/index.d.ts +32 -0
  63. package/dist/browser/index.d.ts.map +1 -0
  64. package/dist/browser/index.js +28 -0
  65. package/dist/browser/index.js.map +1 -0
  66. package/dist/browser/language-switcher.d.ts +70 -0
  67. package/dist/browser/language-switcher.d.ts.map +1 -0
  68. package/dist/browser/language-switcher.js +339 -0
  69. package/dist/browser/language-switcher.js.map +1 -0
  70. package/dist/browser/lionrapid.auto.dev.js +5757 -0
  71. package/dist/browser/lionrapid.auto.dev.js.map +7 -0
  72. package/dist/browser/lionrapid.auto.js +5757 -0
  73. package/dist/browser/lionrapid.auto.js.map +7 -0
  74. package/dist/browser/lionrapid.auto.min.js +80 -0
  75. package/dist/browser/lionrapid.auto.min.js.map +7 -0
  76. package/dist/browser/lionrapid.editor.js +1869 -0
  77. package/dist/browser/lionrapid.editor.js.map +7 -0
  78. package/dist/browser/lionrapid.editor.min.js +415 -0
  79. package/dist/browser/lionrapid.editor.min.js.map +7 -0
  80. package/dist/browser/lionrapid.esm.js +4786 -0
  81. package/dist/browser/lionrapid.esm.js.map +7 -0
  82. package/dist/browser/lionrapid.esm.min.js +3 -0
  83. package/dist/browser/lionrapid.esm.min.js.map +7 -0
  84. package/dist/browser/lionrapid.iife.dev.js +4809 -0
  85. package/dist/browser/lionrapid.iife.dev.js.map +7 -0
  86. package/dist/browser/lionrapid.iife.js +4809 -0
  87. package/dist/browser/lionrapid.iife.js.map +7 -0
  88. package/dist/browser/lionrapid.iife.min.js +3 -0
  89. package/dist/browser/lionrapid.iife.min.js.map +7 -0
  90. package/dist/browser/lionrapid.umd.js +4822 -0
  91. package/dist/browser/lionrapid.umd.js.map +7 -0
  92. package/dist/browser/lionrapid.umd.min.js +18 -0
  93. package/dist/browser/lionrapid.umd.min.js.map +7 -0
  94. package/dist/browser/markup-parser.d.ts +13 -0
  95. package/dist/browser/markup-parser.d.ts.map +1 -0
  96. package/dist/browser/markup-parser.js +56 -0
  97. package/dist/browser/markup-parser.js.map +1 -0
  98. package/dist/browser/md5.d.ts +16 -0
  99. package/dist/browser/md5.d.ts.map +1 -0
  100. package/dist/browser/md5.js +145 -0
  101. package/dist/browser/md5.js.map +1 -0
  102. package/dist/browser/storage/cache-storage-repository.d.ts +44 -0
  103. package/dist/browser/storage/cache-storage-repository.d.ts.map +1 -0
  104. package/dist/browser/storage/cache-storage-repository.js +186 -0
  105. package/dist/browser/storage/cache-storage-repository.js.map +1 -0
  106. package/dist/browser/storage/localstorage-repository.d.ts +51 -0
  107. package/dist/browser/storage/localstorage-repository.d.ts.map +1 -0
  108. package/dist/browser/storage/localstorage-repository.js +270 -0
  109. package/dist/browser/storage/localstorage-repository.js.map +1 -0
  110. package/dist/builder.d.ts +24 -0
  111. package/dist/builder.d.ts.map +1 -0
  112. package/dist/builder.js +169 -0
  113. package/dist/builder.js.map +1 -0
  114. package/dist/cache/CacheManager.d.ts +132 -0
  115. package/dist/cache/CacheManager.d.ts.map +1 -0
  116. package/dist/cache/CacheManager.js +277 -0
  117. package/dist/cache/CacheManager.js.map +1 -0
  118. package/dist/cache/index.d.ts +6 -0
  119. package/dist/cache/index.d.ts.map +1 -0
  120. package/dist/cache/index.js +5 -0
  121. package/dist/cache/index.js.map +1 -0
  122. package/dist/config/index.d.ts +6 -0
  123. package/dist/config/index.d.ts.map +1 -0
  124. package/dist/config/index.js +6 -0
  125. package/dist/config/index.js.map +1 -0
  126. package/dist/config/lionrapid.config.d.ts +12 -0
  127. package/dist/config/lionrapid.config.d.ts.map +1 -0
  128. package/dist/config/lionrapid.config.js +2 -0
  129. package/dist/config/lionrapid.config.js.map +1 -0
  130. package/dist/config/logger.config.d.ts +19 -0
  131. package/dist/config/logger.config.d.ts.map +1 -0
  132. package/dist/config/logger.config.js +9 -0
  133. package/dist/config/logger.config.js.map +1 -0
  134. package/dist/config/repository.config.d.ts +87 -0
  135. package/dist/config/repository.config.d.ts.map +1 -0
  136. package/dist/config/repository.config.js +5 -0
  137. package/dist/config/repository.config.js.map +1 -0
  138. package/dist/content/index.d.ts +20 -0
  139. package/dist/content/index.d.ts.map +1 -0
  140. package/dist/content/index.js +13 -0
  141. package/dist/content/index.js.map +1 -0
  142. package/dist/content/parser/IParser.d.ts +33 -0
  143. package/dist/content/parser/IParser.d.ts.map +1 -0
  144. package/dist/content/parser/IParser.js +6 -0
  145. package/dist/content/parser/IParser.js.map +1 -0
  146. package/dist/content/parser/MarkupParser.d.ts +67 -0
  147. package/dist/content/parser/MarkupParser.d.ts.map +1 -0
  148. package/dist/content/parser/MarkupParser.js +231 -0
  149. package/dist/content/parser/MarkupParser.js.map +1 -0
  150. package/dist/content/serializer/HTMLSerializer.d.ts +61 -0
  151. package/dist/content/serializer/HTMLSerializer.d.ts.map +1 -0
  152. package/dist/content/serializer/HTMLSerializer.js +100 -0
  153. package/dist/content/serializer/HTMLSerializer.js.map +1 -0
  154. package/dist/content/serializer/ISerializer.d.ts +33 -0
  155. package/dist/content/serializer/ISerializer.d.ts.map +1 -0
  156. package/dist/content/serializer/ISerializer.js +6 -0
  157. package/dist/content/serializer/ISerializer.js.map +1 -0
  158. package/dist/content/style-mapper/DefaultStyleMapper.d.ts +59 -0
  159. package/dist/content/style-mapper/DefaultStyleMapper.d.ts.map +1 -0
  160. package/dist/content/style-mapper/DefaultStyleMapper.js +128 -0
  161. package/dist/content/style-mapper/DefaultStyleMapper.js.map +1 -0
  162. package/dist/content/style-mapper/IStyleMapper.d.ts +50 -0
  163. package/dist/content/style-mapper/IStyleMapper.d.ts.map +1 -0
  164. package/dist/content/style-mapper/IStyleMapper.js +6 -0
  165. package/dist/content/style-mapper/IStyleMapper.js.map +1 -0
  166. package/dist/content/types.d.ts +39 -0
  167. package/dist/content/types.d.ts.map +1 -0
  168. package/dist/content/types.js +6 -0
  169. package/dist/content/types.js.map +1 -0
  170. package/dist/content/validator/ContentValidator.d.ts +57 -0
  171. package/dist/content/validator/ContentValidator.d.ts.map +1 -0
  172. package/dist/content/validator/ContentValidator.js +215 -0
  173. package/dist/content/validator/ContentValidator.js.map +1 -0
  174. package/dist/content/validator/IValidator.d.ts +82 -0
  175. package/dist/content/validator/IValidator.d.ts.map +1 -0
  176. package/dist/content/validator/IValidator.js +6 -0
  177. package/dist/content/validator/IValidator.js.map +1 -0
  178. package/dist/errors/translation-errors.d.ts +55 -0
  179. package/dist/errors/translation-errors.d.ts.map +1 -0
  180. package/dist/errors/translation-errors.js +85 -0
  181. package/dist/errors/translation-errors.js.map +1 -0
  182. package/dist/handlers/async-storage-handler.d.ts +77 -0
  183. package/dist/handlers/async-storage-handler.d.ts.map +1 -0
  184. package/dist/handlers/async-storage-handler.js +239 -0
  185. package/dist/handlers/async-storage-handler.js.map +1 -0
  186. package/dist/handlers/handler-chain.d.ts +79 -0
  187. package/dist/handlers/handler-chain.d.ts.map +1 -0
  188. package/dist/handlers/handler-chain.js +366 -0
  189. package/dist/handlers/handler-chain.js.map +1 -0
  190. package/dist/handlers/index.d.ts +7 -0
  191. package/dist/handlers/index.d.ts.map +1 -0
  192. package/dist/handlers/index.js +7 -0
  193. package/dist/handlers/index.js.map +1 -0
  194. package/dist/handlers/network-handler.d.ts +90 -0
  195. package/dist/handlers/network-handler.d.ts.map +1 -0
  196. package/dist/handlers/network-handler.js +283 -0
  197. package/dist/handlers/network-handler.js.map +1 -0
  198. package/dist/handlers/sync-storage-handler.d.ts +88 -0
  199. package/dist/handlers/sync-storage-handler.d.ts.map +1 -0
  200. package/dist/handlers/sync-storage-handler.js +270 -0
  201. package/dist/handlers/sync-storage-handler.js.map +1 -0
  202. package/dist/index.d.ts +37 -0
  203. package/dist/index.d.ts.map +1 -0
  204. package/dist/index.js +30 -0
  205. package/dist/index.js.map +1 -0
  206. package/dist/interfaces/emitter.interface.d.ts +33 -0
  207. package/dist/interfaces/emitter.interface.d.ts.map +1 -0
  208. package/dist/interfaces/emitter.interface.js +2 -0
  209. package/dist/interfaces/emitter.interface.js.map +1 -0
  210. package/dist/interfaces/handler.interface.d.ts +50 -0
  211. package/dist/interfaces/handler.interface.d.ts.map +1 -0
  212. package/dist/interfaces/handler.interface.js +5 -0
  213. package/dist/interfaces/handler.interface.js.map +1 -0
  214. package/dist/interfaces/index.d.ts +8 -0
  215. package/dist/interfaces/index.d.ts.map +1 -0
  216. package/dist/interfaces/index.js +8 -0
  217. package/dist/interfaces/index.js.map +1 -0
  218. package/dist/interfaces/plugin.interface.d.ts +49 -0
  219. package/dist/interfaces/plugin.interface.d.ts.map +1 -0
  220. package/dist/interfaces/plugin.interface.js +6 -0
  221. package/dist/interfaces/plugin.interface.js.map +1 -0
  222. package/dist/interfaces/repository.interface.d.ts +56 -0
  223. package/dist/interfaces/repository.interface.d.ts.map +1 -0
  224. package/dist/interfaces/repository.interface.js +2 -0
  225. package/dist/interfaces/repository.interface.js.map +1 -0
  226. package/dist/lionRapid.d.ts +225 -0
  227. package/dist/lionRapid.d.ts.map +1 -0
  228. package/dist/lionRapid.js +596 -0
  229. package/dist/lionRapid.js.map +1 -0
  230. package/dist/repositories/index.d.ts +3 -0
  231. package/dist/repositories/index.d.ts.map +1 -0
  232. package/dist/repositories/index.js +3 -0
  233. package/dist/repositories/index.js.map +1 -0
  234. package/dist/repositories/memory-repository.d.ts +30 -0
  235. package/dist/repositories/memory-repository.d.ts.map +1 -0
  236. package/dist/repositories/memory-repository.js +100 -0
  237. package/dist/repositories/memory-repository.js.map +1 -0
  238. package/dist/repositories/network-repository.d.ts +138 -0
  239. package/dist/repositories/network-repository.d.ts.map +1 -0
  240. package/dist/repositories/network-repository.js +516 -0
  241. package/dist/repositories/network-repository.js.map +1 -0
  242. package/dist/sync/missing-translation-queue.d.ts +64 -0
  243. package/dist/sync/missing-translation-queue.d.ts.map +1 -0
  244. package/dist/sync/missing-translation-queue.js +184 -0
  245. package/dist/sync/missing-translation-queue.js.map +1 -0
  246. package/dist/types/cache.types.d.ts +62 -0
  247. package/dist/types/cache.types.d.ts.map +1 -0
  248. package/dist/types/cache.types.js +6 -0
  249. package/dist/types/cache.types.js.map +1 -0
  250. package/dist/types/event-payloads.types.d.ts +46 -0
  251. package/dist/types/event-payloads.types.d.ts.map +1 -0
  252. package/dist/types/event-payloads.types.js +6 -0
  253. package/dist/types/event-payloads.types.js.map +1 -0
  254. package/dist/types/generated.types.d.ts +31 -0
  255. package/dist/types/generated.types.d.ts.map +1 -0
  256. package/dist/types/generated.types.js +6 -0
  257. package/dist/types/generated.types.js.map +1 -0
  258. package/dist/types/index.d.ts +7 -0
  259. package/dist/types/index.d.ts.map +1 -0
  260. package/dist/types/index.js +7 -0
  261. package/dist/types/index.js.map +1 -0
  262. package/dist/types/lionRapid.types.d.ts +169 -0
  263. package/dist/types/lionRapid.types.d.ts.map +1 -0
  264. package/dist/types/lionRapid.types.js +8 -0
  265. package/dist/types/lionRapid.types.js.map +1 -0
  266. package/dist/types/parameters.types.d.ts +36 -0
  267. package/dist/types/parameters.types.d.ts.map +1 -0
  268. package/dist/types/parameters.types.js +6 -0
  269. package/dist/types/parameters.types.js.map +1 -0
  270. package/dist/types/translation-data.types.d.ts +54 -0
  271. package/dist/types/translation-data.types.d.ts.map +1 -0
  272. package/dist/types/translation-data.types.js +6 -0
  273. package/dist/types/translation-data.types.js.map +1 -0
  274. package/dist/types/translation.types.d.ts +94 -0
  275. package/dist/types/translation.types.d.ts.map +1 -0
  276. package/dist/types/translation.types.js +5 -0
  277. package/dist/types/translation.types.js.map +1 -0
  278. package/dist/utils/context-utils.d.ts +23 -0
  279. package/dist/utils/context-utils.d.ts.map +1 -0
  280. package/dist/utils/context-utils.js +36 -0
  281. package/dist/utils/context-utils.js.map +1 -0
  282. package/dist/utils/index.d.ts +10 -0
  283. package/dist/utils/index.d.ts.map +1 -0
  284. package/dist/utils/index.js +19 -0
  285. package/dist/utils/index.js.map +1 -0
  286. package/dist/utils/logger.d.ts +229 -0
  287. package/dist/utils/logger.d.ts.map +1 -0
  288. package/dist/utils/logger.js +345 -0
  289. package/dist/utils/logger.js.map +1 -0
  290. package/dist/utils/namespace-utils.d.ts +30 -0
  291. package/dist/utils/namespace-utils.d.ts.map +1 -0
  292. package/dist/utils/namespace-utils.js +56 -0
  293. package/dist/utils/namespace-utils.js.map +1 -0
  294. package/dist/utils/translation-flow.d.ts +18 -0
  295. package/dist/utils/translation-flow.d.ts.map +1 -0
  296. package/dist/utils/translation-flow.js +74 -0
  297. package/dist/utils/translation-flow.js.map +1 -0
  298. package/dist/utils/translation-value-utils.d.ts +57 -0
  299. package/dist/utils/translation-value-utils.d.ts.map +1 -0
  300. package/dist/utils/translation-value-utils.js +194 -0
  301. package/dist/utils/translation-value-utils.js.map +1 -0
  302. package/package.json +76 -0
@@ -0,0 +1,1869 @@
1
+ "use strict";
2
+ var LionRapidEditor = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ // src/browser/editor/editor-styles.ts
8
+ var EDITOR_CSS = `
9
+ /* Reset inside shadow DOM */
10
+ :host {
11
+ all: initial;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
13
+ font-size: 14px;
14
+ color: #1a1a2e;
15
+ line-height: 1.5;
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ *, *::before, *::after {
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ /* \u2500\u2500 FAB (replaces fixed toolbar) \u2500\u2500 */
24
+ .lr-fab {
25
+ position: fixed;
26
+ bottom: 20px;
27
+ right: 20px;
28
+ z-index: 2147483647;
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 0;
32
+ user-select: none;
33
+ }
34
+
35
+ .lr-fab-btn {
36
+ width: 48px;
37
+ height: 48px;
38
+ border-radius: 50%;
39
+ background: #2d7f7f;
40
+ color: #fff;
41
+ border: none;
42
+ font-weight: 700;
43
+ font-size: 16px;
44
+ cursor: grab;
45
+ box-shadow: 0 4px 12px rgba(0,0,0,0.25);
46
+ transition: box-shadow 0.15s;
47
+ flex-shrink: 0;
48
+ z-index: 1;
49
+ }
50
+
51
+ .lr-fab-btn:hover {
52
+ box-shadow: 0 6px 20px rgba(0,0,0,0.3);
53
+ }
54
+
55
+ .lr-fab-btn:active {
56
+ cursor: grabbing;
57
+ }
58
+
59
+ .lr-fab-panel {
60
+ display: none;
61
+ align-items: center;
62
+ gap: 8px;
63
+ background: #1a1a2e;
64
+ color: #fff;
65
+ padding: 8px 12px;
66
+ border-radius: 8px;
67
+ margin-right: 8px;
68
+ box-shadow: 0 4px 12px rgba(0,0,0,0.25);
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .lr-fab.expanded .lr-fab-panel {
73
+ display: flex;
74
+ }
75
+
76
+ .lr-lang-dropdown {
77
+ position: relative;
78
+ }
79
+
80
+ .lr-lang-btn {
81
+ background: rgba(255,255,255,0.1);
82
+ color: #fff;
83
+ border: 1px solid rgba(255,255,255,0.2);
84
+ border-radius: 4px;
85
+ padding: 4px 10px;
86
+ font-size: 13px;
87
+ font-weight: 600;
88
+ cursor: pointer;
89
+ outline: none;
90
+ }
91
+
92
+ .lr-lang-btn:hover {
93
+ background: rgba(255,255,255,0.2);
94
+ }
95
+
96
+ .lr-lang-menu {
97
+ display: none;
98
+ position: absolute;
99
+ bottom: calc(100% + 6px);
100
+ left: 0;
101
+ background: #1a1a2e;
102
+ border: 1px solid rgba(255,255,255,0.15);
103
+ border-radius: 6px;
104
+ padding: 4px;
105
+ min-width: 60px;
106
+ box-shadow: 0 4px 16px rgba(0,0,0,0.4);
107
+ }
108
+
109
+ .lr-lang-menu.open {
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 2px;
113
+ }
114
+
115
+ .lr-lang-item {
116
+ background: none;
117
+ color: #ccc;
118
+ border: none;
119
+ padding: 6px 10px;
120
+ font-size: 13px;
121
+ font-weight: 500;
122
+ cursor: pointer;
123
+ border-radius: 4px;
124
+ text-align: left;
125
+ }
126
+
127
+ .lr-lang-item:hover {
128
+ background: rgba(255,255,255,0.1);
129
+ color: #fff;
130
+ }
131
+
132
+ .lr-lang-item.active {
133
+ background: #2d7f7f;
134
+ color: #fff;
135
+ }
136
+
137
+ .lr-fab-progress {
138
+ font-size: 13px;
139
+ color: rgba(255,255,255,0.7);
140
+ }
141
+
142
+ .lr-fab-exit {
143
+ background: none;
144
+ border: 1px solid rgba(255,255,255,0.3);
145
+ color: #fff;
146
+ padding: 4px 10px;
147
+ border-radius: 4px;
148
+ cursor: pointer;
149
+ font-size: 13px;
150
+ }
151
+
152
+ .lr-fab-exit:hover {
153
+ background: rgba(255,255,255,0.1);
154
+ }
155
+
156
+ /* \u2500\u2500 Overlay \u2500\u2500 */
157
+ .lr-overlay {
158
+ position: fixed;
159
+ pointer-events: none;
160
+ border: 2px solid #2d7f7f;
161
+ border-radius: 3px;
162
+ background: rgba(45, 127, 127, 0.06);
163
+ transition: all 0.1s ease;
164
+ z-index: 2147483646;
165
+ display: none;
166
+ }
167
+
168
+ .lr-overlay.active {
169
+ display: block;
170
+ }
171
+
172
+ /* \u2500\u2500 Sidebar \u2500\u2500 */
173
+ .lr-sidebar {
174
+ position: fixed;
175
+ top: 0;
176
+ right: 0;
177
+ width: 360px;
178
+ min-width: 280px;
179
+ max-width: 600px;
180
+ bottom: 0;
181
+ background: #fff;
182
+ border-left: 1px solid #e0e0e0;
183
+ z-index: 2147483647;
184
+ overflow-y: auto;
185
+ transform: translateX(100%);
186
+ transition: transform 0.2s ease;
187
+ }
188
+
189
+ .lr-sidebar.open {
190
+ transform: translateX(0);
191
+ }
192
+
193
+ /* Resize handle on left edge */
194
+ .lr-sidebar-resize {
195
+ position: absolute;
196
+ top: 0;
197
+ left: -4px;
198
+ width: 8px;
199
+ bottom: 0;
200
+ cursor: col-resize;
201
+ z-index: 1;
202
+ }
203
+
204
+ .lr-sidebar-resize:hover,
205
+ .lr-sidebar-resize.active {
206
+ background: rgba(45, 127, 127, 0.3);
207
+ }
208
+
209
+ .lr-sidebar-header {
210
+ padding: 16px;
211
+ border-bottom: 1px solid #e0e0e0;
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: space-between;
215
+ }
216
+
217
+ .lr-sidebar-title {
218
+ font-weight: 600;
219
+ font-size: 15px;
220
+ }
221
+
222
+ .lr-sidebar-close {
223
+ background: none;
224
+ border: none;
225
+ font-size: 20px;
226
+ cursor: pointer;
227
+ color: #666;
228
+ padding: 4px 8px;
229
+ line-height: 1;
230
+ }
231
+
232
+ .lr-sidebar-close:hover {
233
+ color: #1a1a2e;
234
+ }
235
+
236
+ .lr-sidebar-body {
237
+ padding: 16px;
238
+ }
239
+
240
+ .lr-field {
241
+ margin-bottom: 16px;
242
+ }
243
+
244
+ .lr-field-label {
245
+ display: block;
246
+ font-size: 12px;
247
+ font-weight: 600;
248
+ color: #666;
249
+ text-transform: uppercase;
250
+ letter-spacing: 0.5px;
251
+ margin-bottom: 6px;
252
+ }
253
+
254
+ .lr-field-key {
255
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
256
+ font-size: 12px;
257
+ color: #2d7f7f;
258
+ background: #f0f7f7;
259
+ padding: 4px 8px;
260
+ border-radius: 4px;
261
+ word-break: break-all;
262
+ }
263
+
264
+ .lr-field-original {
265
+ font-size: 13px;
266
+ color: #333;
267
+ background: #f8f8f8;
268
+ padding: 10px 12px;
269
+ border-radius: 6px;
270
+ border: 1px solid #eee;
271
+ white-space: pre-wrap;
272
+ word-break: break-word;
273
+ }
274
+
275
+ .lr-field-tag {
276
+ display: inline;
277
+ background: #d9eded;
278
+ color: #2d7f7f;
279
+ padding: 0 3px;
280
+ border-radius: 2px;
281
+ font-family: monospace;
282
+ font-size: 12px;
283
+ }
284
+
285
+ .lr-textarea {
286
+ width: 100%;
287
+ min-height: 100px;
288
+ padding: 10px 12px;
289
+ border: 1px solid #ddd;
290
+ border-radius: 6px;
291
+ font-family: inherit;
292
+ font-size: 14px;
293
+ line-height: 1.5;
294
+ resize: vertical;
295
+ outline: none;
296
+ transition: border-color 0.15s;
297
+ }
298
+
299
+ .lr-textarea:focus {
300
+ border-color: #2d7f7f;
301
+ box-shadow: 0 0 0 3px rgba(45, 127, 127, 0.1);
302
+ }
303
+
304
+ .lr-preview {
305
+ padding: 10px 12px;
306
+ background: #f8f8f8;
307
+ border: 1px solid #eee;
308
+ border-radius: 6px;
309
+ font-size: 13px;
310
+ min-height: 40px;
311
+ }
312
+
313
+ .lr-status {
314
+ padding: 10px 16px;
315
+ font-size: 12px;
316
+ color: #666;
317
+ text-align: center;
318
+ border-top: 1px solid #eee;
319
+ background: #fafafa;
320
+ }
321
+
322
+ .lr-status.saved {
323
+ color: #22c55e;
324
+ }
325
+
326
+ .lr-status.error {
327
+ color: #ef4444;
328
+ }
329
+
330
+ .lr-status.unsaved {
331
+ color: #f59e0b;
332
+ }
333
+
334
+ /* \u2500\u2500 Page blocks list \u2500\u2500 */
335
+ .lr-blocks-section {
336
+ margin-top: 16px;
337
+ padding-top: 16px;
338
+ border-top: 1px solid #eee;
339
+ }
340
+
341
+ .lr-blocks-title {
342
+ font-size: 12px;
343
+ font-weight: 600;
344
+ color: #666;
345
+ text-transform: uppercase;
346
+ letter-spacing: 0.5px;
347
+ margin-bottom: 8px;
348
+ }
349
+
350
+ .lr-block-item {
351
+ padding: 8px 10px;
352
+ border: 1px solid #eee;
353
+ border-radius: 4px;
354
+ margin-bottom: 4px;
355
+ cursor: pointer;
356
+ font-size: 13px;
357
+ color: #333;
358
+ overflow: hidden;
359
+ transition: background 0.1s;
360
+ display: flex;
361
+ align-items: flex-start;
362
+ gap: 8px;
363
+ }
364
+
365
+ .lr-block-item:hover {
366
+ background: #f0f7f7;
367
+ border-color: #2d7f7f;
368
+ }
369
+
370
+ .lr-block-item.active {
371
+ background: #e5f2f2;
372
+ border-color: #2d7f7f;
373
+ }
374
+
375
+ /* \u2500\u2500 Status dots \u2500\u2500 */
376
+ .lr-status-dot {
377
+ width: 8px;
378
+ height: 8px;
379
+ border-radius: 50%;
380
+ flex-shrink: 0;
381
+ margin-top: 5px;
382
+ background: #d1d5db;
383
+ }
384
+
385
+ .lr-status-dot.missing { background: #d1d5db; }
386
+ .lr-status-dot.mt { background: #60a5fa; }
387
+ .lr-status-dot.edited { background: #f97316; }
388
+ .lr-status-dot.confirmed { background: #22c55e; }
389
+ .lr-status-dot.ready { background: #22c55e; }
390
+ .lr-status-dot.locked { background: #a855f7; }
391
+ .lr-status-dot.unsaved { background: #f59e0b; }
392
+
393
+ /* \u2500\u2500 Locked textarea \u2500\u2500 */
394
+ .lr-textarea-locked {
395
+ background: #f3f4f6;
396
+ color: #6b7280;
397
+ cursor: not-allowed;
398
+ border-color: #e5e7eb;
399
+ }
400
+
401
+ /* \u2500\u2500 Block text layout \u2500\u2500 */
402
+ .lr-block-text {
403
+ overflow: hidden;
404
+ min-width: 0;
405
+ }
406
+
407
+ .lr-block-source {
408
+ overflow: hidden;
409
+ text-overflow: ellipsis;
410
+ white-space: nowrap;
411
+ }
412
+
413
+ .lr-block-preview {
414
+ font-size: 11px;
415
+ color: #888;
416
+ overflow: hidden;
417
+ text-overflow: ellipsis;
418
+ white-space: nowrap;
419
+ margin-top: 2px;
420
+ }
421
+ `;
422
+
423
+ // src/content/style-mapper/DefaultStyleMapper.ts
424
+ var DefaultStyleMapper = class {
425
+ constructor() {
426
+ /**
427
+ * Element → Style mapping
428
+ * Maps HTML element names to their style property
429
+ */
430
+ __publicField(this, "elementToStyles", {
431
+ b: "bold",
432
+ strong: "bold",
433
+ i: "italic",
434
+ em: "italic",
435
+ u: "underline",
436
+ s: "strikethrough",
437
+ del: "strikethrough",
438
+ strike: "strikethrough",
439
+ code: "code",
440
+ mark: "highlight",
441
+ sub: "subscript",
442
+ sup: "superscript"
443
+ });
444
+ /**
445
+ * Style → Element mapping (reverse)
446
+ * Uses simple, familiar elements for developer convenience
447
+ * Also accepts element names directly (strong, em, etc.)
448
+ */
449
+ __publicField(this, "styleToElement", {
450
+ // Semantic style names → simple elements
451
+ bold: "b",
452
+ italic: "i",
453
+ underline: "u",
454
+ strikethrough: "s",
455
+ code: "code",
456
+ highlight: "mark",
457
+ subscript: "sub",
458
+ superscript: "sup",
459
+ // Element names (for servers that store styles as element names)
460
+ strong: "b",
461
+ b: "b",
462
+ em: "i",
463
+ i: "i",
464
+ u: "u",
465
+ s: "s",
466
+ del: "s",
467
+ strike: "s",
468
+ mark: "mark",
469
+ sub: "sub",
470
+ sup: "sup"
471
+ });
472
+ }
473
+ /**
474
+ * Convert HTML element to style properties
475
+ *
476
+ * @param element - Tag name (e.g., "b", "a", "strong")
477
+ * @param attrs - Element attributes (e.g., { href: "/url" })
478
+ * @returns Array of style strings
479
+ */
480
+ mapElementToStyles(element, attrs) {
481
+ const styles = [];
482
+ const tag = element.toLowerCase();
483
+ const mappedStyle = this.elementToStyles[tag];
484
+ if (mappedStyle) {
485
+ styles.push(mappedStyle);
486
+ }
487
+ if (tag === "a" && attrs.href) {
488
+ styles.push(`hyperlink:${attrs.href}`);
489
+ }
490
+ return styles;
491
+ }
492
+ /**
493
+ * Convert style properties to HTML element
494
+ *
495
+ * Priority order:
496
+ * 1. Hyperlink (becomes wrapper element with href)
497
+ * 2. First recognized style property
498
+ * 3. Fallback to span
499
+ *
500
+ * @param styles - Array of style strings
501
+ * @returns Element info for serialization
502
+ */
503
+ mapStylesToElement(styles) {
504
+ for (const style of styles) {
505
+ if (style.startsWith("hyperlink:")) {
506
+ return {
507
+ element: "a",
508
+ attrs: { href: style.slice("hyperlink:".length) }
509
+ };
510
+ }
511
+ }
512
+ for (const style of styles) {
513
+ const element = this.styleToElement[style];
514
+ if (element) {
515
+ return {
516
+ element,
517
+ attrs: {}
518
+ };
519
+ }
520
+ }
521
+ return { element: "span", attrs: {} };
522
+ }
523
+ };
524
+
525
+ // src/browser/markup-parser.ts
526
+ var styleMapper = new DefaultStyleMapper();
527
+ function parseBlockElement(element) {
528
+ const styles = {};
529
+ let tagIndex = 0;
530
+ function walk(node) {
531
+ if (node.nodeType === Node.TEXT_NODE) {
532
+ return node.nodeValue ?? "";
533
+ }
534
+ if (node.nodeType !== Node.ELEMENT_NODE) {
535
+ return "";
536
+ }
537
+ const el = node;
538
+ const tag = el.tagName.toLowerCase();
539
+ const attrs = {};
540
+ for (let i = 0; i < el.attributes.length; i++) {
541
+ const attr = el.attributes[i];
542
+ attrs[attr.name.toLowerCase()] = attr.value;
543
+ }
544
+ const styleList = styleMapper.mapElementToStyles(tag, attrs);
545
+ if (styleList.length > 0) {
546
+ tagIndex++;
547
+ const idx = tagIndex;
548
+ styles[String(idx)] = styleList;
549
+ let inner = "";
550
+ for (let i = 0; i < node.childNodes.length; i++) {
551
+ inner += walk(node.childNodes[i]);
552
+ }
553
+ return `<${idx}>${inner}</${idx}>`;
554
+ }
555
+ let result = "";
556
+ for (let i = 0; i < node.childNodes.length; i++) {
557
+ result += walk(node.childNodes[i]);
558
+ }
559
+ return result;
560
+ }
561
+ let text = "";
562
+ for (let i = 0; i < element.childNodes.length; i++) {
563
+ text += walk(element.childNodes[i]);
564
+ }
565
+ return { text, styles };
566
+ }
567
+
568
+ // src/browser/md5.ts
569
+ function safeAdd(x, y) {
570
+ const lsw = (x & 65535) + (y & 65535);
571
+ const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
572
+ return msw << 16 | lsw & 65535;
573
+ }
574
+ function bitRotateLeft(num, cnt) {
575
+ return num << cnt | num >>> 32 - cnt;
576
+ }
577
+ function md5cmn(q, a, b, x, s, t) {
578
+ return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
579
+ }
580
+ function md5ff(a, b, c, d, x, s, t) {
581
+ return md5cmn(b & c | ~b & d, a, b, x, s, t);
582
+ }
583
+ function md5gg(a, b, c, d, x, s, t) {
584
+ return md5cmn(b & d | c & ~d, a, b, x, s, t);
585
+ }
586
+ function md5hh(a, b, c, d, x, s, t) {
587
+ return md5cmn(b ^ c ^ d, a, b, x, s, t);
588
+ }
589
+ function md5ii(a, b, c, d, x, s, t) {
590
+ return md5cmn(c ^ (b | ~d), a, b, x, s, t);
591
+ }
592
+ function binlMD5(x, len) {
593
+ x[len >> 5] |= 128 << len % 32;
594
+ x[(len + 64 >>> 9 << 4) + 14] = len;
595
+ let a = 1732584193;
596
+ let b = -271733879;
597
+ let c = -1732584194;
598
+ let d = 271733878;
599
+ for (let i = 0; i < x.length; i += 16) {
600
+ const olda = a;
601
+ const oldb = b;
602
+ const oldc = c;
603
+ const oldd = d;
604
+ a = md5ff(a, b, c, d, x[i], 7, -680876936);
605
+ d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
606
+ c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
607
+ b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
608
+ a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
609
+ d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
610
+ c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
611
+ b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
612
+ a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
613
+ d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
614
+ c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
615
+ b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
616
+ a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
617
+ d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
618
+ c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
619
+ b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
620
+ a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
621
+ d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
622
+ c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
623
+ b = md5gg(b, c, d, a, x[i], 20, -373897302);
624
+ a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
625
+ d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
626
+ c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
627
+ b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
628
+ a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
629
+ d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
630
+ c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
631
+ b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
632
+ a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
633
+ d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
634
+ c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
635
+ b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
636
+ a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
637
+ d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
638
+ c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
639
+ b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
640
+ a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
641
+ d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
642
+ c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
643
+ b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
644
+ a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
645
+ d = md5hh(d, a, b, c, x[i + 0], 11, -358537222);
646
+ c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
647
+ b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
648
+ a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
649
+ d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
650
+ c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
651
+ b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
652
+ a = md5ii(a, b, c, d, x[i], 6, -198630844);
653
+ d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
654
+ c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
655
+ b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
656
+ a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
657
+ d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
658
+ c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
659
+ b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
660
+ a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
661
+ d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
662
+ c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
663
+ b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
664
+ a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
665
+ d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
666
+ c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
667
+ b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
668
+ a = safeAdd(a, olda);
669
+ b = safeAdd(b, oldb);
670
+ c = safeAdd(c, oldc);
671
+ d = safeAdd(d, oldd);
672
+ }
673
+ return [a, b, c, d];
674
+ }
675
+ function binl2hex(binarray) {
676
+ const hexTab = "0123456789abcdef";
677
+ let str = "";
678
+ for (let i = 0; i < binarray.length * 4; i++) {
679
+ str += hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hexTab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
680
+ }
681
+ return str;
682
+ }
683
+ function str2binl(str) {
684
+ const bin = [];
685
+ for (let i = 0; i < str.length * 8; i += 8) {
686
+ bin[i >> 5] |= (str.charCodeAt(i / 8) & 255) << i % 32;
687
+ }
688
+ return bin;
689
+ }
690
+ function md5(str) {
691
+ return binl2hex(binlMD5(str2binl(str), str.length * 8));
692
+ }
693
+ function generateAutoKey(text) {
694
+ const trimmed = text.trim();
695
+ return "auto_" + md5(trimmed).substring(0, 12);
696
+ }
697
+
698
+ // src/browser/editor/key-resolver.ts
699
+ var BLOCK_SELECTOR = "p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption";
700
+ function resolveKey(target) {
701
+ const blockEl = target.closest("[data-lr-block]");
702
+ if (blockEl) {
703
+ const key = blockEl.getAttribute("data-lr-block");
704
+ const original = blockEl.getAttribute("data-lr-original");
705
+ let unit;
706
+ let translatedUnit = null;
707
+ if (original) {
708
+ const temp = document.createElement(blockEl.tagName);
709
+ temp.innerHTML = original;
710
+ unit = parseBlockElement(temp);
711
+ translatedUnit = parseBlockElement(blockEl);
712
+ } else {
713
+ unit = parseBlockElement(blockEl);
714
+ }
715
+ return { key, unit, translatedUnit, element: blockEl };
716
+ }
717
+ const nearest = target.closest(BLOCK_SELECTOR);
718
+ if (nearest) {
719
+ const unit = parseBlockElement(nearest);
720
+ const key = generateAutoKey(unit.text);
721
+ return { key, unit, translatedUnit: null, element: nearest };
722
+ }
723
+ const text = target.textContent?.trim();
724
+ if (text && text.length >= 2) {
725
+ const key = generateAutoKey(text);
726
+ const unit = { text, styles: {} };
727
+ return { key, unit, translatedUnit: null, element: target };
728
+ }
729
+ return null;
730
+ }
731
+
732
+ // src/browser/editor/inspector.ts
733
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "CODE", "PRE", "TEXTAREA", "NOSCRIPT", "SVG"]);
734
+ var BLOCK_SELECTOR2 = "p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption";
735
+ var Inspector = class {
736
+ constructor(shadowRoot, onSelect) {
737
+ __publicField(this, "overlay");
738
+ __publicField(this, "currentTarget", null);
739
+ __publicField(this, "onSelect");
740
+ __publicField(this, "rafId", null);
741
+ __publicField(this, "active", true);
742
+ this.onSelect = onSelect;
743
+ this.overlay = document.createElement("div");
744
+ this.overlay.className = "lr-overlay";
745
+ shadowRoot.appendChild(this.overlay);
746
+ this.handleMouseOver = this.handleMouseOver.bind(this);
747
+ this.handleClick = this.handleClick.bind(this);
748
+ this.handleScroll = this.handleScroll.bind(this);
749
+ document.addEventListener("mouseover", this.handleMouseOver, true);
750
+ document.addEventListener("click", this.handleClick, true);
751
+ window.addEventListener("scroll", this.handleScroll, true);
752
+ window.addEventListener("resize", this.handleScroll);
753
+ }
754
+ destroy() {
755
+ this.active = false;
756
+ document.removeEventListener("mouseover", this.handleMouseOver, true);
757
+ document.removeEventListener("click", this.handleClick, true);
758
+ window.removeEventListener("scroll", this.handleScroll, true);
759
+ window.removeEventListener("resize", this.handleScroll);
760
+ if (this.rafId !== null) cancelAnimationFrame(this.rafId);
761
+ this.overlay.remove();
762
+ }
763
+ clearHighlight() {
764
+ this.currentTarget = null;
765
+ this.overlay.classList.remove("active");
766
+ }
767
+ handleMouseOver(e) {
768
+ if (!this.active) return;
769
+ const target = e.target;
770
+ if (!target || !target.closest) return;
771
+ if (target.closest("#__lionrapid-editor")) return;
772
+ if (this.shouldSkip(target)) {
773
+ this.clearHighlight();
774
+ return;
775
+ }
776
+ const block = target.closest(BLOCK_SELECTOR2) || target.closest("[data-lr-block]");
777
+ if (!block) {
778
+ this.clearHighlight();
779
+ return;
780
+ }
781
+ if (block === this.currentTarget) return;
782
+ this.currentTarget = block;
783
+ this.positionOverlay(block);
784
+ }
785
+ handleClick(e) {
786
+ if (!this.active) return;
787
+ const target = e.target;
788
+ if (!target || !target.closest) return;
789
+ if (target.closest("#__lionrapid-editor")) return;
790
+ const block = target.closest(BLOCK_SELECTOR2) || target.closest("[data-lr-block]");
791
+ if (!block) return;
792
+ if (this.shouldSkip(block)) return;
793
+ e.preventDefault();
794
+ e.stopPropagation();
795
+ const resolved = resolveKey(block);
796
+ if (resolved) {
797
+ this.onSelect(resolved);
798
+ }
799
+ }
800
+ handleScroll() {
801
+ if (!this.currentTarget) return;
802
+ if (this.rafId !== null) cancelAnimationFrame(this.rafId);
803
+ this.rafId = requestAnimationFrame(() => {
804
+ if (this.currentTarget) {
805
+ this.positionOverlay(this.currentTarget);
806
+ }
807
+ });
808
+ }
809
+ positionOverlay(element) {
810
+ const rect = element.getBoundingClientRect();
811
+ this.overlay.style.top = `${rect.top}px`;
812
+ this.overlay.style.left = `${rect.left}px`;
813
+ this.overlay.style.width = `${rect.width}px`;
814
+ this.overlay.style.height = `${rect.height}px`;
815
+ this.overlay.classList.add("active");
816
+ }
817
+ shouldSkip(element) {
818
+ let current = element;
819
+ while (current) {
820
+ if (SKIP_TAGS.has(current.tagName)) return true;
821
+ if (current.hasAttribute("data-no-translate")) return true;
822
+ if (current.getAttribute("translate") === "no") return true;
823
+ current = current.parentElement;
824
+ }
825
+ return false;
826
+ }
827
+ };
828
+
829
+ // src/browser/editor/toolbar.ts
830
+ var Toolbar = class {
831
+ constructor(shadowRoot, languages, currentLocale, onLanguageChange, onExit) {
832
+ __publicField(this, "el");
833
+ __publicField(this, "langBtnEl");
834
+ __publicField(this, "langMenuEl");
835
+ __publicField(this, "progressEl");
836
+ __publicField(this, "expanded", false);
837
+ __publicField(this, "currentLocale");
838
+ __publicField(this, "onLanguageChange");
839
+ __publicField(this, "onExit");
840
+ // Drag state
841
+ __publicField(this, "dragging", false);
842
+ __publicField(this, "dragOffsetX", 0);
843
+ __publicField(this, "dragOffsetY", 0);
844
+ this.onLanguageChange = onLanguageChange;
845
+ this.onExit = onExit;
846
+ this.currentLocale = currentLocale;
847
+ this.el = document.createElement("div");
848
+ this.el.className = "lr-fab";
849
+ const fabBtn = document.createElement("button");
850
+ fabBtn.className = "lr-fab-btn";
851
+ fabBtn.textContent = "LR";
852
+ fabBtn.addEventListener("click", (e) => {
853
+ if (this.dragging) return;
854
+ e.stopPropagation();
855
+ this.toggle();
856
+ });
857
+ this.el.appendChild(fabBtn);
858
+ const panel = document.createElement("div");
859
+ panel.className = "lr-fab-panel";
860
+ const langWrap = document.createElement("div");
861
+ langWrap.className = "lr-lang-dropdown";
862
+ this.langBtnEl = document.createElement("button");
863
+ this.langBtnEl.className = "lr-lang-btn";
864
+ this.langBtnEl.textContent = currentLocale.toUpperCase();
865
+ this.langBtnEl.addEventListener("click", (e) => {
866
+ e.stopPropagation();
867
+ this.langMenuEl.classList.toggle("open");
868
+ });
869
+ langWrap.appendChild(this.langBtnEl);
870
+ this.langMenuEl = document.createElement("div");
871
+ this.langMenuEl.className = "lr-lang-menu";
872
+ for (const lang of languages) {
873
+ const item = document.createElement("button");
874
+ item.className = "lr-lang-item";
875
+ if (lang === currentLocale) item.classList.add("active");
876
+ item.textContent = lang.toUpperCase();
877
+ item.addEventListener("click", (e) => {
878
+ e.stopPropagation();
879
+ this.currentLocale = lang;
880
+ this.langBtnEl.textContent = lang.toUpperCase();
881
+ this.langMenuEl.classList.remove("open");
882
+ this.langMenuEl.querySelectorAll(".lr-lang-item").forEach((el) => el.classList.remove("active"));
883
+ item.classList.add("active");
884
+ this.onLanguageChange(lang);
885
+ });
886
+ this.langMenuEl.appendChild(item);
887
+ }
888
+ langWrap.appendChild(this.langMenuEl);
889
+ panel.appendChild(langWrap);
890
+ this.progressEl = document.createElement("span");
891
+ this.progressEl.className = "lr-fab-progress";
892
+ panel.appendChild(this.progressEl);
893
+ const exitBtn = document.createElement("button");
894
+ exitBtn.className = "lr-fab-exit";
895
+ exitBtn.textContent = "Exit";
896
+ exitBtn.addEventListener("click", () => this.onExit());
897
+ panel.appendChild(exitBtn);
898
+ this.el.appendChild(panel);
899
+ shadowRoot.appendChild(this.el);
900
+ this.handleMouseDown = this.handleMouseDown.bind(this);
901
+ this.handleMouseMove = this.handleMouseMove.bind(this);
902
+ this.handleMouseUp = this.handleMouseUp.bind(this);
903
+ fabBtn.addEventListener("mousedown", this.handleMouseDown);
904
+ }
905
+ toggle() {
906
+ this.expanded = !this.expanded;
907
+ this.el.classList.toggle("expanded", this.expanded);
908
+ }
909
+ updateProgress(translated, total) {
910
+ this.progressEl.textContent = `${translated}/${total}`;
911
+ }
912
+ getCurrentLocale() {
913
+ return this.currentLocale;
914
+ }
915
+ destroy() {
916
+ document.removeEventListener("mousemove", this.handleMouseMove);
917
+ document.removeEventListener("mouseup", this.handleMouseUp);
918
+ this.el.remove();
919
+ }
920
+ handleMouseDown(e) {
921
+ this.dragging = false;
922
+ this.dragOffsetX = e.clientX - this.el.getBoundingClientRect().left;
923
+ this.dragOffsetY = e.clientY - this.el.getBoundingClientRect().top;
924
+ document.addEventListener("mousemove", this.handleMouseMove);
925
+ document.addEventListener("mouseup", this.handleMouseUp);
926
+ }
927
+ handleMouseMove(e) {
928
+ this.dragging = true;
929
+ const x = Math.max(0, Math.min(e.clientX - this.dragOffsetX, window.innerWidth - this.el.offsetWidth));
930
+ const y = Math.max(0, Math.min(e.clientY - this.dragOffsetY, window.innerHeight - this.el.offsetHeight));
931
+ this.el.style.left = `${x}px`;
932
+ this.el.style.top = `${y}px`;
933
+ this.el.style.right = "auto";
934
+ this.el.style.bottom = "auto";
935
+ }
936
+ handleMouseUp() {
937
+ document.removeEventListener("mousemove", this.handleMouseMove);
938
+ document.removeEventListener("mouseup", this.handleMouseUp);
939
+ if (this.dragging) {
940
+ setTimeout(() => {
941
+ this.dragging = false;
942
+ }, 0);
943
+ }
944
+ }
945
+ };
946
+
947
+ // src/content/serializer/HTMLSerializer.ts
948
+ var HTMLSerializer = class {
949
+ constructor(options = {}) {
950
+ __publicField(this, "styleMapper");
951
+ this.styleMapper = options.styleMapper ?? new DefaultStyleMapper();
952
+ }
953
+ /**
954
+ * Serialize ContentUnit to HTML string
955
+ *
956
+ * @param unit - ContentUnit with indexed tags and styles
957
+ * @returns HTML string with semantic elements
958
+ */
959
+ serialize(unit) {
960
+ const { text, styles } = unit;
961
+ const tagPattern = /<(\/?)?(\d+)(\/)?>/g;
962
+ let result = "";
963
+ let lastIndex = 0;
964
+ let match;
965
+ while ((match = tagPattern.exec(text)) !== null) {
966
+ result += text.slice(lastIndex, match.index);
967
+ const [fullMatch, closingSlash, indexStr, selfClosingSlash] = match;
968
+ const tagIndex = indexStr;
969
+ const styleList = styles[tagIndex] || [];
970
+ if (closingSlash) {
971
+ const mapping = this.styleMapper.mapStylesToElement(styleList);
972
+ result += `</${mapping.element}>`;
973
+ } else if (selfClosingSlash) {
974
+ const mapping = this.styleMapper.mapStylesToElement(styleList);
975
+ const attrs = this.formatAttributes(mapping.attrs);
976
+ result += `<${mapping.element}${attrs} />`;
977
+ } else {
978
+ const mapping = this.styleMapper.mapStylesToElement(styleList);
979
+ const attrs = this.formatAttributes(mapping.attrs);
980
+ result += `<${mapping.element}${attrs}>`;
981
+ }
982
+ lastIndex = match.index + fullMatch.length;
983
+ }
984
+ result += text.slice(lastIndex);
985
+ return result;
986
+ }
987
+ /**
988
+ * Format attributes object into HTML attribute string
989
+ */
990
+ formatAttributes(attrs) {
991
+ const entries = Object.entries(attrs);
992
+ if (entries.length === 0) return "";
993
+ return " " + entries.map(([key, value]) => `${key}="${this.escapeAttributeValue(value)}"`).join(" ");
994
+ }
995
+ /**
996
+ * Escape special characters in attribute values
997
+ */
998
+ escapeAttributeValue(value) {
999
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1000
+ }
1001
+ };
1002
+
1003
+ // src/browser/editor/live-preview.ts
1004
+ var htmlSerializer = new HTMLSerializer();
1005
+ function applyLivePreview(element, text, styles, key, locale, namespace) {
1006
+ if (!element.hasAttribute("data-lr-original")) {
1007
+ element.setAttribute("data-lr-original", element.innerHTML);
1008
+ }
1009
+ const lr = window.LionRapid;
1010
+ const cache = lr?.autoTranslator ? getCache() : null;
1011
+ if (cache) {
1012
+ const fullKey = `${locale}:${namespace}:${key}`;
1013
+ cache.set(fullKey, text, styles);
1014
+ }
1015
+ if (styles && Object.keys(styles).length > 0) {
1016
+ const unit = { text, styles };
1017
+ element.innerHTML = htmlSerializer.serialize(unit);
1018
+ } else {
1019
+ element.textContent = text;
1020
+ }
1021
+ element.setAttribute("data-lr-block", key);
1022
+ element.setAttribute("data-lionrapid-client", "1");
1023
+ }
1024
+ function getCache() {
1025
+ try {
1026
+ const lr = window.LionRapid;
1027
+ if (!lr) return null;
1028
+ const at = lr.autoTranslator;
1029
+ if (!at) return null;
1030
+ const core = at.core;
1031
+ if (!core) return null;
1032
+ const cache = core.cache;
1033
+ if (!cache || typeof cache.set !== "function") return null;
1034
+ return cache;
1035
+ } catch {
1036
+ return null;
1037
+ }
1038
+ }
1039
+
1040
+ // src/browser/exclude-filter.ts
1041
+ var DEFAULT_PATTERNS = {
1042
+ emails: true,
1043
+ urls: true,
1044
+ numbers: true,
1045
+ phones: true
1046
+ };
1047
+ var PATTERNS = {
1048
+ email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
1049
+ url: /^(https?:\/\/|www\.)[^\s]+$/i,
1050
+ number: /^[\d\s.,+-]+$/,
1051
+ phone: /^[+\d][\d\s().-]{6,}$/
1052
+ };
1053
+ var ExcludeFilter = class {
1054
+ constructor(patterns = {}, minTextLength = 2) {
1055
+ __publicField(this, "patterns");
1056
+ __publicField(this, "minTextLength");
1057
+ this.patterns = { ...DEFAULT_PATTERNS, ...patterns };
1058
+ this.minTextLength = minTextLength;
1059
+ }
1060
+ /**
1061
+ * Check if text should be excluded from translation
1062
+ */
1063
+ shouldExclude(text) {
1064
+ const trimmed = text.trim();
1065
+ if (trimmed.length < this.minTextLength) {
1066
+ return true;
1067
+ }
1068
+ if (this.patterns.emails && PATTERNS.email.test(trimmed)) {
1069
+ return true;
1070
+ }
1071
+ if (this.patterns.urls && PATTERNS.url.test(trimmed)) {
1072
+ return true;
1073
+ }
1074
+ if (this.patterns.numbers && PATTERNS.number.test(trimmed)) {
1075
+ return true;
1076
+ }
1077
+ if (this.patterns.phones && PATTERNS.phone.test(trimmed)) {
1078
+ return true;
1079
+ }
1080
+ return false;
1081
+ }
1082
+ };
1083
+
1084
+ // src/browser/block-extractor.ts
1085
+ var BLOCK_SELECTOR3 = "p,h1,h2,h3,h4,h5,h6,li,td,th,label,button,figcaption,dt,dd,summary,caption";
1086
+ var SKIP_TAGS2 = /* @__PURE__ */ new Set([
1087
+ "SCRIPT",
1088
+ "STYLE",
1089
+ "CODE",
1090
+ "PRE",
1091
+ "TEXTAREA",
1092
+ "NOSCRIPT",
1093
+ "SVG"
1094
+ ]);
1095
+ var SKIP_ATTRIBUTES = [
1096
+ "data-lionrapid-translated",
1097
+ // Server already translated
1098
+ "data-lionrapid-client",
1099
+ // Client already translated
1100
+ "data-no-translate"
1101
+ // Explicitly marked to skip
1102
+ ];
1103
+ var BlockExtractor = class {
1104
+ constructor(excludePatterns = {}, excludeSelectors = [], minTextLength = 2) {
1105
+ __publicField(this, "excludeFilter");
1106
+ __publicField(this, "excludeSelectors");
1107
+ this.excludeFilter = new ExcludeFilter(excludePatterns, minTextLength);
1108
+ this.excludeSelectors = excludeSelectors;
1109
+ }
1110
+ /**
1111
+ * Extract all translatable block elements from a root.
1112
+ * @param includeTranslated - If true, include elements already marked as translated (for editor mode)
1113
+ */
1114
+ extract(root, includeTranslated = false) {
1115
+ const results = [];
1116
+ const descendants = root.querySelectorAll(BLOCK_SELECTOR3);
1117
+ const elements = [];
1118
+ if (root instanceof Element && root.matches(BLOCK_SELECTOR3)) {
1119
+ elements.push(root);
1120
+ }
1121
+ for (let i = 0; i < descendants.length; i++) {
1122
+ elements.push(descendants[i]);
1123
+ }
1124
+ for (let i = 0; i < elements.length; i++) {
1125
+ const el = elements[i];
1126
+ if (this.isInsideSkipTag(el)) {
1127
+ continue;
1128
+ }
1129
+ if (!includeTranslated && this.hasSkipAttribute(el)) {
1130
+ continue;
1131
+ }
1132
+ if (this.matchesExcludeSelector(el)) {
1133
+ continue;
1134
+ }
1135
+ if (el.getAttribute("translate") === "no") {
1136
+ continue;
1137
+ }
1138
+ if (el.querySelector(BLOCK_SELECTOR3)) {
1139
+ continue;
1140
+ }
1141
+ const lrBlock = el.getAttribute("data-lr-block");
1142
+ const lrOriginal = el.getAttribute("data-lr-original");
1143
+ let unit;
1144
+ if (lrOriginal) {
1145
+ const temp = document.createElement(el.tagName);
1146
+ temp.innerHTML = lrOriginal;
1147
+ unit = parseBlockElement(temp);
1148
+ } else {
1149
+ unit = parseBlockElement(el);
1150
+ }
1151
+ const strippedText = unit.text.replace(/<\/?[0-9]+\/?>/g, "");
1152
+ if (this.excludeFilter.shouldExclude(strippedText)) {
1153
+ continue;
1154
+ }
1155
+ let key;
1156
+ if (lrBlock) {
1157
+ key = lrBlock;
1158
+ } else {
1159
+ const dataI18n = el.getAttribute("data-i18n");
1160
+ if (dataI18n) {
1161
+ key = dataI18n.includes(":") ? dataI18n : `common:${dataI18n}`;
1162
+ } else {
1163
+ key = generateAutoKey(unit.text);
1164
+ }
1165
+ }
1166
+ results.push({ element: el, unit, key });
1167
+ }
1168
+ return results;
1169
+ }
1170
+ isInsideSkipTag(element) {
1171
+ let current = element.parentElement;
1172
+ while (current) {
1173
+ if (SKIP_TAGS2.has(current.tagName)) {
1174
+ return true;
1175
+ }
1176
+ current = current.parentElement;
1177
+ }
1178
+ return false;
1179
+ }
1180
+ hasSkipAttribute(element) {
1181
+ let current = element;
1182
+ while (current) {
1183
+ for (const attr of SKIP_ATTRIBUTES) {
1184
+ if (current.hasAttribute(attr)) {
1185
+ return true;
1186
+ }
1187
+ }
1188
+ current = current.parentElement;
1189
+ }
1190
+ return false;
1191
+ }
1192
+ matchesExcludeSelector(element) {
1193
+ for (const selector of this.excludeSelectors) {
1194
+ try {
1195
+ if (element.matches(selector) || element.closest(selector)) {
1196
+ return true;
1197
+ }
1198
+ } catch {
1199
+ }
1200
+ }
1201
+ return false;
1202
+ }
1203
+ };
1204
+
1205
+ // src/browser/editor/sidebar.ts
1206
+ var htmlSerializer2 = new HTMLSerializer();
1207
+ var Sidebar = class {
1208
+ constructor(shadowRoot, api, locale, namespace, defaultLocale, onBlockClick) {
1209
+ __publicField(this, "el");
1210
+ __publicField(this, "bodyEl");
1211
+ __publicField(this, "statusEl");
1212
+ __publicField(this, "api");
1213
+ __publicField(this, "locale");
1214
+ __publicField(this, "namespace");
1215
+ __publicField(this, "defaultLocale");
1216
+ __publicField(this, "current", null);
1217
+ __publicField(this, "blockExtractor");
1218
+ __publicField(this, "onBlockClick");
1219
+ __publicField(this, "statusMap", /* @__PURE__ */ new Map());
1220
+ __publicField(this, "unsavedKeys", /* @__PURE__ */ new Set());
1221
+ __publicField(this, "onProgressUpdate", null);
1222
+ __publicField(this, "lastFetchTime", 0);
1223
+ __publicField(this, "lastFlushTime", 0);
1224
+ this.api = api;
1225
+ this.locale = locale;
1226
+ this.namespace = namespace;
1227
+ this.defaultLocale = defaultLocale;
1228
+ this.onBlockClick = onBlockClick;
1229
+ this.blockExtractor = new BlockExtractor();
1230
+ this.el = document.createElement("div");
1231
+ this.el.className = "lr-sidebar";
1232
+ const resizeHandle = document.createElement("div");
1233
+ resizeHandle.className = "lr-sidebar-resize";
1234
+ this.initResize(resizeHandle);
1235
+ this.el.appendChild(resizeHandle);
1236
+ const header = document.createElement("div");
1237
+ header.className = "lr-sidebar-header";
1238
+ const title = document.createElement("span");
1239
+ title.className = "lr-sidebar-title";
1240
+ title.textContent = "Edit Translation";
1241
+ header.appendChild(title);
1242
+ const closeBtn = document.createElement("button");
1243
+ closeBtn.className = "lr-sidebar-close";
1244
+ closeBtn.innerHTML = "&times;";
1245
+ closeBtn.addEventListener("click", () => this.close());
1246
+ header.appendChild(closeBtn);
1247
+ this.el.appendChild(header);
1248
+ this.bodyEl = document.createElement("div");
1249
+ this.bodyEl.className = "lr-sidebar-body";
1250
+ this.el.appendChild(this.bodyEl);
1251
+ this.statusEl = document.createElement("div");
1252
+ this.statusEl.className = "lr-status";
1253
+ this.api.setStatusCallback((status, message) => {
1254
+ this.statusEl.textContent = message || status;
1255
+ this.statusEl.className = `lr-status ${status}`;
1256
+ });
1257
+ this.api.setFlushedCallback(async (keys) => {
1258
+ this.lastFlushTime = Date.now();
1259
+ const freshStatuses = await this.api.fetchStatus(keys, this.locale, this.namespace);
1260
+ for (const [key, status] of Object.entries(freshStatuses)) {
1261
+ this.statusMap.set(key, status);
1262
+ this.unsavedKeys.delete(key);
1263
+ }
1264
+ this.refreshBlocksList();
1265
+ this.emitProgress();
1266
+ });
1267
+ shadowRoot.appendChild(this.el);
1268
+ }
1269
+ open(resolved) {
1270
+ this.current = resolved;
1271
+ this.el.classList.add("open");
1272
+ this.renderContent(resolved);
1273
+ }
1274
+ close() {
1275
+ this.current = null;
1276
+ this.el.classList.remove("open");
1277
+ }
1278
+ isOpen() {
1279
+ return this.el.classList.contains("open");
1280
+ }
1281
+ updateLocale(locale) {
1282
+ this.locale = locale;
1283
+ this.statusMap.clear();
1284
+ this.unsavedKeys.clear();
1285
+ }
1286
+ setProgressCallback(cb) {
1287
+ this.onProgressUpdate = cb;
1288
+ }
1289
+ /**
1290
+ * Hydrate the status map with server data for all page blocks.
1291
+ * Called once on editor init.
1292
+ */
1293
+ async hydrateStatus() {
1294
+ const blocks = this.blockExtractor.extract(document.body, true);
1295
+ const keys = blocks.map((b) => b.key);
1296
+ if (keys.length === 0) return;
1297
+ const statuses = await this.api.fetchStatus(keys, this.locale, this.namespace);
1298
+ for (const [key, status] of Object.entries(statuses)) {
1299
+ this.statusMap.set(key, status);
1300
+ }
1301
+ for (const block of blocks) {
1302
+ const status = this.statusMap.get(block.key);
1303
+ if (status && status.text && !status.isReady) {
1304
+ applyLivePreview(block.element, status.text, block.unit.styles, block.key, this.locale, this.namespace);
1305
+ }
1306
+ }
1307
+ this.emitProgress();
1308
+ }
1309
+ getTranslatedCount() {
1310
+ let count = 0;
1311
+ for (const status of this.statusMap.values()) {
1312
+ if (status && status.rowState !== "empty") count++;
1313
+ }
1314
+ return count;
1315
+ }
1316
+ getTotalCount() {
1317
+ return this.blockExtractor.extract(document.body, true).length;
1318
+ }
1319
+ emitProgress() {
1320
+ if (this.onProgressUpdate) {
1321
+ this.onProgressUpdate(this.getTranslatedCount(), this.getTotalCount());
1322
+ }
1323
+ }
1324
+ destroy() {
1325
+ this.el.remove();
1326
+ }
1327
+ async renderContent(resolved) {
1328
+ const { key, unit, element } = resolved;
1329
+ this.bodyEl.innerHTML = "";
1330
+ const isStale = this.lastFlushTime > this.lastFetchTime || this.lastFetchTime === 0;
1331
+ if (isStale) {
1332
+ const blocks = this.blockExtractor.extract(document.body, true);
1333
+ const allKeys = blocks.map((b) => b.key);
1334
+ if (!allKeys.includes(key)) allKeys.push(key);
1335
+ const freshStatuses = await this.api.fetchStatus(allKeys, this.locale, this.namespace);
1336
+ for (const [k, s] of Object.entries(freshStatuses)) {
1337
+ this.statusMap.set(k, s);
1338
+ }
1339
+ this.lastFetchTime = Date.now();
1340
+ } else {
1341
+ const freshStatus = await this.api.fetchStatus([key], this.locale, this.namespace);
1342
+ if (freshStatus[key] !== void 0) {
1343
+ this.statusMap.set(key, freshStatus[key]);
1344
+ }
1345
+ }
1346
+ const status = this.statusMap.get(key) ?? null;
1347
+ this.addField("Key", () => {
1348
+ const keyEl = document.createElement("div");
1349
+ keyEl.className = "lr-field-key";
1350
+ keyEl.textContent = key;
1351
+ return keyEl;
1352
+ });
1353
+ this.addField("Original", () => {
1354
+ const origEl = document.createElement("div");
1355
+ origEl.className = "lr-field-original";
1356
+ origEl.innerHTML = this.renderTextWithTags(unit.text);
1357
+ return origEl;
1358
+ });
1359
+ const isSourceLocale = this.locale === this.defaultLocale;
1360
+ const isLocked = isSourceLocale || status?.isLocked || status?.isReady || false;
1361
+ const textarea = document.createElement("textarea");
1362
+ this.addField("Translation", () => {
1363
+ textarea.className = "lr-textarea";
1364
+ textarea.value = status?.text || resolved.translatedUnit?.text || "";
1365
+ textarea.placeholder = unit.text;
1366
+ if (isLocked) {
1367
+ textarea.readOnly = true;
1368
+ textarea.classList.add("lr-textarea-locked");
1369
+ textarea.title = isSourceLocale ? "Source language \u2014 switch to a target language to edit" : status?.isReady ? "Approved for delivery \u2014 read only" : "Locked \u2014 read only";
1370
+ }
1371
+ return textarea;
1372
+ });
1373
+ this.bodyEl.appendChild(this.statusEl);
1374
+ const previewEl = document.createElement("div");
1375
+ this.addField("Preview", () => {
1376
+ previewEl.className = "lr-preview";
1377
+ if (textarea.value) {
1378
+ previewEl.innerHTML = this.renderPreview(textarea.value, unit.styles);
1379
+ }
1380
+ return previewEl;
1381
+ });
1382
+ if (!isLocked) {
1383
+ textarea.addEventListener("input", () => {
1384
+ const text = textarea.value;
1385
+ previewEl.innerHTML = this.renderPreview(text, unit.styles);
1386
+ if (text) {
1387
+ applyLivePreview(element, text, unit.styles, key, this.locale, this.namespace);
1388
+ }
1389
+ this.unsavedKeys.add(key);
1390
+ this.api.queueSave({
1391
+ key,
1392
+ locale: this.locale,
1393
+ namespace: this.namespace,
1394
+ text,
1395
+ styles: unit.styles
1396
+ });
1397
+ });
1398
+ }
1399
+ this.renderBlocksList();
1400
+ if (!isLocked) {
1401
+ setTimeout(() => textarea.focus(), 100);
1402
+ }
1403
+ }
1404
+ addField(label, contentFn) {
1405
+ const field = document.createElement("div");
1406
+ field.className = "lr-field";
1407
+ const labelEl = document.createElement("span");
1408
+ labelEl.className = "lr-field-label";
1409
+ labelEl.textContent = label;
1410
+ field.appendChild(labelEl);
1411
+ field.appendChild(contentFn());
1412
+ this.bodyEl.appendChild(field);
1413
+ }
1414
+ /**
1415
+ * Get CSS class for the status dot based on actual row state.
1416
+ */
1417
+ getDotClass(key) {
1418
+ if (this.unsavedKeys.has(key)) return "unsaved";
1419
+ const s = this.statusMap.get(key);
1420
+ if (!s) return "missing";
1421
+ if (s.isLocked) return "locked";
1422
+ if (s.isReady) return "ready";
1423
+ if (s.isConfirmed) return "confirmed";
1424
+ if (s.rowState === "edited") return "edited";
1425
+ if (s.rowState === "machine_translated") return "mt";
1426
+ return "missing";
1427
+ }
1428
+ renderTextWithTags(text) {
1429
+ return text.replace(
1430
+ /<(\/?)?(\d+)(\/)?>/g,
1431
+ (match) => `<span class="lr-field-tag">${match}</span>`
1432
+ );
1433
+ }
1434
+ renderPreview(text, styles) {
1435
+ if (!text) return "";
1436
+ if (Object.keys(styles).length > 0) {
1437
+ const unit = { text, styles };
1438
+ return htmlSerializer2.serialize(unit);
1439
+ }
1440
+ return text;
1441
+ }
1442
+ initResize(handle) {
1443
+ let startX = 0;
1444
+ let startWidth = 0;
1445
+ const onMouseMove = (e) => {
1446
+ const newWidth = startWidth + (startX - e.clientX);
1447
+ const clamped = Math.max(280, Math.min(600, newWidth));
1448
+ this.el.style.width = `${clamped}px`;
1449
+ };
1450
+ const onMouseUp = () => {
1451
+ handle.classList.remove("active");
1452
+ document.removeEventListener("mousemove", onMouseMove);
1453
+ document.removeEventListener("mouseup", onMouseUp);
1454
+ };
1455
+ handle.addEventListener("mousedown", (e) => {
1456
+ e.preventDefault();
1457
+ startX = e.clientX;
1458
+ startWidth = this.el.offsetWidth;
1459
+ handle.classList.add("active");
1460
+ document.addEventListener("mousemove", onMouseMove);
1461
+ document.addEventListener("mouseup", onMouseUp);
1462
+ });
1463
+ }
1464
+ refreshBlocksList() {
1465
+ const existing = this.bodyEl.querySelector(".lr-blocks-section");
1466
+ if (existing) existing.remove();
1467
+ this.renderBlocksList();
1468
+ }
1469
+ renderBlocksList() {
1470
+ const section = document.createElement("div");
1471
+ section.className = "lr-blocks-section";
1472
+ const blocks = this.blockExtractor.extract(document.body, true);
1473
+ const translatedCount = this.getTranslatedCount();
1474
+ const title = document.createElement("div");
1475
+ title.className = "lr-blocks-title";
1476
+ title.textContent = `Blocks on this page (${translatedCount}/${blocks.length} translated)`;
1477
+ section.appendChild(title);
1478
+ for (const block of blocks) {
1479
+ const item = document.createElement("div");
1480
+ item.className = "lr-block-item";
1481
+ const isActive = this.current && block.element === this.current.element;
1482
+ if (isActive) item.classList.add("active");
1483
+ const dot = document.createElement("span");
1484
+ dot.className = "lr-status-dot";
1485
+ dot.classList.add(this.getDotClass(block.key));
1486
+ item.appendChild(dot);
1487
+ const textWrap = document.createElement("div");
1488
+ textWrap.className = "lr-block-text";
1489
+ const displayText = block.unit.text.replace(/<\/?[0-9]+\/?>/g, "");
1490
+ const sourceSpan = document.createElement("div");
1491
+ sourceSpan.className = "lr-block-source";
1492
+ sourceSpan.textContent = displayText.length > 60 ? displayText.slice(0, 60) + "..." : displayText;
1493
+ sourceSpan.title = displayText;
1494
+ textWrap.appendChild(sourceSpan);
1495
+ const blockStatus = this.statusMap.get(block.key);
1496
+ if (blockStatus && blockStatus.text) {
1497
+ const previewSpan = document.createElement("div");
1498
+ previewSpan.className = "lr-block-preview";
1499
+ const previewText = blockStatus.text.replace(/<\/?[0-9]+\/?>/g, "");
1500
+ previewSpan.textContent = previewText.length > 50 ? previewText.slice(0, 50) + "..." : previewText;
1501
+ textWrap.appendChild(previewSpan);
1502
+ }
1503
+ item.appendChild(textWrap);
1504
+ item.addEventListener("click", () => {
1505
+ block.element.scrollIntoView({ behavior: "smooth", block: "center" });
1506
+ this.onBlockClick({ key: block.key, unit: block.unit, translatedUnit: null, element: block.element });
1507
+ });
1508
+ section.appendChild(item);
1509
+ }
1510
+ this.bodyEl.appendChild(section);
1511
+ }
1512
+ };
1513
+
1514
+ // src/browser/editor/editor-api.ts
1515
+ var EditorAPI = class {
1516
+ constructor(apiBase, sessionToken, projectId) {
1517
+ __publicField(this, "queue", []);
1518
+ __publicField(this, "apiBase");
1519
+ __publicField(this, "sessionToken");
1520
+ __publicField(this, "projectId");
1521
+ __publicField(this, "flushInterval", null);
1522
+ __publicField(this, "renewalInterval", null);
1523
+ __publicField(this, "onStatus", () => {
1524
+ });
1525
+ __publicField(this, "onFlushed", () => {
1526
+ });
1527
+ __publicField(this, "onSessionExpired", null);
1528
+ __publicField(this, "statusAbort", null);
1529
+ __publicField(this, "handleVisibility", () => {
1530
+ if (document.visibilityState === "hidden") {
1531
+ this.flush();
1532
+ }
1533
+ });
1534
+ this.apiBase = apiBase.replace(/\/$/, "");
1535
+ this.sessionToken = sessionToken;
1536
+ this.projectId = projectId;
1537
+ }
1538
+ setStatusCallback(cb) {
1539
+ this.onStatus = cb;
1540
+ }
1541
+ setFlushedCallback(cb) {
1542
+ this.onFlushed = cb;
1543
+ }
1544
+ setSessionExpiredCallback(cb) {
1545
+ this.onSessionExpired = cb;
1546
+ }
1547
+ /**
1548
+ * Start auto-flush (30s interval + visibilitychange) and session renewal (60s check)
1549
+ */
1550
+ startAutoFlush() {
1551
+ this.flushInterval = setInterval(() => this.flush(), 3e4);
1552
+ document.addEventListener("visibilitychange", this.handleVisibility);
1553
+ this.renewalInterval = setInterval(() => this.maybeRenewSession(), 6e4);
1554
+ }
1555
+ /**
1556
+ * Stop auto-flush and flush remaining items
1557
+ */
1558
+ async stopAndFlush() {
1559
+ if (this.flushInterval) {
1560
+ clearInterval(this.flushInterval);
1561
+ this.flushInterval = null;
1562
+ }
1563
+ if (this.renewalInterval) {
1564
+ clearInterval(this.renewalInterval);
1565
+ this.renewalInterval = null;
1566
+ }
1567
+ document.removeEventListener("visibilitychange", this.handleVisibility);
1568
+ await this.flush();
1569
+ }
1570
+ /**
1571
+ * Queue a translation for saving
1572
+ */
1573
+ queueSave(item) {
1574
+ const idx = this.queue.findIndex(
1575
+ (q) => q.key === item.key && q.locale === item.locale && q.namespace === item.namespace
1576
+ );
1577
+ if (idx >= 0) {
1578
+ this.queue[idx] = item;
1579
+ } else {
1580
+ this.queue.push(item);
1581
+ }
1582
+ this.onStatus("unsaved", `${this.queue.length} unsaved`);
1583
+ if (this.queue.length > 50) {
1584
+ this.flush();
1585
+ }
1586
+ }
1587
+ /**
1588
+ * Flush all queued translations to the server
1589
+ */
1590
+ async flush() {
1591
+ if (this.queue.length === 0) return;
1592
+ const items = [...this.queue];
1593
+ this.onStatus("saving");
1594
+ try {
1595
+ const response = await fetch(
1596
+ `${this.apiBase}/api/integrations/targets/batch`,
1597
+ {
1598
+ method: "PUT",
1599
+ headers: {
1600
+ "Content-Type": "application/json",
1601
+ "Authorization": `Bearer ${this.sessionToken}`
1602
+ },
1603
+ body: JSON.stringify({
1604
+ translations: items.map((t) => ({
1605
+ key: `${t.locale}:${t.namespace}:${t.key}`,
1606
+ value: { text: t.text, styles: t.styles }
1607
+ }))
1608
+ })
1609
+ }
1610
+ );
1611
+ if (response.status === 401) {
1612
+ this.handleSessionExpired();
1613
+ return;
1614
+ }
1615
+ if (!response.ok) {
1616
+ throw new Error(`HTTP ${response.status}`);
1617
+ }
1618
+ const flushedKeys = items.map((i) => i.key);
1619
+ for (const item of items) {
1620
+ const idx = this.queue.indexOf(item);
1621
+ if (idx >= 0) this.queue.splice(idx, 1);
1622
+ }
1623
+ this.onFlushed(flushedKeys);
1624
+ if (this.queue.length === 0) {
1625
+ this.onStatus("saved", "All changes saved");
1626
+ } else {
1627
+ this.onStatus("unsaved", `${this.queue.length} unsaved`);
1628
+ }
1629
+ } catch (err) {
1630
+ this.onStatus("error", `Save failed: ${err.message}`);
1631
+ }
1632
+ }
1633
+ /**
1634
+ * Fetch translation status for a batch of keys (includes unconfirmed rows).
1635
+ * Cancels any in-flight status request. Caps at 200 keys per request.
1636
+ */
1637
+ async fetchStatus(keys, locale, namespace) {
1638
+ if (keys.length === 0) return {};
1639
+ if (this.statusAbort) this.statusAbort.abort();
1640
+ this.statusAbort = new AbortController();
1641
+ const batch = keys.slice(0, 200);
1642
+ const params = new URLSearchParams({ locale, namespace, keys: batch.join(",") });
1643
+ try {
1644
+ const response = await fetch(
1645
+ `${this.apiBase}/api/integrations/targets/status?${params}`,
1646
+ {
1647
+ headers: { "Authorization": `Bearer ${this.sessionToken}` },
1648
+ signal: this.statusAbort.signal
1649
+ }
1650
+ );
1651
+ if (response.status === 401) {
1652
+ this.handleSessionExpired();
1653
+ return {};
1654
+ }
1655
+ if (!response.ok) return {};
1656
+ const data = await response.json();
1657
+ return data.translations;
1658
+ } catch (err) {
1659
+ if (err.name === "AbortError") return {};
1660
+ return {};
1661
+ }
1662
+ }
1663
+ get pendingCount() {
1664
+ return this.queue.length;
1665
+ }
1666
+ // ============================================
1667
+ // Session renewal
1668
+ // ============================================
1669
+ /**
1670
+ * Check if session is within 2min of expiry, and renew if so.
1671
+ */
1672
+ async maybeRenewSession() {
1673
+ const exp = this.getSessionExp();
1674
+ if (!exp) return;
1675
+ const timeLeft = exp * 1e3 - Date.now();
1676
+ if (timeLeft > 12e4) return;
1677
+ await this.renewSession();
1678
+ }
1679
+ async renewSession() {
1680
+ try {
1681
+ const response = await fetch(`${this.apiBase}/api/editor/renew`, {
1682
+ method: "POST",
1683
+ headers: {
1684
+ "Authorization": `Bearer ${this.sessionToken}`
1685
+ }
1686
+ });
1687
+ if (response.status === 401) {
1688
+ this.handleSessionExpired();
1689
+ return;
1690
+ }
1691
+ if (!response.ok) return;
1692
+ const data = await response.json();
1693
+ this.sessionToken = data.session_token;
1694
+ try {
1695
+ const raw = this.sessionToken.startsWith("es_") ? this.sessionToken.slice(3) : this.sessionToken;
1696
+ const parts = raw.split(".");
1697
+ if (parts.length === 3) {
1698
+ const payload = JSON.parse(atob(parts[1]));
1699
+ sessionStorage.setItem("__lr_editor_session", JSON.stringify({
1700
+ token: this.sessionToken,
1701
+ exp: payload.exp || 0
1702
+ }));
1703
+ }
1704
+ } catch {
1705
+ }
1706
+ } catch {
1707
+ }
1708
+ }
1709
+ getSessionExp() {
1710
+ try {
1711
+ const raw = this.sessionToken.startsWith("es_") ? this.sessionToken.slice(3) : this.sessionToken;
1712
+ const parts = raw.split(".");
1713
+ if (parts.length !== 3) return null;
1714
+ const payload = JSON.parse(atob(parts[1]));
1715
+ return payload.exp || null;
1716
+ } catch {
1717
+ return null;
1718
+ }
1719
+ }
1720
+ handleSessionExpired() {
1721
+ this.onStatus("error", "Session expired. Please reopen the editor from the dashboard.");
1722
+ if (this.onSessionExpired) {
1723
+ this.onSessionExpired();
1724
+ }
1725
+ }
1726
+ };
1727
+
1728
+ // src/browser/editor/editor-init.ts
1729
+ var SESSION_KEY = "__lr_editor_session";
1730
+ function getStoredSession() {
1731
+ try {
1732
+ const raw = sessionStorage.getItem(SESSION_KEY);
1733
+ if (!raw) return null;
1734
+ const parsed = JSON.parse(raw);
1735
+ if (parsed.exp * 1e3 < Date.now() + 3e4) {
1736
+ sessionStorage.removeItem(SESSION_KEY);
1737
+ return null;
1738
+ }
1739
+ return parsed.token;
1740
+ } catch {
1741
+ return null;
1742
+ }
1743
+ }
1744
+ function storeSession(token) {
1745
+ try {
1746
+ const raw = token.startsWith("es_") ? token.slice(3) : token;
1747
+ const parts = raw.split(".");
1748
+ if (parts.length !== 3) return;
1749
+ const payload = JSON.parse(atob(parts[1]));
1750
+ const session = { token, exp: payload.exp || 0 };
1751
+ sessionStorage.setItem(SESSION_KEY, JSON.stringify(session));
1752
+ } catch {
1753
+ }
1754
+ }
1755
+ function clearSession() {
1756
+ sessionStorage.removeItem(SESSION_KEY);
1757
+ }
1758
+ function initEditor() {
1759
+ const scriptEl = document.querySelector("script[data-grant]");
1760
+ if (!scriptEl) {
1761
+ console.warn("[LionRapid Editor] No script tag with data-grant found");
1762
+ return;
1763
+ }
1764
+ const grant = scriptEl.dataset.grant || "";
1765
+ const locale = scriptEl.dataset.locale || "";
1766
+ const apiBase = scriptEl.dataset.api || "https://api.lionrapid.com";
1767
+ const projectId = scriptEl.dataset.project || "";
1768
+ let attempts = 0;
1769
+ const maxAttempts = 100;
1770
+ const poll = setInterval(() => {
1771
+ attempts++;
1772
+ const lr = window.LionRapid;
1773
+ if (lr) {
1774
+ clearInterval(poll);
1775
+ activate(lr, grant, locale, apiBase, projectId);
1776
+ } else if (attempts >= maxAttempts) {
1777
+ clearInterval(poll);
1778
+ console.warn("[LionRapid Editor] Timed out waiting for window.LionRapid");
1779
+ }
1780
+ }, 100);
1781
+ }
1782
+ async function activate(lr, grant, locale, apiBase, projectId) {
1783
+ const config = window.__lionrapidConfig;
1784
+ const languages = config?.languages || [locale];
1785
+ const namespace = config?.namespace || "default";
1786
+ const defaultLocale = config?.defaultLocale || "en";
1787
+ const currentLocale = locale || lr.getCurrentLocale();
1788
+ const normalizedApiBase = apiBase.replace(/\/$/, "");
1789
+ let sessionToken = getStoredSession();
1790
+ if (!sessionToken && grant) {
1791
+ try {
1792
+ const res = await fetch(`${normalizedApiBase}/api/editor/exchange`, {
1793
+ method: "POST",
1794
+ headers: { "Content-Type": "application/json" },
1795
+ body: JSON.stringify({ grant, domain: window.location.host })
1796
+ });
1797
+ if (!res.ok) {
1798
+ console.error("[LionRapid Editor] Grant exchange failed:", res.status);
1799
+ return;
1800
+ }
1801
+ const data = await res.json();
1802
+ sessionToken = data.session_token;
1803
+ if (sessionToken) {
1804
+ storeSession(sessionToken);
1805
+ }
1806
+ } catch (err) {
1807
+ console.error("[LionRapid Editor] Grant exchange error:", err);
1808
+ return;
1809
+ }
1810
+ }
1811
+ if (!sessionToken) {
1812
+ console.error("[LionRapid Editor] No valid session. Please reopen the editor from the dashboard.");
1813
+ return;
1814
+ }
1815
+ const host = document.createElement("div");
1816
+ host.id = "__lionrapid-editor";
1817
+ host.setAttribute("data-no-translate", "");
1818
+ document.body.appendChild(host);
1819
+ const shadow = host.attachShadow({ mode: "closed" });
1820
+ const styleEl = document.createElement("style");
1821
+ styleEl.textContent = EDITOR_CSS;
1822
+ shadow.appendChild(styleEl);
1823
+ const switcherEl = document.querySelector("[data-lionrapid-switcher]");
1824
+ if (switcherEl) switcherEl.style.display = "none";
1825
+ const api = new EditorAPI(normalizedApiBase, sessionToken, projectId);
1826
+ api.startAutoFlush();
1827
+ const handleBlockSelect = (resolved) => {
1828
+ sidebar.open(resolved);
1829
+ inspector.clearHighlight();
1830
+ };
1831
+ const sidebar = new Sidebar(shadow, api, currentLocale, namespace, defaultLocale, handleBlockSelect);
1832
+ const inspector = new Inspector(shadow, handleBlockSelect);
1833
+ const toolbar = new Toolbar(
1834
+ shadow,
1835
+ languages,
1836
+ currentLocale,
1837
+ // Language change
1838
+ async (newLocale) => {
1839
+ sidebar.updateLocale(newLocale);
1840
+ sidebar.close();
1841
+ inspector.clearHighlight();
1842
+ await lr.changeLanguage(newLocale);
1843
+ await sidebar.hydrateStatus();
1844
+ },
1845
+ // Exit
1846
+ async () => {
1847
+ await api.stopAndFlush();
1848
+ inspector.destroy();
1849
+ toolbar.destroy();
1850
+ sidebar.destroy();
1851
+ host.remove();
1852
+ clearSession();
1853
+ }
1854
+ );
1855
+ sidebar.setProgressCallback((translated, total) => {
1856
+ toolbar.updateProgress(translated, total);
1857
+ });
1858
+ sidebar.hydrateStatus().then(() => {
1859
+ toolbar.updateProgress(sidebar.getTranslatedCount(), sidebar.getTotalCount());
1860
+ });
1861
+ const url = new URL(window.location.href);
1862
+ if (url.searchParams.has("__lr_editor")) {
1863
+ url.searchParams.delete("__lr_editor");
1864
+ history.replaceState(null, "", url.toString());
1865
+ }
1866
+ }
1867
+ initEditor();
1868
+ })();
1869
+ //# sourceMappingURL=lionrapid.editor.js.map