@heycys/gallery 0.1.2 → 0.2.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 (292) hide show
  1. package/THIRD-PARTY-NOTICES.md +2 -2
  2. package/browser/editor/components/align.mjs +58 -0
  3. package/browser/editor/components/code/index.mjs +1 -1
  4. package/browser/editor/components/embed/page.mjs +1 -1
  5. package/browser/editor/components/file/page.mjs +4 -4
  6. package/browser/editor/components/heading/index.mjs +7 -6
  7. package/browser/editor/components/icons.mjs +3 -0
  8. package/browser/editor/components/image/index.mjs +2 -0
  9. package/browser/editor/components/image/page.mjs +172 -90
  10. package/browser/editor/components/installed/index.mjs +6 -2
  11. package/browser/editor/components/installed/page.mjs +80 -13
  12. package/browser/editor/components/media/icons.mjs +13 -8
  13. package/browser/editor/components/media/page.mjs +5 -3
  14. package/browser/editor/components/media/toolbar.mjs +12 -3
  15. package/browser/editor/components/paragraph/index.mjs +4 -3
  16. package/browser/editor/components/registry.mjs +17 -5
  17. package/browser/editor/components/table/index.mjs +4 -2
  18. package/browser/editor/page/after/index.mjs +288 -0
  19. package/browser/editor/page/align.mjs +91 -0
  20. package/browser/editor/page/beside.mjs +5 -6
  21. package/browser/editor/page/block-menu/index.mjs +6 -3
  22. package/browser/editor/page/blocks.mjs +3 -1
  23. package/browser/editor/page/board/hint.mjs +64 -27
  24. package/browser/editor/page/board/index.mjs +160 -71
  25. package/browser/editor/page/board/motion.mjs +177 -0
  26. package/browser/editor/page/board/settings.mjs +4 -3
  27. package/browser/editor/page/clipboard/index.mjs +10 -3
  28. package/browser/editor/page/clipboard/own/blocks.mjs +3 -4
  29. package/browser/editor/page/clipboard/own/describe.mjs +6 -2
  30. package/browser/editor/page/commit.mjs +2 -2
  31. package/browser/editor/page/core.mjs +3 -1
  32. package/browser/editor/page/custom-page.mjs +15 -11
  33. package/browser/editor/page/editor.mjs +31 -14
  34. package/browser/editor/page/history.mjs +19 -4
  35. package/browser/editor/page/insert/index.mjs +20 -22
  36. package/browser/editor/page/menu.mjs +15 -7
  37. package/browser/editor/page/page-objects.mjs +3 -7
  38. package/browser/editor/page/present-links.mjs +5 -4
  39. package/browser/editor/page/present-shell.mjs +63 -0
  40. package/browser/editor/page/selection/index.mjs +10 -2
  41. package/browser/editor/page/table/binding.mjs +87 -0
  42. package/browser/editor/page/table/icons.mjs +0 -2
  43. package/browser/editor/page/table/index.mjs +10 -14
  44. package/browser/editor/page/table/views.mjs +5 -52
  45. package/browser/editor/page/table/widths.mjs +82 -10
  46. package/browser/editor/page/typing/index.mjs +44 -12
  47. package/browser/editor/page/typing/leave.mjs +19 -5
  48. package/browser/shared/block-packs.mjs +23 -8
  49. package/browser/shared/component-catalog.mjs +15 -8
  50. package/browser/shared/component-source.mjs +3 -1
  51. package/browser/shared/components/heading.mjs +2 -1
  52. package/browser/shared/components/installed.mjs +1 -1
  53. package/browser/shared/components/page.mjs +2 -0
  54. package/browser/shared/components/paragraph.mjs +8 -1
  55. package/browser/shared/components/table.mjs +1 -0
  56. package/browser/shared/emoji/line-icons.mjs +37 -0
  57. package/browser/shared/http.mjs +9 -0
  58. package/browser/shared/i18n/en.mjs +40 -17
  59. package/browser/shared/outline.mjs +30 -2
  60. package/browser/shared/pack-manifest.mjs +12 -1
  61. package/browser/shared/pages.mjs +169 -57
  62. package/browser/shared/pinyin.mjs +92 -0
  63. package/browser/shared/present-step.mjs +17 -0
  64. package/browser/shared/sidebar-head.mjs +70 -0
  65. package/browser/shared/web-links.mjs +5 -0
  66. package/dist/chunk-2OHANJ6Q.mjs +2 -0
  67. package/dist/{chunk-QOUEFUCH.mjs → chunk-3MBRL2PM.mjs} +1 -1
  68. package/dist/{chunk-NWR2LT5B.mjs → chunk-4OKWZY5E.mjs} +1 -1
  69. package/dist/{chunk-RMECZA2K.mjs → chunk-5K44HLTZ.mjs} +1 -1
  70. package/dist/{chunk-NFMCP2HI.mjs → chunk-5XA2ZBAE.mjs} +2 -2
  71. package/dist/{chunk-OUUP3WKT.mjs → chunk-67N2QJYT.mjs} +1 -1
  72. package/dist/chunk-6KBYRA2C.mjs +2 -0
  73. package/dist/{chunk-SX4LJYT6.mjs → chunk-6N3PX4XX.mjs} +1 -1
  74. package/dist/{chunk-RFDZXFJF.mjs → chunk-7JDX4E65.mjs} +1 -1
  75. package/dist/chunk-7M2BQQGZ.mjs +2 -0
  76. package/dist/{chunk-ZUNVAXLH.mjs → chunk-7OG26PGF.mjs} +1 -1
  77. package/dist/{chunk-B4P2CM2J.mjs → chunk-AHN3NFKJ.mjs} +1 -1
  78. package/dist/chunk-ALZ6ZENZ.mjs +3 -0
  79. package/dist/{chunk-UGWYKHBL.mjs → chunk-AR5D57ZD.mjs} +2 -2
  80. package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
  81. package/dist/{chunk-HLYV7RIH.mjs → chunk-CMA72MAG.mjs} +1 -1
  82. package/dist/{chunk-D4CKRD3V.mjs → chunk-DAZL73VK.mjs} +2 -2
  83. package/dist/{chunk-6VTU6KEO.mjs → chunk-DJISHPUL.mjs} +1 -1
  84. package/dist/{chunk-BPL64HGA.mjs → chunk-EAIFITAV.mjs} +1 -1
  85. package/dist/chunk-EP25HDAJ.mjs +2 -0
  86. package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
  87. package/dist/{chunk-3KJHLHUO.mjs → chunk-F6HVDZR4.mjs} +1 -1
  88. package/dist/{chunk-3NJACR4W.mjs → chunk-FDMAXMQA.mjs} +1 -1
  89. package/dist/chunk-FETSREUN.mjs +5 -0
  90. package/dist/{chunk-RKPWLGZ3.mjs → chunk-GHOYKMQ3.mjs} +1 -1
  91. package/dist/chunk-HDWTZ763.mjs +12 -0
  92. package/dist/{chunk-VTKNEYT3.mjs → chunk-HPOI4ZX7.mjs} +1 -1
  93. package/dist/{chunk-OW5NWPOJ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
  94. package/dist/{chunk-V6RIYQZZ.mjs → chunk-ITOH5EGO.mjs} +1 -1
  95. package/dist/{chunk-ZWPMCK7P.mjs → chunk-IV64FFI4.mjs} +1 -1
  96. package/dist/chunk-J43Z6LSS.mjs +2 -0
  97. package/dist/chunk-J5PUIV3Y.mjs +4 -0
  98. package/dist/{chunk-KTKHRC6H.mjs → chunk-JA7G53PC.mjs} +1 -1
  99. package/dist/{chunk-NWFBE2YA.mjs → chunk-JLTZF4LA.mjs} +1 -1
  100. package/dist/{chunk-GY3FAUVM.mjs → chunk-JZT7I2OJ.mjs} +1 -1
  101. package/dist/{chunk-QKXMPUXC.mjs → chunk-K5XATEOO.mjs} +1 -1
  102. package/dist/chunk-L2SUIS5S.mjs +2 -0
  103. package/dist/{chunk-TEZAWDSQ.mjs → chunk-LM4XFQHZ.mjs} +1 -1
  104. package/dist/{chunk-5ZJA7CXT.mjs → chunk-MN7BZL4R.mjs} +1 -1
  105. package/dist/chunk-MTYYHM2G.mjs +15 -0
  106. package/dist/{chunk-HMYTM3GT.mjs → chunk-MXUBKDRO.mjs} +1 -1
  107. package/dist/{chunk-MHW7ZKOU.mjs → chunk-OEXWRJME.mjs} +1 -1
  108. package/dist/{chunk-CRJEG7MT.mjs → chunk-OFHXGWOX.mjs} +1 -1
  109. package/dist/chunk-ORID3AE7.mjs +166 -0
  110. package/dist/{chunk-32ZUEFZ7.mjs → chunk-PGDE24N5.mjs} +1 -1
  111. package/dist/{chunk-UNOYVIYR.mjs → chunk-POTA2FV4.mjs} +1 -1
  112. package/dist/{chunk-N7AZZLGT.mjs → chunk-QE5RG76S.mjs} +18 -17
  113. package/dist/{chunk-N76HWBA3.mjs → chunk-R2ZDLJYT.mjs} +1 -1
  114. package/dist/chunk-SWUTTZCN.mjs +2 -0
  115. package/dist/{chunk-POQSE4C2.mjs → chunk-TR7TWGHB.mjs} +1 -1
  116. package/dist/chunk-U5XL7D6A.mjs +3 -0
  117. package/dist/{chunk-PVER7LSC.mjs → chunk-U7NZI6N4.mjs} +2 -2
  118. package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
  119. package/dist/chunk-UJQJKNGK.mjs +2 -0
  120. package/dist/chunk-UYVCQK3H.mjs +22 -0
  121. package/dist/{chunk-FJYN7RTH.mjs → chunk-VFX2KNHP.mjs} +1 -1
  122. package/dist/{chunk-UBBRTJ2R.mjs → chunk-VS7JIGJW.mjs} +1 -1
  123. package/dist/{chunk-NVRNLK25.mjs → chunk-W7665ZEC.mjs} +2 -2
  124. package/dist/{chunk-NG375ILQ.mjs → chunk-WQDQWCX5.mjs} +1 -1
  125. package/dist/chunk-YLPSMDIS.mjs +5 -0
  126. package/dist/{chunk-VXACTLO3.mjs → chunk-YNAWBAYN.mjs} +1 -1
  127. package/dist/{chunk-ZB4H7ZN4.mjs → chunk-YROX2O22.mjs} +1 -1
  128. package/dist/{chunk-YQGT4AB2.mjs → chunk-ZDLPUOAG.mjs} +1 -1
  129. package/dist/chunk-ZQS6H473.mjs +16 -0
  130. package/dist/{chunk-OJKMINIU.mjs → chunk-ZWWC4KLF.mjs} +15 -15
  131. package/dist/cli.mjs +1 -1
  132. package/dist/server.mjs +1 -1
  133. package/dist/worker.mjs +1 -1
  134. package/kit/board.css +5 -5
  135. package/kit/code.js +15 -15
  136. package/kit/report.css +120 -41
  137. package/kit/runtime.js +45 -7
  138. package/kit/tables.js +2 -0
  139. package/kit/ui.js +2 -0
  140. package/package.json +1 -1
  141. package/packs/gallery/chart-view/kit.js +3 -3
  142. package/packs/gallery/chart-view/writing.md +1 -1
  143. package/packs/gallery/countdown/component.json +1 -1
  144. package/packs/gallery/countdown/kit.js +89 -29
  145. package/packs/gallery/countdown/preview.svg +1 -1
  146. package/packs/gallery/countdown/style.css +88 -100
  147. package/packs/gallery/countdown/writing.md +2 -2
  148. package/packs/gallery/habit/component.json +3 -3
  149. package/packs/gallery/habit/kit.js +114 -75
  150. package/packs/gallery/habit/preview.svg +1 -1
  151. package/packs/gallery/habit/style.css +133 -205
  152. package/packs/gallery/habit/writing.md +2 -2
  153. package/packs/gallery/heatmap/component.json +2 -2
  154. package/packs/gallery/heatmap/kit.js +335 -173
  155. package/packs/gallery/heatmap/preview.svg +1 -1
  156. package/packs/gallery/heatmap/style.css +158 -170
  157. package/packs/gallery/heatmap/writing.md +2 -2
  158. package/packs/gallery/kanban/kit.js +3 -3
  159. package/packs/gallery/kanban/writing.md +1 -1
  160. package/packs/gallery/metric/kit.js +3 -3
  161. package/packs/gallery/metric/writing.md +1 -1
  162. package/packs/gallery/note/component.json +3 -3
  163. package/packs/gallery/note/kit.js +40 -13
  164. package/packs/gallery/note/preview.svg +1 -1
  165. package/packs/gallery/note/style.css +51 -30
  166. package/packs/gallery/note/writing.md +2 -2
  167. package/packs/gallery/recent/component.json +2 -2
  168. package/packs/gallery/recent/kit.js +141 -86
  169. package/packs/gallery/recent/preview.svg +1 -1
  170. package/packs/gallery/recent/style.css +53 -126
  171. package/packs/gallery/recent/writing.md +2 -2
  172. package/packs/gallery/static/component.json +2 -2
  173. package/packs/gallery/static/preview.svg +1 -1
  174. package/packs/gallery/static/style.css +99 -66
  175. package/packs/gallery/static/writing.md +2 -2
  176. package/packs/gallery/stats/component.json +3 -3
  177. package/packs/gallery/stats/kit.js +253 -72
  178. package/packs/gallery/stats/preview.svg +1 -1
  179. package/packs/gallery/stats/style.css +64 -142
  180. package/packs/gallery/stats/writing.md +2 -2
  181. package/packs/gallery/today/component.json +2 -2
  182. package/packs/gallery/today/kit.js +62 -27
  183. package/packs/gallery/today/preview.svg +1 -1
  184. package/packs/gallery/today/style.css +101 -72
  185. package/packs/gallery/today/writing.md +2 -2
  186. package/packs/gallery/todos/component.json +1 -1
  187. package/packs/gallery/todos/kit.js +218 -71
  188. package/packs/gallery/todos/preview.svg +1 -1
  189. package/packs/gallery/todos/style.css +170 -223
  190. package/packs/gallery/todos/writing.md +1 -1
  191. package/packs/gallery/weather/component.json +2 -2
  192. package/packs/gallery/weather/kit.js +165 -90
  193. package/packs/gallery/weather/preview.svg +1 -1
  194. package/packs/gallery/weather/style.css +100 -218
  195. package/packs/gallery/weather/writing.md +1 -1
  196. package/packs/gallery-soft/countdown/component.json +18 -0
  197. package/packs/gallery-soft/countdown/example.html +3 -0
  198. package/packs/gallery-soft/countdown/kit.js +206 -0
  199. package/packs/gallery-soft/countdown/preview.svg +1 -0
  200. package/packs/gallery-soft/countdown/style.css +211 -0
  201. package/packs/gallery-soft/countdown/template.html +3 -0
  202. package/packs/gallery-soft/countdown/writing.md +24 -0
  203. package/packs/gallery-soft/habit/component.json +14 -0
  204. package/packs/gallery-soft/habit/example.html +3 -0
  205. package/packs/gallery-soft/habit/kit.js +366 -0
  206. package/packs/gallery-soft/habit/preview.svg +1 -0
  207. package/packs/gallery-soft/habit/style.css +251 -0
  208. package/packs/gallery-soft/habit/template.html +3 -0
  209. package/packs/gallery-soft/habit/writing.md +23 -0
  210. package/packs/gallery-soft/heatmap/component.json +18 -0
  211. package/packs/gallery-soft/heatmap/example.html +3 -0
  212. package/packs/gallery-soft/heatmap/kit.js +498 -0
  213. package/packs/gallery-soft/heatmap/preview.svg +1 -0
  214. package/packs/gallery-soft/heatmap/style.css +181 -0
  215. package/packs/gallery-soft/heatmap/template.html +3 -0
  216. package/packs/gallery-soft/heatmap/writing.md +23 -0
  217. package/packs/gallery-soft/note/component.json +14 -0
  218. package/packs/gallery-soft/note/example.html +3 -0
  219. package/packs/gallery-soft/note/kit.js +162 -0
  220. package/packs/gallery-soft/note/preview.svg +1 -0
  221. package/packs/gallery-soft/note/style.css +148 -0
  222. package/packs/gallery-soft/note/template.html +3 -0
  223. package/packs/gallery-soft/note/writing.md +20 -0
  224. package/packs/gallery-soft/recent/component.json +13 -0
  225. package/packs/gallery-soft/recent/example.html +1 -0
  226. package/packs/gallery-soft/recent/kit.js +335 -0
  227. package/packs/gallery-soft/recent/preview.svg +1 -0
  228. package/packs/gallery-soft/recent/style.css +160 -0
  229. package/packs/gallery-soft/recent/template.html +1 -0
  230. package/packs/gallery-soft/recent/writing.md +17 -0
  231. package/packs/gallery-soft/static/component.json +13 -0
  232. package/packs/gallery-soft/static/example.html +12 -0
  233. package/packs/gallery-soft/static/preview.svg +1 -0
  234. package/packs/gallery-soft/static/style.css +186 -0
  235. package/packs/gallery-soft/static/template.html +1 -0
  236. package/packs/gallery-soft/static/writing.md +20 -0
  237. package/packs/gallery-soft/stats/component.json +13 -0
  238. package/packs/gallery-soft/stats/example.html +1 -0
  239. package/packs/gallery-soft/stats/kit.js +341 -0
  240. package/packs/gallery-soft/stats/preview.svg +1 -0
  241. package/packs/gallery-soft/stats/style.css +119 -0
  242. package/packs/gallery-soft/stats/template.html +1 -0
  243. package/packs/gallery-soft/stats/writing.md +13 -0
  244. package/packs/gallery-soft/today/component.json +12 -0
  245. package/packs/gallery-soft/today/example.html +1 -0
  246. package/packs/gallery-soft/today/kit.js +222 -0
  247. package/packs/gallery-soft/today/preview.svg +1 -0
  248. package/packs/gallery-soft/today/style.css +178 -0
  249. package/packs/gallery-soft/today/template.html +1 -0
  250. package/packs/gallery-soft/today/writing.md +16 -0
  251. package/packs/gallery-soft/todos/component.json +19 -0
  252. package/packs/gallery-soft/todos/example.html +3 -0
  253. package/packs/gallery-soft/todos/kit.js +527 -0
  254. package/packs/gallery-soft/todos/preview.svg +1 -0
  255. package/packs/gallery-soft/todos/style.css +212 -0
  256. package/packs/gallery-soft/todos/template.html +3 -0
  257. package/packs/gallery-soft/todos/writing.md +32 -0
  258. package/packs/gallery-soft/weather/component.json +19 -0
  259. package/packs/gallery-soft/weather/example.html +3 -0
  260. package/packs/gallery-soft/weather/kit.js +411 -0
  261. package/packs/gallery-soft/weather/preview.svg +1 -0
  262. package/packs/gallery-soft/weather/style.css +185 -0
  263. package/packs/gallery-soft/weather/template.html +3 -0
  264. package/packs/gallery-soft/weather/writing.md +28 -0
  265. package/release.json +9 -9
  266. package/skill/SKILL.md +12 -12
  267. package/skill/references/building-blocks.md +4 -4
  268. package/skill/references/pages-and-data.md +13 -9
  269. package/skill/references/writing-html.md +2 -0
  270. package/web/assets/index-BM_K-Auu.css +1 -0
  271. package/web/assets/index-DRslOBiv.js +15 -0
  272. package/web/index.html +2 -2
  273. package/dist/chunk-2NRBY6AJ.mjs +0 -2
  274. package/dist/chunk-35NQ4PLQ.mjs +0 -2
  275. package/dist/chunk-4AV456RQ.mjs +0 -2
  276. package/dist/chunk-5XYHDBJ5.mjs +0 -2
  277. package/dist/chunk-6A53MZYZ.mjs +0 -2
  278. package/dist/chunk-6EXH3ORZ.mjs +0 -166
  279. package/dist/chunk-BVEFGE4B.mjs +0 -2
  280. package/dist/chunk-D4EURAX4.mjs +0 -4
  281. package/dist/chunk-DQCE7TJY.mjs +0 -5
  282. package/dist/chunk-FWNRPMCJ.mjs +0 -12
  283. package/dist/chunk-FWW4CSAZ.mjs +0 -2
  284. package/dist/chunk-JVHHHGEA.mjs +0 -3
  285. package/dist/chunk-PZDLJJTJ.mjs +0 -3
  286. package/dist/chunk-SXHBYZST.mjs +0 -16
  287. package/dist/chunk-UFNYFFKO.mjs +0 -20
  288. package/dist/chunk-UKSHDZZ6.mjs +0 -2
  289. package/dist/chunk-VJPFVNNW.mjs +0 -15
  290. package/dist/chunk-VS3ZMVGX.mjs +0 -5
  291. package/web/assets/index-BK2e_07w.js +0 -13
  292. package/web/assets/index-CWFI83mF.css +0 -1
@@ -5,7 +5,7 @@ import { formulaMaker } from "../formula/index.mjs";
5
5
  import { inlineItems, spaceAround } from "../../../shared/inline-spacing.mjs";
6
6
  import { caretAt, editable, hostOf, select } from "../caret.mjs";
7
7
  import { formatRange, joinMarks, outOfMarks, rangeAt, rangeOffsets } from "../marks.mjs";
8
- import { BASIC_UNITS, HOSTS, KIND_NAMES, accepts, clipboardOf, takesFormula, texOnly } from "../../components/registry.mjs";
8
+ import { BASIC_UNITS, HOSTS, KIND_NAMES, accepts, clipboardOf, replacedByFile, takesFormula, texOnly } from "../../components/registry.mjs";
9
9
  import { listType } from "../../components/list/index.mjs";
10
10
  import { nodeAround } from "../../components/flowchart/index.mjs";
11
11
  import { cellsData } from "../../components/table/index.mjs";
@@ -284,7 +284,9 @@ export function createClipboard({ editing, blocks, struct, recordText, say, load
284
284
  // anchor.parent, after })): right after the block of `at` (unitElement: a new code block's code stands
285
285
  // for the block), whole, where a block pasted there goes (placeFor: the first box out that takes it, a
286
286
  // list or quote that does not splitting after the block), one undo step (`put`, handed to the
287
- // component, which makes the block and fills it). Returns whether one did.
287
+ // component, which makes the block and fills it, the fill part of that step). An empty block of a kind
288
+ // that says `replacedByFile` (an empty paragraph: notion-lite's pasteFiles removes an empty text block)
289
+ // gives its place to the block instead of staying before it. Returns whether one did.
288
290
  const fromFiles = (at, files) => {
289
291
  const unit = unitElement(at);
290
292
  if (!unit?.closest("main")) return false;
@@ -294,7 +296,12 @@ export function createClipboard({ editing, blocks, struct, recordText, say, load
294
296
  say(t("这里放不下这种块。"));
295
297
  return false;
296
298
  }
297
- struct(place.parents, [], () => ({ touched: [], focus: null, created: placeAfter(place, [block]) }));
299
+ const replaced = replacedByFile(unit.getAttribute("data-ge-unit-kind")) && removableEmpty(unit, unit) && !isFirstLine(unit);
300
+ struct([...new Set([unit.parentElement, ...place.parents])], [], () => {
301
+ const created = placeAfter(place, [block]);
302
+ if (replaced) removalTarget(unit).remove();
303
+ return { touched: [], focus: null, created };
304
+ });
298
305
  return true;
299
306
  };
300
307
  return pageObjects().some((page) => {
@@ -2,9 +2,8 @@ import { escapeText } from "../../../../shared/escape.mjs";
2
2
  import { heldIn, newContainer, newHost, pastedLines } from "../../core.mjs";
3
3
  import { pageBuild } from "../../../components/builders.mjs";
4
4
  import { BLOCK_TYPES, COPIED_AS_ITSELF, HOLDS_ONE, TEXT_TYPES, TOGGLES, UNSAID, accepts, clipboardHooks, clipboardOf, isLead, sizeValue } from "../../../components/registry.mjs";
5
- import { BLOCK_COLORS } from "../../../components/colors.mjs";
6
5
  import { MAX_CELL_HTML } from "../../../components/table/index.mjs";
7
- import { copied, copyOf } from "./describe.mjs";
6
+ import { BLOCK_LOOKS, copied, copyOf } from "./describe.mjs";
8
7
  import { imageSource, kindOfBlock, webPage } from "./rules.mjs";
9
8
 
10
9
  // Described blocks (describe.mjs and selection.mjs, and import/to-blocks.mjs for other programs' content) made (build),
@@ -42,7 +41,7 @@ export function checkBlocks(blocks) {
42
41
  const { type } = block;
43
42
  const clip = clipboardOf(type);
44
43
  if (box && !accepts(box, kindOfBlock(block)) && clip.pastedIn !== box) return false;
45
- if (block.color !== undefined && (typeof block.color !== "string" || !block.color.split(" ").every((name) => BLOCK_COLORS.includes(name)))) return false;
44
+ if (block.color !== undefined && (typeof block.color !== "string" || !block.color.split(" ").every((name) => BLOCK_LOOKS.includes(name)))) return false;
46
45
  if (TEXT_TYPES.has(type)) return string(block.html);
47
46
  if (type === "copy") return COPIES.has(block.kind) && Number.isInteger(block.unit) && [block.report, block.text, block.html].every((value) => typeof value === "string")
48
47
  && (block.data === undefined || (Boolean(clipboardOf(block.data?.type).data) && valid(block.data)));
@@ -120,7 +119,7 @@ function inline(doc, html, { formula = null, mention = null, picture = null } =
120
119
  }
121
120
 
122
121
 
123
- // A block's colors (`color`: their classes, one of each family), those its kind takes (TOGGLES).
122
+ // A block's colors (`color`: their classes, one of each family, and a text block's alignment and indent), those its kind takes (TOGGLES).
124
123
  function colored(element, block) {
125
124
  for (const name of block.color?.split(" ") ?? []) if (TOGGLES[element.getAttribute("data-ge-unit-kind")]?.includes(name)) element.classList.add(name);
126
125
  return element;
@@ -1,5 +1,6 @@
1
1
  import { isHost, nestedBlocks, serializeInner, unitsOf } from "../../core.mjs";
2
2
  import { BLOCK_TYPES, COPIED_AS_ITSELF, COPY_KEEPS, TEXT_TYPES, askClipboard, clipboardOf, sizeValue, unitClipboard } from "../../../components/registry.mjs";
3
+ import { ALIGN_TOGGLES, TEXT_BLOCKS } from "../../../components/align.mjs";
3
4
  import { BLOCK_COLORS } from "../../../components/colors.mjs";
4
5
  import { containerType, typeOf } from "../../convert.mjs";
5
6
  import { pageOfType, pageOfUnit } from "../../page-objects.mjs";
@@ -72,8 +73,11 @@ export function copied(doc, block) {
72
73
  return (typeof block.data?.html === "string" ? copy.outerHTML === block.html : copy.textContent === block.text) ? unit : null;
73
74
  }
74
75
 
75
- /** A block's colors (their classes, space-separated), or null. */
76
- export const colorOf = (node) => BLOCK_COLORS.filter((name) => node.classList.contains(name)).join(" ") || null;
76
+ /** The classes a block's copy carries as its `color`: its colors, and a paragraph's or a heading's alignment and indent. */
77
+ export const BLOCK_LOOKS = [...BLOCK_COLORS, ...ALIGN_TOGGLES];
78
+
79
+ /** A block's colors, and a text block's alignment and indent (their classes, space-separated), or null. */
80
+ export const colorOf = (node) => (node.matches?.(TEXT_BLOCKS) ? BLOCK_LOOKS : BLOCK_COLORS).filter((name) => node.classList.contains(name)).join(" ") || null;
77
81
 
78
82
  /** The type a text block is copied as: its own, the type of the text blocks of what wraps it (a quote's paragraph), or null (code, whose text is no text block's). */
79
83
  export function copiedType(host) {
@@ -1241,8 +1241,8 @@ export function createCommits({ root, map, baselines, units, boxes, getLayout, s
1241
1241
  else if (element) proposals.set(identityOf(element), id);
1242
1242
  }
1243
1243
  for (const [key, hash] of Object.entries(receipt.hashes ?? {})) if (map.units[Number(key)]) map.units[Number(key)].hash = hash;
1244
- // The keys of tables the file gave the blocks the commit made and the tables it keyed (a view's
1245
- // 视图 ›: the key its frame took, an attribute of the unit), other than the page chose
1244
+ // The keys of tables the file gave the blocks the commit made and the tables it keyed (a view
1245
+ // made from a menu: the key its frame took, an attribute of the unit), other than the page chose
1246
1246
  // (source/chart-keys.mjs: one the file had meanwhile): theirs on the page too, so that each card and
1247
1247
  // view reads the table the file links it to ({ old: new | null }, null: off a table); `rekeyed` says
1248
1248
  // which it changed ([element, name, old, new | null]), for the undo steps that recorded them (history.mjs).
@@ -10,6 +10,7 @@ import { gridOf, tableChange } from "../components/table/grid.mjs";
10
10
  import { HELD_TAGS, heldListType, listType } from "../components/list/index.mjs";
11
11
  import { isFlowchart, isStep } from "../components/flowchart/index.mjs";
12
12
  import { markEmpty } from "../components/placeholder.mjs";
13
+ import { carry } from "../components/align.mjs";
13
14
  import { unread } from "./tex.mjs";
14
15
  import { MENTION, MENTION_ICON, MENTION_PAGE } from "../../shared/components/mention.mjs";
15
16
  import { FLOW_BLOCKS } from "../../shared/components/index.mjs";
@@ -577,7 +578,8 @@ export function newTail(container, type) {
577
578
  }
578
579
 
579
580
  // New blocks split off `from` are paragraphs, or list items in a list.
580
- const newBlock = (doc, tag, from) => newHost(doc, tag === "li" ? "item" : "paragraph", from);
581
+ // A paragraph split off a paragraph or a heading keeps its alignment and indent (components/align.mjs carry).
582
+ const newBlock = (doc, tag, from) => (tag === "li" ? newHost(doc, "item", from) : carry(from, newHost(doc, "paragraph", from)));
581
583
 
582
584
  /** A new, empty paragraph, as a split makes one outside a list (the page's title's Enter: typing/ enterBody). */
583
585
  export const newParagraphHost = (doc) => newBlock(doc, "p", null);
@@ -10,10 +10,12 @@ import { connectWorkbench } from "./session.mjs";
10
10
  // slug is "@<slug>", the page's address in the workbench (#@<slug>), which no document's slug can be.
11
11
  // It answers the session's own commands (theme, keys, scroll-top: src/editor/page/session.mjs),
12
12
  // prepare-close (once the board's ops sent are answered: each is written at once, nothing else waits),
13
- // add-card (the top bar's 添加自定义块: the library of the page's first board, under the button's place that
14
- // the workbench sends) and, on the home page, restore-defaults (恢复默认卡片, the sidebar's: the board's, one step of its
15
- // undo; its answer { ok, added, skipped } the workbench says) and undo-restore ({ cards }: that step
16
- // undone while it is the board's last, the workbench's 撤销),
13
+ // add-card where it has a board (the top bar's 添加自定义块: the library of the page's first board, under the
14
+ // button's place that the workbench sends; a page with none, a blank one or one an Agent wrote with no
15
+ // board, offers no 添加自定义块) and, on a page with the default cards (its page map's `defaults`: its
16
+ // page.json's defaultCards, the default home page's), restore-defaults (恢复默认卡片, its menu's: the board's,
17
+ // one step of its undo; its answer { ok, added, skipped } the workbench says) and undo-restore ({ cards }:
18
+ // that step undone while it is the board's last, the workbench's 撤销),
17
19
  // and sends ready, reload (its live script heard that page.html or page.json changed, or a block it
18
20
  // holds: the workbench loads it again in a hidden frame and swaps it in once drawn), open (a link to a
19
21
  // document or another page, or a block's context.workspace.open: doc-open-request) and key. Opened on its own (not in a frame of the workbench), it has no
@@ -24,6 +26,13 @@ const IDLE = { editing: false, pending: false, saving: false, offline: false, co
24
26
  // Nothing stands over a custom page as a modal.
25
27
  const NO_MODALS = { open: () => [], listen() {}, closeTop: () => false };
26
28
 
29
+ // The page map (routes.mjs: its slug, its token, the version it was served at, its blocks): the boards edit.
30
+ let map = null;
31
+ try {
32
+ map = JSON.parse(document.querySelector("script[data-ge-page]")?.textContent ?? "null");
33
+ } catch {}
34
+ const hasBoard = Boolean(document.querySelector(".gallery-board"));
35
+
27
36
  let board = null;
28
37
  const session = connectWorkbench({
29
38
  slug: `@${slug}`,
@@ -35,8 +44,8 @@ const session = connectWorkbench({
35
44
  },
36
45
  // 添加自定义块 of the workbench's top bar: the library opens in the page, at `anchor` (the button's place in
37
46
  // this frame's coordinates; none: in the middle of the window), the first board's.
38
- "add-card": async ({ anchor } = {}) => (board ? board.addCard({ anchor }) : { ok: false, reason: "board" }),
39
- ...(slug === "home" ? {
47
+ ...(hasBoard ? { "add-card": async ({ anchor } = {}) => (board ? board.addCard({ anchor }) : { ok: false, reason: "board" }) } : {}),
48
+ ...(map?.defaults ? {
40
49
  "restore-defaults": async () => (board ? board.restoreDefaults() : { ok: false, reason: "board" }),
41
50
  "undo-restore": async ({ cards }) => (board ? board.undoRestore(cards) : { ok: false, reason: "board" }),
42
51
  } : {}),
@@ -44,11 +53,6 @@ const session = connectWorkbench({
44
53
  modals: NO_MODALS,
45
54
  });
46
55
 
47
- // The page map (routes.mjs: its slug, its token, the version it was served at, its blocks): the boards edit.
48
- let map = null;
49
- try {
50
- map = JSON.parse(document.querySelector("script[data-ge-page]")?.textContent ?? "null");
51
- } catch {}
52
56
  board = map?.token ? startBoard({ map, reload: () => (session ? session.reload() : location.reload()), hosted: Boolean(session) }) : null;
53
57
  // The live script (routes.mjs) shows the page again through the workbench, which swaps a new page in
54
58
  // once drawn: no reload seen; once the board's own writes, a drag, a form or a field typed in are done.
@@ -15,6 +15,7 @@ import { createCommits, kindOf } from "./commit.mjs";
15
15
  import { createConflicts, packageChanged, unfit } from "./conflicts.mjs";
16
16
  import { checkLeft, createAutosave, keepWith, lostKey, pendingKey, readLost, recover, reloadView, setReloader, takeView, viewNow } from "./autosave.mjs";
17
17
  import { SOLO, connectWorkbench } from "./session.mjs";
18
+ import { OUTLINE_OPEN_KEY, onOutlineOpen, outlineOpen, setOutlineOpen } from "../../shared/preferences.mjs";
18
19
  import { lookOfElement } from "../../shared/document-look.mjs";
19
20
  import { belowTargets, convertBlock, convertGroup, groupParents, groupTargets, noTextToCarry, targetsOf, touchedBy, turnInto, turnParents, turnUnit, typeOf } from "./convert.mjs";
20
21
  import { joinCode, rangeAt, rangeOffsets } from "./marks.mjs";
@@ -24,6 +25,7 @@ import { serveFiles } from "./served.mjs";
24
25
  import { createFormatting } from "./format-bar/index.mjs";
25
26
  import { connectHints, hideHint, readOnlyReason, scheduleHint, trackPointer } from "./hints/index.mjs";
26
27
  import { createInsert } from "./insert/index.mjs";
28
+ import { createAfter } from "./after/index.mjs";
27
29
  import { connectLinks, dismissLink, drawPasted, followLink, leadsToPage, linkBoxNode, linkHolds, linkNodes, linkOf, linkPending, openLink, pageLinkOf } from "./links/index.mjs";
28
30
  import { bookmarkItems, connectViews, linkTitle, offerViews } from "./links/views.mjs";
29
31
  import { connectChipFills } from "./chip-fills.mjs";
@@ -36,6 +38,7 @@ import { createResize } from "./resize/index.mjs";
36
38
  import { connectPages, fieldOpen, keptByComponent, menuRow, modalOps, pageAt, pageIcon, pageObjects, pageOf, pageOfHost, pageOfType, pageOfUnit, panelOpen, typedAsIs } from "./page-objects.mjs";
37
39
  import { preloading } from "./type-grid.mjs";
38
40
  import { createColumnGaps } from "./columns/index.mjs";
41
+ import { viewFrames } from "./table/binding.mjs";
39
42
  import { createTableEditing } from "./table/index.mjs";
40
43
  import { createAtMenu } from "./at-menu/index.mjs";
41
44
  import { createSlashMenu } from "./slash-menu/index.mjs";
@@ -72,6 +75,7 @@ import { language, t } from "../../shared/i18n/index.mjs";
72
75
  //
73
76
  // typing/ Enter, Backspace/Delete at block edges, auto spacing, placeholders
74
77
  // insert/ new blocks: below a block (its menu), a click below the last block, a paragraph before or after
78
+ // after/ the caret after a block of no text (a click right of it): Backspace deletes it, Enter or a key a paragraph after it
75
79
  // format-bar/ the bar over selected text and its keys (marks.mjs does the marks)
76
80
  // links/ the link box (Ctrl+K), following links; hints/: why something is read-only
77
81
  // formula/ the formula box: selected text made an inline formula (Ctrl+Shift+E)
@@ -261,7 +265,9 @@ function showLanded(node) {
261
265
  modalOps().show(node);
262
266
  }
263
267
 
264
- const insert = createInsert({ struct, blocks: () => blocks });
268
+ // The caret after a block of no text, where a click right of one puts it (insert/ clickBlank).
269
+ const after = createAfter({ struct, blocks: () => blocks, keepText: (node) => blocks.keepText(node), removeBlocks: (units) => blocks.remove(units), say, undo: () => undo(), redo: () => redo() });
270
+ const insert = createInsert({ struct, blocks: () => blocks, after });
265
271
  const { newParagraph } = insert;
266
272
 
267
273
  // What a component does on the page beyond what every block does is its own page module
@@ -326,6 +332,8 @@ const typing = createTyping({
326
332
  adding: () => slash?.adding ?? null,
327
333
  leave: createLeaving({ struct, touchedBy, convertHost, typeOf, targetsOf }),
328
334
  showHidden: (node) => blocks?.folds.showAround(node), // a folded heading's section opened before a join takes in its words (fold/)
335
+ look: lookStep, // a block's alignment and indent taken off by Backspace (align.mjs)
336
+ afterAbove: (host) => after.back(host), // Backspace right after a block of no text: the caret after it (after/)
329
337
  });
330
338
  // Selecting text across blocks: its listeners go before the editor's own keys and clipboard.
331
339
  const crossText = createTextSelection({
@@ -673,7 +681,7 @@ function convertHost(host, type, typed = null, after = null) {
673
681
  if (!focus && !typed) return null;
674
682
  const more = focus && after ? after(focus) : [];
675
683
  return { touched: [focus ?? host], focus: focus ?? host, created: [...created, ...more] };
676
- });
684
+ }, { attributes: viewFrames(type) });
677
685
  if (result) afterConvert(result.focus, type, result.focus !== host ? t("已转换为{0}", menuRow(type).label) : null);
678
686
  }
679
687
 
@@ -703,10 +711,9 @@ function afterConvert(focus, type, said) {
703
711
  * heading) and the new block turns into the type; below any other block, an empty paragraph of its
704
712
  * box does (the first box out that takes one). One undo step.
705
713
  */
706
- // `also` (a table's 视图 ›: page/table/views.mjs): `attributes` ([element, names]) the step records too, and
707
- // `done(made)` run in it on the new block (the element where the empty paragraph was), so that what binds
708
- // the block to the one above it is the same step.
709
- function insertBelow(target, type, typed = null, also = null) {
714
+ // A view of a table (a custom block that reads tables) is bound to the table above it as it is made
715
+ // (installed/page.mjs create), the tables' keys recorded in the same step (viewFrames).
716
+ function insertBelow(target, type, typed = null) {
710
717
  const splits = isHost(target) && can(target, "split");
711
718
  let after = target;
712
719
  while (!splits && after && !accepts(after.parentElement?.getAttribute("data-ge-box-kind"), "paragraph")) after = after.parentElement?.closest(OUTER);
@@ -724,12 +731,10 @@ function insertBelow(target, type, typed = null, also = null) {
724
731
  after.after(fresh);
725
732
  }
726
733
  if (!fresh) return null;
727
- const prior = fresh.previousSibling; // what the new block stands right after, made where the paragraph was
728
734
  const turned = type === "paragraph" && fresh.localName === "p" ? { focus: null, created: [] } : convertBlock(fresh, type, { fresh: true });
729
- also?.done?.(fresh.isConnected ? fresh : prior?.nextElementSibling ?? null);
730
735
  const focus = turned.focus ?? fresh;
731
736
  return { touched: splits ? [target, focus] : [focus], focus, created: turned.created };
732
- }, { attributes: also?.attributes ?? [] });
737
+ }, { attributes: viewFrames(type) });
733
738
  if (result) afterConvert(result.focus, type, t("已插入{0}", menuRow(type).label));
734
739
  }
735
740
 
@@ -782,7 +787,7 @@ function convertUnit(unit, type, { loaded = false } = {}) {
782
787
  const turned = turnUnit(unit, type);
783
788
  emptied = Boolean(turned.emptied);
784
789
  return turned.focus ? { touched: [turned.focus], focus: turned.focus, created: turned.created, select: emptied ? null : unit } : null;
785
- });
790
+ }, { attributes: viewFrames(type) });
786
791
  if (!result) return;
787
792
  if (!made || !onCreated(type, result.focus)) {
788
793
  if (emptied && isHost(result.focus)) caretAt(result.focus, "start");
@@ -823,6 +828,7 @@ function convertAll(group, type, { select = true } = {}) {
823
828
  function onKeydown(event) {
824
829
  compositionOver(event);
825
830
  if (!editing) return;
831
+ if (after.key(event)) return; // the caret after a block: its keys are its own (after/)
826
832
  // A 批注's card is a field of its own: its keys are its own, but for Ctrl+S, which saves it and the report.
827
833
  if (annotate?.holds(event.target)) {
828
834
  const mod = event.ctrlKey || event.metaKey;
@@ -928,7 +934,8 @@ function onKeydown(event) {
928
934
  moveHostBlock(host, event.key === "ArrowUp" ? -1 : 1);
929
935
  } else if (event.key.startsWith("Arrow") && !mod && !event.altKey && !event.shiftKey && !renamed) {
930
936
  // ←/→ stop on both sides of inline code's edges (typing/code-edges.mjs); a tab's caret stays in its name.
931
- if ((textFilters(host) && codeArrow(host, event.key)) || arrow(host, event.key)) event.preventDefault();
937
+ // ← or ↑ at the start of the text right after a block of no text: the caret after that block (after/).
938
+ if ((textFilters(host) && codeArrow(host, event.key)) || after.up(host, event.key) || arrow(host, event.key)) event.preventDefault();
932
939
  }
933
940
  }
934
941
 
@@ -1243,6 +1250,15 @@ const COMMANDS = {
1243
1250
  if (!editing || !annotate) return { ok: false, reason: "editing" };
1244
1251
  return annotate.action(message);
1245
1252
  },
1253
+ // The outline (目录) switched by the workbench's 更多: a draft of another workspace is shown from that workspace's own origin,
1254
+ // whose storage the workbench cannot write (the switch is kept per origin: shared/preferences.mjs outlineOpen), so it asks and
1255
+ // this page keeps it, shown as another tab's change is (the storage event the outline listens for). Answers the switch as kept.
1256
+ async outline({ open }) {
1257
+ if (typeof open !== "boolean") return { ok: false, reason: "open" };
1258
+ setOutlineOpen(MAP.slug, open);
1259
+ dispatchEvent(new StorageEvent("storage", { key: OUTLINE_OPEN_KEY }));
1260
+ return { ok: true, outline: outlineOpen(MAP.slug) };
1261
+ },
1246
1262
  // Rename the report: its title, selected for typing (saving it updates <title> and report.json).
1247
1263
  async "edit-title"() {
1248
1264
  const title = document.querySelector('[data-ge-kind="title"]');
@@ -1473,8 +1489,9 @@ function connect() {
1473
1489
  // The look the page shows (字体与布局: its <html>'s, src/shared/document-look.mjs), which the workbench's
1474
1490
  // 更多 marks as chosen; told again when it changes (the kit's ui.js, doc-look-change).
1475
1491
  // `title`: null for a page with no title at all (the workbench names it by its doc.json); "" an untitled one (未命名文档).
1476
- workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: titleNode() ? currentTitle() : null, icon: pageIcon(), look: lookOfElement(document.documentElement), state: sessionState(), annotations: annotate?.count() }), commands: COMMANDS, modals: modalOps() });
1492
+ workbench = connectWorkbench({ slug: MAP.slug, snapshot: () => ({ title: titleNode() ? currentTitle() : null, icon: pageIcon(), look: lookOfElement(document.documentElement), state: sessionState(), annotations: annotate?.count(), outline: outlineOpen(MAP.slug) }), commands: COMMANDS, modals: modalOps() });
1477
1493
  addEventListener("doc-look-change", () => workbench?.update());
1494
+ onOutlineOpen(() => workbench?.update()); // the switch changed in another tab of this origin: the workbench's 目录 follows it (a draft's)
1478
1495
  // A block's context.workspace.open (the kit's runtime/workspace.js): a document, or a block of one, goes
1479
1496
  // where a link to it goes (goTo: in the workbench, its address; on its own, the page once saved); a custom
1480
1497
  // page, the workbench's #@<slug>, or /pages/<slug> once saved.
@@ -1655,8 +1672,8 @@ function boot() {
1655
1672
  gaps = createColumnGaps({ active, struct, look: lookStep, say, settle });
1656
1673
  for (const page of pageObjects()) page.install?.(); // the round "+" at a flowchart node's right, renaming Sheet tabs
1657
1674
  tables = createTableEditing({
1658
- active, struct, look: lookStep, selectBlock: (unit) => blocks.select(unit), resizeTable: (event, unit, axis) => resize.startTable(event, unit, axis), say, cellsOf: (data) => clipboard.cellsOf(data), upload: uploadTo, typedKey,
1659
- recordStep, insertBelow: (unit, type, also) => insertBelow(unit, type, null, also),
1675
+ active, struct, look: lookStep, selectBlock: (unit) => blocks.select(unit), resizeTable: (event, unit, axis) => resize.startTable(event, unit, axis), say, settle, cellsOf: (data) => clipboard.cellsOf(data), upload: uploadTo, typedKey,
1676
+ recordStep,
1660
1677
  });
1661
1678
  selectAll = createSelectAll({ blocks, say, cells: { table: () => tables.selectedTable, clear: () => tables.clear() } });
1662
1679
  createFrame(() => editing);
@@ -69,7 +69,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
69
69
  typing.time = at;
70
70
  } else {
71
71
  closeTyping();
72
- typing = { type: "text", element: host, before, after, time: at };
72
+ typing = { type: "text", element: host, before, after, time: at, began: at };
73
73
  }
74
74
  changed(now);
75
75
  }
@@ -83,8 +83,14 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
83
83
  changed(true);
84
84
  }
85
85
 
86
+ // A run of typing closed: a step of its own, unless it went into the block the last step made for
87
+ // what was typed (struct's `typedInto`: a paragraph after a block, after/), begun within a second of it:
88
+ // then it is part of that step, which undo takes away whole, redo putting the words back with it.
86
89
  function closeTyping() {
87
- if (typingChanged()) push(typing);
90
+ const last = undoStack.at(-1);
91
+ if (typingChanged() && last?.typedInto === typing.element && typing.began - last.madeAt < 1000) last.htmlAfter.set(typing.element, typing.after);
92
+ else if (typingChanged()) push(typing);
93
+ if (typing && last?.typedInto) last.typedInto = null;
88
94
  typing = null;
89
95
  }
90
96
 
@@ -151,7 +157,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
151
157
  // An attribute the file gave another value than the page had (`changes`: [element, name, from, to],
152
158
  // `to` null for none; a key of a table a commit gave a pair anew: page/commit.mjs apply), the page's
153
159
  // element set so: the steps that recorded `from` of it record `to` instead, so that an undo or a redo
154
- // puts back what the file has, never the value it changed (a table's 视图 › redone with the key its view
160
+ // puts back what the file has, never the value it changed (a view made from "/" redone with the key its view
155
161
  // no longer carries).
156
162
  function rekeyed(changes) {
157
163
  if (changes.length === 0) return;
@@ -162,6 +168,14 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
162
168
  if (values?.some(([each, value]) => each === name && value === from)) recorded.set(element, values.map(([each, value]) => [each, each === name && value === from ? to : value]));
163
169
  }
164
170
  }
171
+ // A view's table chosen in its 设置 is a step of look (installed/page.mjs readTable): its attributes by element.
172
+ if (step.type === "look") {
173
+ for (const recorded of [step.before, step.after]) {
174
+ step.elements.forEach((each, index) => {
175
+ for (const [element, name, from, to] of changes) if (each === element && recorded[index]?.attributes?.[name] === from) recorded[index].attributes[name] = to;
176
+ });
177
+ }
178
+ }
165
179
  }
166
180
  }
167
181
 
@@ -194,7 +208,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
194
208
  // spans and widths, which its operations change: components/table/page.mjs). `caret`: where the caret
195
209
  // stood in the first text block it touched is kept too, which undo puts back (notion-lite's Enter in the
196
210
  // page's title, enter.js: "撤销后光标回到回车前的位置"; typing/ enterBody).
197
- function struct(parents, touched, mutate, { attributes = [], caret = false } = {}) {
211
+ function struct(parents, touched, mutate, { attributes = [], caret = false, typedInto = null } = {}) {
198
212
  closeTyping();
199
213
  // What the change may leave untidy is tidied after it by the components that know (rows of
200
214
  // columns with a column emptied, lists set side by side: tidy), so what they name is recorded
@@ -246,6 +260,7 @@ export function createHistory({ changed, beforeUndo, blocks, afterText, refuse =
246
260
  selectAfter: result.selectAfter ?? null, // what redo selects instead
247
261
  caret: result.caret ?? null, // where redo puts the caret when it selects nothing (tidyEmptied)
248
262
  settle: result.settle ?? null,
263
+ typedInto, madeAt: Date.now(), // the block made for what is typed, whose first run of typing is part of this step (closeTyping)
249
264
  });
250
265
  result.touched.forEach((node) => lastKnown.set(node, ownHTML(node)));
251
266
  note("history", "change");
@@ -1,10 +1,10 @@
1
1
  import { OUTER, UNIT, blocksIn, boxKind, heldIn, newHost, newTail, placeBefore, unitElement, withLeads } from "../core.mjs";
2
2
  import { containerType } from "../convert.mjs";
3
3
  import { begin, done, made } from "../made.mjs";
4
- import { selectsBeside } from "../beside.mjs";
4
+ import { caretBeside } from "../beside.mjs";
5
5
  import { caretAt, nearestOnLine, placeCaret, selectAt } from "../caret.mjs";
6
6
  import { sectionOps } from "../page-objects.mjs";
7
- import { contentOf, endOf, hoverAt, innerBox, measure } from "../geometry.mjs";
7
+ import { endOf, hoverAt, innerBox, measure } from "../geometry.mjs";
8
8
  import { BOXED, HOLDS_ONE, accepts } from "../../components/registry.mjs";
9
9
  import { DIALOG } from "../../components/kit.mjs";
10
10
 
@@ -18,9 +18,9 @@ import { DIALOG } from "../../components/kit.mjs";
18
18
 
19
19
  /**
20
20
  * struct(parents, touched, mutate): the editor's undoable structural change (history.mjs). blocks():
21
- * the block tools (blocks.mjs).
21
+ * the block tools (blocks.mjs). after: the caret after a block (after/).
22
22
  */
23
- export function createInsert({ struct, blocks }) {
23
+ export function createInsert({ struct, blocks, after }) {
24
24
  // An empty paragraph right before the block of a text block, the caret staying where it is.
25
25
  // Returns whether one could go there.
26
26
  function paragraphBefore(host) {
@@ -54,10 +54,10 @@ export function createInsert({ struct, blocks }) {
54
54
  // (geometry.mjs endOf). Under the last block of a box whose end is its own padding (endIsPadding: a
55
55
  // highlight block, a callout, a disclosure's content; the user's, 2026-11-06, as notion-lite's click-blank.js
56
56
  // makes a block only below the page and in a column) it is a gap like any other, below. Right of
57
- // the text area a boxed block (a code block, a callout, a file…: not text) or a row of columns
58
- // stands in, at its height (boxedBeside), that block selected, so Delete takes it and Enter puts a
59
- // paragraph after it; between a narrower block's right edge and its column's, as beside any block
60
- // of no text (below). Elsewhere beside text, the caret goes into the text block on that line (the nearest of those
57
+ // a boxed block (a code block, a callout, a file…: not text) or a row of columns, at its height
58
+ // (boxedBeside), the caret after it, at its bottom right (after/: Backspace deletes it, Enter or a
59
+ // key makes a paragraph after it; the user's, 2026-11-09: a click there selected it before).
60
+ // Elsewhere beside text, the caret goes into the text block on that line (the nearest of those
61
61
  // side by side), where the line is nearest the click (Notion's way); in the gap between two
62
62
  // blocks, or beside a block of no text, into the nearer of the blocks around it that is text, in the
63
63
  // innermost box at that height (boxBeside: the press may be on the page's margin, the block in a
@@ -82,7 +82,7 @@ export function createInsert({ struct, blocks }) {
82
82
  if (end) return newParagraph(end.box, end.after);
83
83
  const top = y + scrollY;
84
84
  const beside = boxedBeside(main, x, y, target);
85
- if (beside) return blocks().select(beside);
85
+ if (beside) return after.place(beside);
86
86
  const host = hoverAt(measure(main, { top, bottom: top }), x + scrollX, top, { reach: Infinity, skip: (unit) => !unit.text });
87
87
  if (host) return caretNear(host, x, y);
88
88
  for (let around = boxBeside(box, x, y); around; around = around === box ? null : around.parentElement.closest("[data-ge-box]")) {
@@ -157,26 +157,24 @@ export function createInsert({ struct, blocks }) {
157
157
  const texts = [unit, ...unit.querySelectorAll("[data-ge-block], [data-ge-new]")].filter((node) => node.isContentEditable && node.getClientRects().length > 0 && !node.closest("details:not([open]) > :not(summary)"));
158
158
  return (last ? texts.at(-1) : texts[0]) ?? null;
159
159
  }
160
- // A boxed block or a row of columns at height `y` with (x, y) right of the text area it stands in
161
- // (the content box of its box: the page's column, a column of 分栏, a section's, a quote's, a
162
- // highlight block's or a callout's, a disclosure's content) and right of the block itself (one
163
- // wider than that area), or null: in the page's margin, or in the padding of the box it stands in;
164
- // never between a narrower block's right edge and its area's (a table, a picture: the user's,
165
- // 2026-11-07, a click there selected it), nor in another text area (the blank box `target` the
166
- // press was on holds the block: a column further right is no place beside one in the column left
167
- // of it). The innermost, but never a block inside a row of columns the click is to the right of too
168
- // (the row is selected whole): past a column's edge is the gap to the next column, whose press is
169
- // the gap's (columns/: it drags, a click alone changes nothing), or past the row, which is selected.
160
+ // A boxed block or a row of columns at height `y` with (x, y) right of it: between its right edge
161
+ // and the text area's it stands in (a table, a picture narrower than its column: the user's,
162
+ // 2026-11-09, the caret after it where a click there once selected it, 2026-11-07, and later went to
163
+ // the nearest text), in the padding of the box it stands in (a highlight block's, a callout's) or in the
164
+ // page's margin; or null. Never in another text area (the blank box `target` the press was on holds the
165
+ // block: a column further right is no place beside one in the column left of it). The innermost,
166
+ // but never a block inside a row of columns the click is to the right of too (the caret goes after
167
+ // the row): past a column's edge is the gap to the next column, whose press is the gap's (columns/:
168
+ // it drags, a click alone changes nothing), or past the row.
170
169
  function boxedBeside(main, x, y, target) {
171
170
  let found = null;
172
171
  const right = (unit) => {
173
172
  const rect = unit.getBoundingClientRect();
174
173
  if (y < rect.top || y > rect.bottom || (target?.contains && !target.contains(unit))) return false;
175
- const box = unit.parentElement;
176
- return x > Math.max(rect.right, contentOf(box.getBoundingClientRect(), getComputedStyle(box)).right);
174
+ return x > rect.right;
177
175
  };
178
176
  for (const unit of main.querySelectorAll(`[data-ge-unit-kind]`)) {
179
- if (!selectsBeside(unit) || !unit.parentElement?.hasAttribute("data-ge-box") || unit.parentElement.closest("details:not([open])")) continue;
177
+ if (!caretBeside(unit) || !unit.parentElement?.hasAttribute("data-ge-box") || unit.parentElement.closest("details:not([open])")) continue;
180
178
  if (!right(unit)) continue;
181
179
  const row = unit.parentElement.closest('[data-ge-unit-kind="columns"]');
182
180
  if (row && main.contains(row) && right(row)) continue;
@@ -86,8 +86,8 @@ const STYLE = `
86
86
  /* A note wraps at the card's width and never widens it (no width of its own: contain). */
87
87
  .ge-pop-note { contain: inline-size; padding: 0 12px 6px 40px; color: var(--ge-tx3); font-size: 12px; line-height: 1.5; }
88
88
  .ge-pop-empty { padding: 8px 12px; color: var(--ge-tx3); }
89
- /* What a row makes, shown beside it under the pointer (a component package's preview.svg). */
90
- .ge-pop-preview { display: block; width: 240px; max-height: 180px; border-radius: 4px; object-fit: contain; background: var(--ge-bg); }
89
+ /* What a row makes, shown beside it under the pointer (a component package's preview.svg), in the middle of the bubble: its description under it wraps wider than the picture. */
90
+ .ge-pop-preview { display: block; width: 240px; max-width: 100%; max-height: 180px; margin: 0 auto; border-radius: 4px; object-fit: contain; object-position: center; background: var(--ge-bg); }
91
91
  .ge-pop-search { position: sticky; top: 0; z-index: 1; padding: 8px 8px 6px; background: var(--ge-bg); }
92
92
  .ge-pop-search input { box-sizing: border-box; width: 100%; height: 28px; margin: 0; padding: 0 8px; border: 0; border-radius: 6px; outline: none; background: var(--ge-hover); color: var(--ge-tx); font: inherit; box-shadow: inset 0 0 0 1px var(--ge-line); }
93
93
  .ge-pop-search input:focus { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ge-tx3) 60%, transparent); }
@@ -145,7 +145,9 @@ export const menuOpen = () => opened.size > 0;
145
145
  * as its rows (FIT; SEARCHING with a search field). around: the rect of the block a handle
146
146
  * (the anchor) stands for; the menu keeps clear of that block: left of it beside the handle, else
147
147
  * below or above it (scrolling inside when only part of it fits), and over it only when the block
148
- * leaves no room at all. side: "right": beside the anchor, right of it (left of it when there is no
148
+ * leaves no room at all; a tall block (more than TALL high: a table, a formula, a highlight, an image)
149
+ * has it beside the handle, left of it or, with no room there,
150
+ * right of it over the block, centered on the handle, never over the handle. side: "right": beside the anchor, right of it (left of it when there is no
149
151
  * room), level with it (a section's bar). Without either the menu stands below the anchor (the
150
152
  * caret), or above it when there is no room below; align: "end", its right edge at the anchor's.
151
153
  * icons: false, rows of words alone (no column for icons: a code block's languages; a row that opens a
@@ -276,6 +278,7 @@ export function createMenu() {
276
278
  }
277
279
 
278
280
  const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
281
+ const TALL = 34; // px: a block taller than this (a line of text is 26px) is a tall one: its menu may stand over it
279
282
 
280
283
  function place() {
281
284
  if (options.inside) return;
@@ -324,11 +327,16 @@ export function createMenu() {
324
327
  const below = innerHeight - margin - (block.bottom + gap);
325
328
  const above = block.top - gap - margin;
326
329
  const beside = anchor.left - gap - width;
327
- if (beside >= margin) {
328
- // Left of the block, centered on its handle.
330
+ // A block of more than a line (a table, a formula, a highlight, an image: taller than TALL) may
331
+ // have the menu over it: beside its handle, right of it where there is no room left of it,
332
+ // rather than below the block, away from the handle.
333
+ const middle = (anchor.top + anchor.bottom) / 2;
334
+ const onRight = block.bottom - block.top > TALL && anchor.right + gap + width <= innerWidth - margin;
335
+ if (beside >= margin || onRight) {
336
+ // Beside the handle (never over it), left first, centered on it.
329
337
  fit(room);
330
- left = beside;
331
- top = clamp((anchor.top + anchor.bottom) / 2 - height / 2, margin, innerHeight - margin - height);
338
+ left = beside >= margin ? beside : anchor.right + gap;
339
+ top = clamp(middle - height / 2, margin, innerHeight - margin - height);
332
340
  } else if (Math.max(below, above) >= Math.min(height, 180)) {
333
341
  // Below the block, else above it; in the larger of the two when it fits in neither.
334
342
  left = clamp(anchor.left, margin, innerWidth - width - margin);
@@ -36,14 +36,10 @@ const variants = () => Object.keys(TYPE_COMPONENT).flatMap((type) => Object.entr
36
36
 
37
37
  /**
38
38
  * The keys of the variants the menus of blocks offer now ("/", 在下方新建, the handle's +, 转换成): every
39
- * one but a view of a table (`readsTables`: a custom block whose needs hold "tables"), which a table's
40
- * 视图 › alone offers, as it puts one in bound to that table (viewKeys): one put in anywhere else would
41
- * only say it has no table to read.
39
+ * one, a view of a table (`readsTables`: a custom block whose needs hold "tables") too, which binds
40
+ * itself to the table above it as it is made (components/installed/page.mjs; page/table/binding.mjs).
42
41
  */
43
- export const variantKeys = () => variants().filter(([, row]) => !row.readsTables).map(([key]) => key);
44
-
45
- /** The keys of the variants that are views of a table, for a table's 视图 › (table/views.mjs). */
46
- export const viewKeys = () => variants().filter(([, row]) => row.readsTables).map(([key]) => key);
42
+ export const variantKeys = () => variants().map(([key]) => key);
47
43
 
48
44
  /** The page objects, in the order they are made. */
49
45
  export const pageObjects = () => Object.values(pages);
@@ -2,7 +2,7 @@ import { t } from "../../shared/i18n/index.mjs";
2
2
  import { addressOn, elsewhere, fromFile, isPageAddress, mayBePage, pageAddress, workbenchHash } from "../../shared/page-address.mjs";
3
3
  import { inTrash, linkedPages, pageLinks } from "../../shared/page-links.mjs";
4
4
  import { entryOfKey } from "../../shared/extension-sidebar.mjs";
5
- import { PROTOCOL, VERSION } from "../../shared/session.mjs";
5
+ import { SHELL, tell } from "./present-shell.mjs";
6
6
 
7
7
  // The links of a page shown in 演示视图 (?present: src/server/routes.mjs), which has no editor and whose
8
8
  // frame the presentation holds (src/workbench/app/features/present/). A link to a document or a block
@@ -16,7 +16,8 @@ import { PROTOCOL, VERSION } from "../../shared/session.mjs";
16
16
  // a click stays theirs: an anchor, a link to save, a link of a target of its own, any click with a key
17
17
  // held. A workbench's address that names a web link of the sidebar, or an entry of an extension's column
18
18
  // (#~<id>/<entry id>, one with an address), is a web address to the presentation: its page goes to a new
19
- // tab; an entry the list does not hold, or one with no address, is said gone as a document is.
19
+ // tab; an entry the list does not hold, or one with no address, is said gone as a document is. (In a draft's presentation, a frame
20
+ // of its workspace's own server, the word goes to the workbench of another origin and a link keeps it around the page: present-shell.mjs.)
20
21
 
21
22
  const own = pageAddress(location.href)?.slug ?? null;
22
23
  const openTab = (address) => window.open(address, "_blank", "noopener");
@@ -24,13 +25,13 @@ const read = (route) => fetch(`/__gallery/workspace/${route}`).then((answer) =>
24
25
 
25
26
  function go({ slug, block }) {
26
27
  const fragment = block ? `#${encodeURIComponent(block)}` : "";
27
- if (slug !== own) location.assign(`/generated/${slug}.html?present${fragment}`);
28
+ if (slug !== own) location.assign(`/generated/${slug}.html?present${SHELL ? `&shell=${encodeURIComponent(SHELL)}` : ""}${fragment}`); // a draft's presentation keeps the workbench around it (present-shell.mjs)
28
29
  else if (block) dispatchEvent(new CustomEvent("doc-jump-request", { detail: { hash: fragment } })); // the kit's jump, a step of the history as a link #<block id> is
29
30
  }
30
31
 
31
32
  function sayGone(name, { trash = [] } = {}) {
32
33
  const text = inTrash(trash, name) ? t("这个页面已删除。") : t("没有找到这个页面。");
33
- window.parent.postMessage({ protocol: PROTOCOL, v: VERSION, type: "notice", slug: own, text }, location.origin);
34
+ tell("notice", { text });
34
35
  }
35
36
 
36
37
  async function follow(address) {