@aglyn/plugins-ai 1.0.0-beta.270 → 1.0.0-beta.272

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 (281) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +4 -3
  3. package/src/lib/activity/ai-activity-actions.js +4 -0
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/assets/site-styles/beauty.webp +0 -0
  6. package/src/lib/assets/site-styles/blog.webp +0 -0
  7. package/src/lib/assets/site-styles/business.webp +0 -0
  8. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  9. package/src/lib/assets/site-styles/education.webp +0 -0
  10. package/src/lib/assets/site-styles/events.webp +0 -0
  11. package/src/lib/assets/site-styles/fitness.webp +0 -0
  12. package/src/lib/assets/site-styles/landing.webp +0 -0
  13. package/src/lib/assets/site-styles/music.webp +0 -0
  14. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  15. package/src/lib/assets/site-styles/personal.webp +0 -0
  16. package/src/lib/assets/site-styles/photography.webp +0 -0
  17. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  18. package/src/lib/assets/site-styles/professional.webp +0 -0
  19. package/src/lib/assets/site-styles/realestate.webp +0 -0
  20. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  21. package/src/lib/assets/site-styles/store.webp +0 -0
  22. package/src/lib/assets/site-styles/studio.webp +0 -0
  23. package/src/lib/assets/site-styles/trades.webp +0 -0
  24. package/src/lib/assets/site-styles/wellness.webp +0 -0
  25. package/src/lib/assets/site-styles/yoga.webp +0 -0
  26. package/src/lib/components/ai-credits-card.component.d.ts +4 -3
  27. package/src/lib/components/ai-credits-card.component.js +15 -15
  28. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  29. package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
  30. package/src/lib/components/ai-job-input-form.component.js +100 -0
  31. package/src/lib/components/ai-job-input-form.component.js.map +1 -0
  32. package/src/lib/components/ai-job-requests.d.ts +4 -0
  33. package/src/lib/components/ai-job-requests.js +4 -0
  34. package/src/lib/components/ai-job-requests.js.map +1 -1
  35. package/src/lib/components/ai-site-build-page.component.js +18 -0
  36. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  37. package/src/lib/components/ai-site-import-job.component.d.ts +41 -0
  38. package/src/lib/components/ai-site-import-job.component.js +707 -0
  39. package/src/lib/components/ai-site-import-job.component.js.map +1 -0
  40. package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
  41. package/src/lib/components/ai-site-import-start.component.js +335 -0
  42. package/src/lib/components/ai-site-import-start.component.js.map +1 -0
  43. package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
  44. package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
  45. package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
  46. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  47. package/src/lib/components/ai-site-start-card.component.js +261 -119
  48. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  50. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  51. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  52. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  53. package/src/lib/components/ai-site-start-tracking.js +14 -0
  54. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  55. package/src/lib/components/ai-site-style-previews.js +26 -22
  56. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  57. package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
  58. package/src/lib/components/ai-upsell-dialog.component.js +8 -6
  59. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +11 -1
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +9 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/assist-panel.component.js +8 -4
  65. package/src/lib/components/assist-panel.component.js.map +1 -1
  66. package/src/lib/components/staff-org-ai-card.component.js +4 -7
  67. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  68. package/src/lib/declarations.d.ts +2 -2
  69. package/src/lib/declarations.js +9 -5
  70. package/src/lib/declarations.js.map +1 -1
  71. package/src/lib/jobs/ai-build-capabilities.js +3 -1
  72. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  73. package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
  74. package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
  75. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  76. package/src/lib/jobs/ai-job-build-step.js +5 -3
  77. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  78. package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
  79. package/src/lib/jobs/ai-job-drafts.js +24 -0
  80. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  81. package/src/lib/jobs/ai-job-form-step.d.ts +15 -1
  82. package/src/lib/jobs/ai-job-form-step.js +75 -8
  83. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
  85. package/src/lib/jobs/ai-job-import-step.js +809 -0
  86. package/src/lib/jobs/ai-job-import-step.js.map +1 -0
  87. package/src/lib/jobs/ai-job-page-language.d.ts +52 -1
  88. package/src/lib/jobs/ai-job-page-language.js +99 -5
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  90. package/src/lib/jobs/ai-job-page-step.d.ts +19 -2
  91. package/src/lib/jobs/ai-job-page-step.js +42 -18
  92. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  94. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  95. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  97. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  99. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  100. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  101. package/src/lib/jobs/ai-job-site-look.js +18 -5
  102. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  103. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  104. package/src/lib/jobs/ai-job-site-step.js +95 -22
  105. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-text-step.d.ts +15 -1
  107. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  109. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  110. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  111. package/src/lib/jobs/ai-jobs.d.ts +13 -1
  112. package/src/lib/jobs/ai-jobs.js +77 -10
  113. package/src/lib/jobs/ai-jobs.js.map +1 -1
  114. package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
  115. package/src/lib/jobs/ai-layout-owner-photos.js +47 -0
  116. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
  117. package/src/lib/jobs/ai-layout-stock-photos.d.ts +27 -2
  118. package/src/lib/jobs/ai-layout-stock-photos.js +190 -19
  119. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  120. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  121. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  122. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  123. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  124. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  125. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  126. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  127. package/src/lib/jobs/ai-site-publish.js +162 -13
  128. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  129. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +77 -3
  130. package/src/lib/jobs/ai-theme-brand-inputs.js +135 -31
  131. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  132. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  133. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  134. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  135. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  136. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  137. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  139. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  140. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  141. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  142. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  143. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  145. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  146. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  147. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  148. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  149. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  150. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  151. package/src/lib/layout-language/ai-layout-design.js +411 -97
  152. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  154. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  155. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  156. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  157. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  158. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  159. package/src/lib/layout-language/ai-layout-language.js +4 -3
  160. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  161. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  162. package/src/lib/layout-language/ai-layout-links.js +30 -0
  163. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  164. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  165. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  166. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  167. package/src/lib/model/ai-build-job.d.ts +16 -1
  168. package/src/lib/model/ai-build-job.js +40 -0
  169. package/src/lib/model/ai-build-job.js.map +1 -1
  170. package/src/lib/model/ai-credit-estimate.d.ts +20 -0
  171. package/src/lib/model/ai-credit-estimate.js +13 -0
  172. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  173. package/src/lib/model/ai-job-activity.d.ts +2 -2
  174. package/src/lib/model/ai-job-activity.js +6 -4
  175. package/src/lib/model/ai-job-activity.js.map +1 -1
  176. package/src/lib/model/ai-job-input.d.ts +33 -0
  177. package/src/lib/model/ai-job-input.js +59 -0
  178. package/src/lib/model/ai-job-input.js.map +1 -0
  179. package/src/lib/model/ai-job-notice.d.ts +4 -0
  180. package/src/lib/model/ai-job-notice.js +28 -4
  181. package/src/lib/model/ai-job-notice.js.map +1 -1
  182. package/src/lib/model/ai-jobs.types.d.ts +69 -3
  183. package/src/lib/model/ai-jobs.types.js +2 -1
  184. package/src/lib/model/ai-jobs.types.js.map +1 -1
  185. package/src/lib/model/ai-site-details.d.ts +122 -0
  186. package/src/lib/model/ai-site-details.js +300 -0
  187. package/src/lib/model/ai-site-details.js.map +1 -0
  188. package/src/lib/model/ai-site-entity.d.ts +181 -0
  189. package/src/lib/model/ai-site-entity.js +390 -0
  190. package/src/lib/model/ai-site-entity.js.map +1 -0
  191. package/src/lib/model/ai-site-import-view.d.ts +130 -0
  192. package/src/lib/model/ai-site-import-view.js +182 -0
  193. package/src/lib/model/ai-site-import-view.js.map +1 -0
  194. package/src/lib/model/ai-site-import.d.ts +302 -0
  195. package/src/lib/model/ai-site-import.js +526 -0
  196. package/src/lib/model/ai-site-import.js.map +1 -0
  197. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  198. package/src/lib/model/ai-site-inspiration.js +109 -0
  199. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  200. package/src/lib/model/ai-site-job.d.ts +13 -1
  201. package/src/lib/model/ai-site-job.js +15 -2
  202. package/src/lib/model/ai-site-job.js.map +1 -1
  203. package/src/lib/model/ai-site-kinds.js +109 -21
  204. package/src/lib/model/ai-site-kinds.js.map +1 -1
  205. package/src/lib/model/ai-site-look.d.ts +10 -0
  206. package/src/lib/model/ai-site-look.js +24 -0
  207. package/src/lib/model/ai-site-look.js.map +1 -1
  208. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  209. package/src/lib/model/ai-site-start-seo.js +25 -7
  210. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  211. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  212. package/src/lib/model/ai-site-start-steps.js +67 -5
  213. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  214. package/src/lib/model/ai-site-start.d.ts +81 -4
  215. package/src/lib/model/ai-site-start.js +207 -13
  216. package/src/lib/model/ai-site-start.js.map +1 -1
  217. package/src/lib/model/ai-upsell-copy.js +4 -1
  218. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  219. package/src/lib/plan-entitlements.d.ts +4 -3
  220. package/src/lib/plan-entitlements.js +3 -8
  221. package/src/lib/plan-entitlements.js.map +1 -1
  222. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  223. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  224. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  225. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  226. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  227. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  228. package/src/lib/runtime/ai-doctrine.js +1 -1
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  231. package/src/lib/runtime/ai-node-tree.js +9 -1
  232. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  233. package/src/lib/server/ai-create-route.d.ts +1 -1
  234. package/src/lib/server/ai-create-route.js +3 -4
  235. package/src/lib/server/ai-create-route.js.map +1 -1
  236. package/src/lib/server/ai-jobs-resume.js +27 -3
  237. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  238. package/src/lib/server/ai-jobs-route.js +18 -0
  239. package/src/lib/server/ai-jobs-route.js.map +1 -1
  240. package/src/lib/server/ai-site-import-route.d.ts +89 -0
  241. package/src/lib/server/ai-site-import-route.js +356 -0
  242. package/src/lib/server/ai-site-import-route.js.map +1 -0
  243. package/src/lib/server.js +14 -0
  244. package/src/lib/server.js.map +1 -1
  245. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
  246. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
  247. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
  248. package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
  249. package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
  250. package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
  251. package/src/lib/site-import/site-import-crawl.d.ts +163 -0
  252. package/src/lib/site-import/site-import-crawl.js +467 -0
  253. package/src/lib/site-import/site-import-crawl.js.map +1 -0
  254. package/src/lib/site-import/site-import-flag.d.ts +23 -0
  255. package/src/lib/site-import/site-import-flag.js +23 -0
  256. package/src/lib/site-import/site-import-flag.js.map +1 -0
  257. package/src/lib/site-import/site-import-html.d.ts +94 -0
  258. package/src/lib/site-import/site-import-html.js +462 -0
  259. package/src/lib/site-import/site-import-html.js.map +1 -0
  260. package/src/lib/site-import/site-import-images.d.ts +54 -0
  261. package/src/lib/site-import/site-import-images.js +204 -0
  262. package/src/lib/site-import/site-import-images.js.map +1 -0
  263. package/src/lib/site-import/site-import-page.d.ts +48 -0
  264. package/src/lib/site-import/site-import-page.js +150 -0
  265. package/src/lib/site-import/site-import-page.js.map +1 -0
  266. package/src/lib/site-import/site-import-review.d.ts +40 -0
  267. package/src/lib/site-import/site-import-review.js +149 -0
  268. package/src/lib/site-import/site-import-review.js.map +1 -0
  269. package/src/lib/site-import/site-import-robots.d.ts +52 -0
  270. package/src/lib/site-import/site-import-robots.js +126 -0
  271. package/src/lib/site-import/site-import-robots.js.map +1 -0
  272. package/src/lib/usage/assist-credits.d.ts +4 -4
  273. package/src/lib/usage/assist-credits.js +4 -4
  274. package/src/lib/usage/assist-credits.js.map +1 -1
  275. package/src/lib/usage/staff-org-ai-view.js +6 -3
  276. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  277. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  278. package/src/lib/usage/staff-org-ai.js +7 -7
  279. package/src/lib/usage/staff-org-ai.js.map +1 -1
  280. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  281. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -0,0 +1,462 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*==========================================
17
+ * WHAT A CRAWLED PAGE SAYS (AGL-3974, parent AGL-3973).
18
+ *
19
+ * "Bring my site" reads a person's existing pages for their words, their
20
+ * structure and their pictures. No server HTML parser is a dependency of any
21
+ * lib, and a page's text in order is a small question, so this is a
22
+ * tokenizer: tags and text runs, with the few elements that matter tracked
23
+ * and everything else read through.
24
+ *
25
+ * What never survives, before a single tag is walked:
26
+ * - scripts, styles, iframes, embeds, objects, SVG, canvas, media players,
27
+ * templates and comments, content and all;
28
+ * - any form with a card, bank or payment field, whole;
29
+ * - and a page with a password field is not read at all: it is refused and
30
+ * reported (`loginForm`), because a sign-in page is exactly what a
31
+ * phishing clone wants and nothing a site import needs.
32
+ *
33
+ * The output is DATA. Text from a page reaches a model only fenced as
34
+ * untrusted (`fenceUntrustedText`), never as instructions.
35
+ *=========================================*/ /** One run of a page's content, in the order the page draws it. */ import { _ as _extends } from "@swc/helpers/_/_extends";
36
+ /** Ceilings that keep one odd page from becoming the whole inventory. */ export const SITE_IMPORT_PAGE_LIMITS = {
37
+ blocks: 400,
38
+ blockChars: 2000,
39
+ images: 100,
40
+ links: 300,
41
+ navLinks: 60,
42
+ forms: 10,
43
+ fieldsPerForm: 30
44
+ };
45
+ /** Elements removed with everything inside them. */ const REMOVED_WITH_CONTENT = [
46
+ 'script',
47
+ 'style',
48
+ 'noscript',
49
+ 'template',
50
+ 'iframe',
51
+ 'frame',
52
+ 'frameset',
53
+ 'object',
54
+ 'applet',
55
+ 'svg',
56
+ 'math',
57
+ 'canvas',
58
+ 'video',
59
+ 'audio'
60
+ ];
61
+ /** Form controls, removed with their content once the forms are summarized. */ const FORM_CONTROLS = [
62
+ 'textarea',
63
+ 'select',
64
+ 'button'
65
+ ];
66
+ /** Void elements removed wherever they appear. */ const REMOVED_VOID = /<(?:embed|param|source|track|link|base)\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
67
+ const PASSWORD_INPUT = /<input\b(?:[^>"']|"[^"]*"|'[^']*')*\btype\s*=\s*["']?password\b/i;
68
+ /**
69
+ * A field that takes money or a bank detail: an autocomplete hint for a
70
+ * card, or a name or id that says card, CVC, IBAN, routing or payment.
71
+ */ const PAYMENT_FIELD = /<(?:input|select)\b(?:[^>"']|"[^"]*"|'[^']*')*?(?:\bautocomplete\s*=\s*["']?\s*cc-|\b(?:name|id|data-stripe|data-braintree-name)\s*=\s*["']?[^"'\s>]*(?:card|cc[-_]?(?:num|number|exp|csc|cvv|cvc)|cvv|cvc|csc|iban|routing|account[-_]?number|payment|billing[-_]?card))/i;
72
+ const BLOCK_TAGS = {
73
+ p: 'paragraph',
74
+ li: 'list-item',
75
+ dt: 'paragraph',
76
+ dd: 'paragraph',
77
+ td: 'paragraph',
78
+ th: 'paragraph',
79
+ address: 'paragraph',
80
+ pre: 'paragraph',
81
+ figcaption: 'paragraph',
82
+ caption: 'paragraph',
83
+ label: 'paragraph',
84
+ blockquote: 'quote'
85
+ };
86
+ /** Tags that end a run of loose text without being a block themselves. */ const BREAK_TAGS = new Set([
87
+ 'div',
88
+ 'section',
89
+ 'article',
90
+ 'main',
91
+ 'aside',
92
+ 'header',
93
+ 'footer',
94
+ 'nav',
95
+ 'ul',
96
+ 'ol',
97
+ 'dl',
98
+ 'table',
99
+ 'tr',
100
+ 'tbody',
101
+ 'thead',
102
+ 'figure',
103
+ 'form',
104
+ 'hr',
105
+ 'br',
106
+ 'body'
107
+ ]);
108
+ const NAMED_ENTITIES = {
109
+ amp: '&',
110
+ lt: '<',
111
+ gt: '>',
112
+ quot: '"',
113
+ apos: "'",
114
+ nbsp: ' ',
115
+ rsquo: '’',
116
+ lsquo: '‘',
117
+ rdquo: '”',
118
+ ldquo: '“',
119
+ mdash: '—',
120
+ ndash: '–',
121
+ hellip: '…',
122
+ copy: '©',
123
+ reg: '®',
124
+ trade: '™',
125
+ middot: '·',
126
+ bull: '•',
127
+ eacute: 'é',
128
+ egrave: 'è',
129
+ aacute: 'á',
130
+ ntilde: 'ñ',
131
+ uuml: 'ü',
132
+ ouml: 'ö',
133
+ auml: 'ä',
134
+ deg: '°',
135
+ times: '×'
136
+ };
137
+ /** Character references to characters; an unknown name stays as written. */ export function decodeHtmlEntities(text) {
138
+ return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z][a-z0-9]*);/gi, (whole, body)=>{
139
+ if (body[0] === '#') {
140
+ const code = body[1] === 'x' || body[1] === 'X' ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);
141
+ if (!Number.isFinite(code) || code <= 0 || code > 0x10ffff) return '';
142
+ if (code < 32 && code !== 9 && code !== 10 && code !== 13) return '';
143
+ return String.fromCodePoint(code);
144
+ }
145
+ const named = NAMED_ENTITIES[body.toLowerCase()];
146
+ return named != null ? named : whole;
147
+ });
148
+ }
149
+ const ATTRIBUTE = /([^\s=/>"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>"']+)))?/g;
150
+ /** A tag's attributes, names lowercased, values decoded. */ export function parseAttributes(source) {
151
+ const attributes = {};
152
+ for (const match of source.matchAll(ATTRIBUTE)){
153
+ var _ref, _ref1, _match_;
154
+ const name = match[1].toLowerCase();
155
+ if (name in attributes) continue;
156
+ attributes[name] = decodeHtmlEntities((_ref = (_ref1 = (_match_ = match[2]) != null ? _match_ : match[3]) != null ? _ref1 : match[4]) != null ? _ref : '');
157
+ }
158
+ return attributes;
159
+ }
160
+ const TAG = /<(\/?)([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
161
+ /** Every `<meta>` tag's attributes. */ function metaTags(html) {
162
+ const out = [];
163
+ for (const match of html.matchAll(/<meta\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi)){
164
+ out.push(parseAttributes(match[1]));
165
+ }
166
+ return out;
167
+ }
168
+ /** The `content` of every `<meta name="…">` with this name. */ export function readMetaValues(html, name) {
169
+ const wanted = name.toLowerCase();
170
+ return metaTags(html).filter((attributes)=>{
171
+ var _attributes_name;
172
+ return ((_attributes_name = attributes['name']) != null ? _attributes_name : '').toLowerCase() === wanted;
173
+ }).map((attributes)=>{
174
+ var _attributes_content;
175
+ return ((_attributes_content = attributes['content']) != null ? _attributes_content : '').trim();
176
+ }).filter(Boolean);
177
+ }
178
+ function collapse(text) {
179
+ return text.replace(/\s+/g, ' ').trim();
180
+ }
181
+ function clip(text, max) {
182
+ return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text;
183
+ }
184
+ function removeElements(html, tags) {
185
+ let out = html;
186
+ for (const tag of tags){
187
+ const paired = new RegExp(`<${tag}\\b(?:[^>"']|"[^"]*"|'[^']*')*>[\\s\\S]*?<\\/${tag}\\s*>`, 'gi');
188
+ out = out.replace(paired, ' ');
189
+ // One left open runs to the end of the document, as a browser reads it.
190
+ const unclosed = new RegExp(`<${tag}\\b(?:[^>"']|"[^"]*"|'[^']*')*>[\\s\\S]*$`, 'i');
191
+ out = out.replace(unclosed, ' ');
192
+ }
193
+ return out;
194
+ }
195
+ /** Forms with a payment field go whole; the rest are summarized, then go. */ function takeForms(html) {
196
+ const kept = [];
197
+ let removed = 0;
198
+ const out = html.replace(/<form\b(?:[^>"']|"[^"]*"|'[^']*')*>[\s\S]*?(?:<\/form\s*>|$)/gi, (form)=>{
199
+ if (PAYMENT_FIELD.test(form)) {
200
+ removed += 1;
201
+ return ' ';
202
+ }
203
+ if (kept.length < SITE_IMPORT_PAGE_LIMITS.forms) {
204
+ const fields = [];
205
+ for (const match of form.matchAll(/<(input|textarea|select)\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi)){
206
+ const attributes = parseAttributes(match[2]);
207
+ const type = match[1].toLowerCase() === 'input' ? (attributes['type'] || 'text').toLowerCase() : match[1].toLowerCase();
208
+ if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'image') continue;
209
+ fields.push({
210
+ type,
211
+ name: clip(attributes['name'] || attributes['id'] || '', 80)
212
+ });
213
+ if (fields.length >= SITE_IMPORT_PAGE_LIMITS.fieldsPerForm) break;
214
+ }
215
+ kept.push({
216
+ fields
217
+ });
218
+ }
219
+ return ' ';
220
+ });
221
+ return {
222
+ html: out,
223
+ kept,
224
+ removed
225
+ };
226
+ }
227
+ /** `raw` against the page, as an absolute http(s) address without a fragment. */ export function resolveSiteUrl(raw, base) {
228
+ const value = String(raw != null ? raw : '').trim();
229
+ if (!value || /^(?:javascript|data|mailto|tel|sms|blob|about):/i.test(value)) return null;
230
+ try {
231
+ const url = new URL(value, base);
232
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') return null;
233
+ url.hash = '';
234
+ return url;
235
+ } catch (unused) {
236
+ return null;
237
+ }
238
+ }
239
+ /** The first address a `srcset` offers. */ function firstSrcset(srcset) {
240
+ var _String_split_;
241
+ const first = (_String_split_ = String(srcset != null ? srcset : '').split(',')[0]) == null ? void 0 : _String_split_.trim().split(/\s+/)[0];
242
+ return first || undefined;
243
+ }
244
+ /**
245
+ * The words, structure, pictures and links of one page.
246
+ *
247
+ * `isSameSite(url)` says whether an address is on the site being imported;
248
+ * only those become `links`, and images are marked by it.
249
+ */ export function extractSiteImportPage(html, pageUrl, isSameSite) {
250
+ var _ref;
251
+ var _parseAttributes_lang;
252
+ const empty = {
253
+ title: '',
254
+ description: '',
255
+ lang: null,
256
+ canonical: null,
257
+ blocks: [],
258
+ images: [],
259
+ navLinks: [],
260
+ links: [],
261
+ forms: {
262
+ kept: [],
263
+ removed: 0
264
+ },
265
+ loginForm: false
266
+ };
267
+ let source = String(html != null ? html : '').replace(/<!--[\s\S]*?(?:-->|$)/g, ' ').replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, ' ');
268
+ if (PASSWORD_INPUT.test(source)) return _extends({}, empty, {
269
+ loginForm: true
270
+ });
271
+ source = removeElements(source, REMOVED_WITH_CONTENT).replace(REMOVED_VOID, ' ');
272
+ // Forms next, so a field list is read before the controls go.
273
+ const forms = takeForms(source);
274
+ source = removeElements(forms.html, FORM_CONTROLS);
275
+ const metas = metaTags(source);
276
+ const metaContent = (key, attribute)=>{
277
+ var _ref;
278
+ var _metas_find;
279
+ return collapse((_ref = (_metas_find = metas.find((m)=>{
280
+ var _m_attribute;
281
+ return ((_m_attribute = m[attribute]) != null ? _m_attribute : '').toLowerCase() === key;
282
+ })) == null ? void 0 : _metas_find['content']) != null ? _ref : '');
283
+ };
284
+ const titleMatch = /<title\b[^>]*>([\s\S]*?)<\/title\s*>/i.exec(source);
285
+ const title = clip(collapse(decodeHtmlEntities((_ref = titleMatch == null ? void 0 : titleMatch[1]) != null ? _ref : '')) || metaContent('og:title', 'property'), 300);
286
+ const description = clip(metaContent('description', 'name') || metaContent('og:description', 'property'), 1000);
287
+ const htmlTag = /<html\b((?:[^>"']|"[^"]*"|'[^']*')*)>/i.exec(source);
288
+ const lang = htmlTag ? ((_parseAttributes_lang = parseAttributes(htmlTag[1])['lang']) == null ? void 0 : _parseAttributes_lang.trim()) || null : null;
289
+ let canonical = null;
290
+ for (const match of String(html != null ? html : '').matchAll(/<link\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi)){
291
+ var _attributes_rel;
292
+ const attributes = parseAttributes(match[1]);
293
+ if (((_attributes_rel = attributes['rel']) != null ? _attributes_rel : '').toLowerCase().split(/\s+/).includes('canonical')) {
294
+ var _ref1;
295
+ var _resolveSiteUrl;
296
+ canonical = (_ref1 = (_resolveSiteUrl = resolveSiteUrl(attributes['href'], pageUrl)) == null ? void 0 : _resolveSiteUrl.toString()) != null ? _ref1 : null;
297
+ break;
298
+ }
299
+ }
300
+ const bodyStart = source.search(/<body\b/i);
301
+ const body = bodyStart >= 0 ? source.slice(bodyStart) : source.replace(/<head\b[\s\S]*?<\/head\s*>/i, ' ');
302
+ const hasNav = /<nav\b/i.test(body);
303
+ const blocks = [];
304
+ const images = [];
305
+ const imageSeen = new Set();
306
+ const navLinks = [];
307
+ const links = [];
308
+ const linkSeen = new Set();
309
+ let navDepth = 0;
310
+ let headerDepth = 0;
311
+ let footerDepth = 0;
312
+ let current = null;
313
+ let buffer = '';
314
+ let anchor = null;
315
+ const region = ()=>footerDepth > 0 ? 'footer' : headerDepth > 0 ? 'header' : 'main';
316
+ // A site without a <nav> keeps its menu in the header; its links are the nav.
317
+ const inNav = ()=>navDepth > 0 || !hasNav && headerDepth > 0;
318
+ const flush = ()=>{
319
+ var _ref;
320
+ const text = clip(collapse(decodeHtmlEntities(buffer)), SITE_IMPORT_PAGE_LIMITS.blockChars);
321
+ buffer = '';
322
+ const kind = (_ref = current == null ? void 0 : current.kind) != null ? _ref : 'paragraph';
323
+ const level = current == null ? void 0 : current.level;
324
+ current = null;
325
+ if (!text || navDepth > 0 || blocks.length >= SITE_IMPORT_PAGE_LIMITS.blocks) return;
326
+ const last = blocks[blocks.length - 1];
327
+ if (last && last.kind !== 'image' && last.text === text) return;
328
+ if (kind === 'heading') {
329
+ blocks.push({
330
+ kind,
331
+ level: level != null ? level : 2,
332
+ text,
333
+ region: region()
334
+ });
335
+ } else {
336
+ blocks.push({
337
+ kind,
338
+ text,
339
+ region: region()
340
+ });
341
+ }
342
+ };
343
+ for (const match of body.matchAll(new RegExp(`${TAG.source}|([^<]+)|<`, 'g'))){
344
+ var _match_;
345
+ const text = match[4];
346
+ if (text !== undefined || match[2] === undefined) {
347
+ const run = text != null ? text : '<';
348
+ buffer += run;
349
+ if (anchor) anchor.text += run;
350
+ continue;
351
+ }
352
+ const closing = match[1] === '/';
353
+ const tag = match[2].toLowerCase();
354
+ const attributes = closing ? {} : parseAttributes((_match_ = match[3]) != null ? _match_ : '');
355
+ if (tag === 'a') {
356
+ if (closing) {
357
+ if (anchor) {
358
+ const label = clip(collapse(decodeHtmlEntities(anchor.text)), 120);
359
+ if (inNav() && label && navLinks.length < SITE_IMPORT_PAGE_LIMITS.navLinks) {
360
+ navLinks.push({
361
+ text: label,
362
+ url: anchor.url.toString()
363
+ });
364
+ }
365
+ anchor = null;
366
+ }
367
+ continue;
368
+ }
369
+ const url = resolveSiteUrl(attributes['href'], pageUrl);
370
+ anchor = url ? {
371
+ url,
372
+ text: ''
373
+ } : null;
374
+ if (url && isSameSite(url) && links.length < SITE_IMPORT_PAGE_LIMITS.links) {
375
+ const key = url.toString();
376
+ if (!linkSeen.has(key)) {
377
+ linkSeen.add(key);
378
+ links.push(key);
379
+ }
380
+ }
381
+ continue;
382
+ }
383
+ if (tag === 'img' && !closing) {
384
+ const url = resolveSiteUrl(attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset']), pageUrl);
385
+ if (url && images.length < SITE_IMPORT_PAGE_LIMITS.images) {
386
+ var _attributes_alt;
387
+ const key = url.toString();
388
+ const alt = clip(collapse((_attributes_alt = attributes['alt']) != null ? _attributes_alt : ''), 300);
389
+ if (!imageSeen.has(key)) {
390
+ imageSeen.add(key);
391
+ images.push({
392
+ url: key,
393
+ alt,
394
+ sameSite: isSameSite(url)
395
+ });
396
+ if (navDepth === 0) {
397
+ flush();
398
+ if (blocks.length < SITE_IMPORT_PAGE_LIMITS.blocks) {
399
+ blocks.push({
400
+ kind: 'image',
401
+ url: key,
402
+ alt,
403
+ region: region()
404
+ });
405
+ }
406
+ }
407
+ }
408
+ }
409
+ continue;
410
+ }
411
+ if (tag === 'nav' || tag === 'header' || tag === 'footer') {
412
+ flush();
413
+ const step = closing ? -1 : 1;
414
+ if (tag === 'nav') navDepth = Math.max(0, navDepth + step);
415
+ if (tag === 'header') headerDepth = Math.max(0, headerDepth + step);
416
+ if (tag === 'footer') footerDepth = Math.max(0, footerDepth + step);
417
+ continue;
418
+ }
419
+ const heading = /^h([1-6])$/.exec(tag);
420
+ if (heading || BLOCK_TAGS[tag]) {
421
+ if (closing) {
422
+ if ((current == null ? void 0 : current.tag) === tag) flush();
423
+ continue;
424
+ }
425
+ flush();
426
+ current = heading ? {
427
+ kind: 'heading',
428
+ level: Number(heading[1]),
429
+ tag
430
+ } : {
431
+ kind: BLOCK_TAGS[tag],
432
+ tag
433
+ };
434
+ continue;
435
+ }
436
+ if (BREAK_TAGS.has(tag)) {
437
+ if (tag === 'br' && current) {
438
+ buffer += ' ';
439
+ continue;
440
+ }
441
+ flush();
442
+ }
443
+ }
444
+ flush();
445
+ return {
446
+ title,
447
+ description,
448
+ lang,
449
+ canonical,
450
+ blocks,
451
+ images,
452
+ navLinks,
453
+ links,
454
+ forms: {
455
+ kept: forms.kept,
456
+ removed: forms.removed
457
+ },
458
+ loginForm: false
459
+ };
460
+ }
461
+
462
+ //# sourceMappingURL=site-import-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/site-import/site-import-html.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * WHAT A CRAWLED PAGE SAYS (AGL-3974, parent AGL-3973).\n *\n * \"Bring my site\" reads a person's existing pages for their words, their\n * structure and their pictures. No server HTML parser is a dependency of any\n * lib, and a page's text in order is a small question, so this is a\n * tokenizer: tags and text runs, with the few elements that matter tracked\n * and everything else read through.\n *\n * What never survives, before a single tag is walked:\n * - scripts, styles, iframes, embeds, objects, SVG, canvas, media players,\n * templates and comments, content and all;\n * - any form with a card, bank or payment field, whole;\n * - and a page with a password field is not read at all: it is refused and\n * reported (`loginForm`), because a sign-in page is exactly what a\n * phishing clone wants and nothing a site import needs.\n *\n * The output is DATA. Text from a page reaches a model only fenced as\n * untrusted (`fenceUntrustedText`), never as instructions.\n *=========================================*/\n\n/** One run of a page's content, in the order the page draws it. */\nexport type SiteImportBlock =\n | { kind: 'heading'; level: 1 | 2 | 3 | 4 | 5 | 6; text: string; region: SiteImportRegion }\n | { kind: 'paragraph' | 'list-item' | 'quote'; text: string; region: SiteImportRegion }\n | { kind: 'image'; url: string; alt: string; region: SiteImportRegion }\n\n/** Where on the page a block sat: the header and footer carry business details. */\nexport type SiteImportRegion = 'header' | 'main' | 'footer'\n\nexport interface SiteImportImage {\n /** Absolute https (or http) address. */\n url: string\n alt: string\n /** On the site's own origin; otherwise a CDN or a third party. The importer decides. */\n sameSite: boolean\n}\n\nexport interface SiteImportNavLink {\n text: string\n url: string\n}\n\n/** A form that was kept: what it asks, never what was typed. */\nexport interface SiteImportForm {\n fields: { type: string; name: string }[]\n}\n\nexport interface SiteImportExtract {\n title: string\n description: string\n lang: string | null\n canonical: string | null\n blocks: SiteImportBlock[]\n images: SiteImportImage[]\n navLinks: SiteImportNavLink[]\n /** Same-site page addresses, fragment-free, in first-seen order. */\n links: string[]\n forms: { kept: SiteImportForm[]; removed: number }\n /** True when the page has a password field; nothing else is extracted. */\n loginForm: boolean\n}\n\n/** Ceilings that keep one odd page from becoming the whole inventory. */\nexport const SITE_IMPORT_PAGE_LIMITS = {\n blocks: 400,\n blockChars: 2_000,\n images: 100,\n links: 300,\n navLinks: 60,\n forms: 10,\n fieldsPerForm: 30,\n} as const\n\n/** Elements removed with everything inside them. */\nconst REMOVED_WITH_CONTENT = [\n 'script',\n 'style',\n 'noscript',\n 'template',\n 'iframe',\n 'frame',\n 'frameset',\n 'object',\n 'applet',\n 'svg',\n 'math',\n 'canvas',\n 'video',\n 'audio',\n]\n\n/** Form controls, removed with their content once the forms are summarized. */\nconst FORM_CONTROLS = ['textarea', 'select', 'button']\n\n/** Void elements removed wherever they appear. */\nconst REMOVED_VOID = /<(?:embed|param|source|track|link|base)\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>/gi\n\nconst PASSWORD_INPUT = /<input\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*\\btype\\s*=\\s*[\"']?password\\b/i\n\n/**\n * A field that takes money or a bank detail: an autocomplete hint for a\n * card, or a name or id that says card, CVC, IBAN, routing or payment.\n */\nconst PAYMENT_FIELD =\n /<(?:input|select)\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*?(?:\\bautocomplete\\s*=\\s*[\"']?\\s*cc-|\\b(?:name|id|data-stripe|data-braintree-name)\\s*=\\s*[\"']?[^\"'\\s>]*(?:card|cc[-_]?(?:num|number|exp|csc|cvv|cvc)|cvv|cvc|csc|iban|routing|account[-_]?number|payment|billing[-_]?card))/i\n\nconst BLOCK_TAGS: Record<string, 'paragraph' | 'list-item' | 'quote'> = {\n p: 'paragraph',\n li: 'list-item',\n dt: 'paragraph',\n dd: 'paragraph',\n td: 'paragraph',\n th: 'paragraph',\n address: 'paragraph',\n pre: 'paragraph',\n figcaption: 'paragraph',\n caption: 'paragraph',\n label: 'paragraph',\n blockquote: 'quote',\n}\n\n/** Tags that end a run of loose text without being a block themselves. */\nconst BREAK_TAGS = new Set([\n 'div',\n 'section',\n 'article',\n 'main',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'ul',\n 'ol',\n 'dl',\n 'table',\n 'tr',\n 'tbody',\n 'thead',\n 'figure',\n 'form',\n 'hr',\n 'br',\n 'body',\n])\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: ' ',\n rsquo: '’',\n lsquo: '‘',\n rdquo: '”',\n ldquo: '“',\n mdash: '—',\n ndash: '–',\n hellip: '…',\n copy: '©',\n reg: '®',\n trade: '™',\n middot: '·',\n bull: '•',\n eacute: 'é',\n egrave: 'è',\n aacute: 'á',\n ntilde: 'ñ',\n uuml: 'ü',\n ouml: 'ö',\n auml: 'ä',\n deg: '°',\n times: '×',\n}\n\n/** Character references to characters; an unknown name stays as written. */\nexport function decodeHtmlEntities(text: string): string {\n return text.replace(/&(#x[0-9a-f]+|#\\d+|[a-z][a-z0-9]*);/gi, (whole, body: string) => {\n if (body[0] === '#') {\n const code =\n body[1] === 'x' || body[1] === 'X' ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10)\n if (!Number.isFinite(code) || code <= 0 || code > 0x10ffff) return ''\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) return ''\n return String.fromCodePoint(code)\n }\n const named = NAMED_ENTITIES[body.toLowerCase()]\n return named ?? whole\n })\n}\n\nconst ATTRIBUTE = /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>\"']+)))?/g\n\n/** A tag's attributes, names lowercased, values decoded. */\nexport function parseAttributes(source: string): Record<string, string> {\n const attributes: Record<string, string> = {}\n for (const match of source.matchAll(ATTRIBUTE)) {\n const name = match[1].toLowerCase()\n if (name in attributes) continue\n attributes[name] = decodeHtmlEntities(match[2] ?? match[3] ?? match[4] ?? '')\n }\n return attributes\n}\n\nconst TAG = /<(\\/?)([a-zA-Z][\\w:-]*)((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/g\n\n/** Every `<meta>` tag's attributes. */\nfunction metaTags(html: string): Record<string, string>[] {\n const out: Record<string, string>[] = []\n for (const match of html.matchAll(/<meta\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n out.push(parseAttributes(match[1]))\n }\n return out\n}\n\n/** The `content` of every `<meta name=\"…\">` with this name. */\nexport function readMetaValues(html: string, name: string): string[] {\n const wanted = name.toLowerCase()\n return metaTags(html)\n .filter((attributes) => (attributes['name'] ?? '').toLowerCase() === wanted)\n .map((attributes) => (attributes['content'] ?? '').trim())\n .filter(Boolean)\n}\n\nfunction collapse(text: string): string {\n return text.replace(/\\s+/g, ' ').trim()\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text\n}\n\nfunction removeElements(html: string, tags: readonly string[]): string {\n let out = html\n for (const tag of tags) {\n const paired = new RegExp(`<${tag}\\\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\\\s\\\\S]*?<\\\\/${tag}\\\\s*>`, 'gi')\n out = out.replace(paired, ' ')\n // One left open runs to the end of the document, as a browser reads it.\n const unclosed = new RegExp(`<${tag}\\\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\\\s\\\\S]*$`, 'i')\n out = out.replace(unclosed, ' ')\n }\n return out\n}\n\n/** Forms with a payment field go whole; the rest are summarized, then go. */\nfunction takeForms(html: string): { html: string; kept: SiteImportForm[]; removed: number } {\n const kept: SiteImportForm[] = []\n let removed = 0\n const out = html.replace(/<form\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\s\\S]*?(?:<\\/form\\s*>|$)/gi, (form) => {\n if (PAYMENT_FIELD.test(form)) {\n removed += 1\n return ' '\n }\n if (kept.length < SITE_IMPORT_PAGE_LIMITS.forms) {\n const fields: SiteImportForm['fields'] = []\n for (const match of form.matchAll(/<(input|textarea|select)\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const attributes = parseAttributes(match[2])\n const type = match[1].toLowerCase() === 'input' ? (attributes['type'] || 'text').toLowerCase() : match[1].toLowerCase()\n if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'image') continue\n fields.push({ type, name: clip(attributes['name'] || attributes['id'] || '', 80) })\n if (fields.length >= SITE_IMPORT_PAGE_LIMITS.fieldsPerForm) break\n }\n kept.push({ fields })\n }\n return ' '\n })\n return { html: out, kept, removed }\n}\n\n/** `raw` against the page, as an absolute http(s) address without a fragment. */\nexport function resolveSiteUrl(raw: string | undefined, base: string): URL | null {\n const value = String(raw ?? '').trim()\n if (!value || /^(?:javascript|data|mailto|tel|sms|blob|about):/i.test(value)) return null\n try {\n const url = new URL(value, base)\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return null\n url.hash = ''\n return url\n } catch {\n return null\n }\n}\n\n/** The first address a `srcset` offers. */\nfunction firstSrcset(srcset: string | undefined): string | undefined {\n const first = String(srcset ?? '').split(',')[0]?.trim().split(/\\s+/)[0]\n return first || undefined\n}\n\n/**\n * The words, structure, pictures and links of one page.\n *\n * `isSameSite(url)` says whether an address is on the site being imported;\n * only those become `links`, and images are marked by it.\n */\nexport function extractSiteImportPage(\n html: string,\n pageUrl: string,\n isSameSite: (url: URL) => boolean,\n): SiteImportExtract {\n const empty: SiteImportExtract = {\n title: '',\n description: '',\n lang: null,\n canonical: null,\n blocks: [],\n images: [],\n navLinks: [],\n links: [],\n forms: { kept: [], removed: 0 },\n loginForm: false,\n }\n let source = String(html ?? '')\n .replace(/<!--[\\s\\S]*?(?:-->|$)/g, ' ')\n .replace(/<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/g, ' ')\n if (PASSWORD_INPUT.test(source)) return { ...empty, loginForm: true }\n\n source = removeElements(source, REMOVED_WITH_CONTENT).replace(REMOVED_VOID, ' ')\n // Forms next, so a field list is read before the controls go.\n const forms = takeForms(source)\n source = removeElements(forms.html, FORM_CONTROLS)\n\n const metas = metaTags(source)\n const metaContent = (key: string, attribute: 'name' | 'property') =>\n collapse(metas.find((m) => (m[attribute] ?? '').toLowerCase() === key)?.['content'] ?? '')\n const titleMatch = /<title\\b[^>]*>([\\s\\S]*?)<\\/title\\s*>/i.exec(source)\n const title = clip(\n collapse(decodeHtmlEntities(titleMatch?.[1] ?? '')) || metaContent('og:title', 'property'),\n 300,\n )\n const description = clip(\n metaContent('description', 'name') || metaContent('og:description', 'property'),\n 1_000,\n )\n const htmlTag = /<html\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/i.exec(source)\n const lang = htmlTag ? parseAttributes(htmlTag[1])['lang']?.trim() || null : null\n let canonical: string | null = null\n for (const match of String(html ?? '').matchAll(/<link\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const attributes = parseAttributes(match[1])\n if ((attributes['rel'] ?? '').toLowerCase().split(/\\s+/).includes('canonical')) {\n canonical = resolveSiteUrl(attributes['href'], pageUrl)?.toString() ?? null\n break\n }\n }\n\n const bodyStart = source.search(/<body\\b/i)\n const body = bodyStart >= 0 ? source.slice(bodyStart) : source.replace(/<head\\b[\\s\\S]*?<\\/head\\s*>/i, ' ')\n const hasNav = /<nav\\b/i.test(body)\n\n const blocks: SiteImportBlock[] = []\n const images: SiteImportImage[] = []\n const imageSeen = new Set<string>()\n const navLinks: SiteImportNavLink[] = []\n const links: string[] = []\n const linkSeen = new Set<string>()\n\n let navDepth = 0\n let headerDepth = 0\n let footerDepth = 0\n let current: { kind: 'heading' | 'paragraph' | 'list-item' | 'quote'; level?: number; tag: string } | null = null\n let buffer = ''\n let anchor: { url: URL; text: string } | null = null\n\n const region = (): SiteImportRegion =>\n footerDepth > 0 ? 'footer' : headerDepth > 0 ? 'header' : 'main'\n // A site without a <nav> keeps its menu in the header; its links are the nav.\n const inNav = () => navDepth > 0 || (!hasNav && headerDepth > 0)\n\n const flush = () => {\n const text = clip(collapse(decodeHtmlEntities(buffer)), SITE_IMPORT_PAGE_LIMITS.blockChars)\n buffer = ''\n const kind = current?.kind ?? 'paragraph'\n const level = current?.level\n current = null\n if (!text || navDepth > 0 || blocks.length >= SITE_IMPORT_PAGE_LIMITS.blocks) return\n const last = blocks[blocks.length - 1]\n if (last && last.kind !== 'image' && last.text === text) return\n if (kind === 'heading') {\n blocks.push({ kind, level: (level ?? 2) as 1 | 2 | 3 | 4 | 5 | 6, text, region: region() })\n } else {\n blocks.push({ kind, text, region: region() })\n }\n }\n\n for (const match of body.matchAll(new RegExp(`${TAG.source}|([^<]+)|<`, 'g'))) {\n const text = match[4]\n if (text !== undefined || match[2] === undefined) {\n const run = text ?? '<'\n buffer += run\n if (anchor) anchor.text += run\n continue\n }\n const closing = match[1] === '/'\n const tag = match[2].toLowerCase()\n const attributes = closing ? {} : parseAttributes(match[3] ?? '')\n\n if (tag === 'a') {\n if (closing) {\n if (anchor) {\n const label = clip(collapse(decodeHtmlEntities(anchor.text)), 120)\n if (inNav() && label && navLinks.length < SITE_IMPORT_PAGE_LIMITS.navLinks) {\n navLinks.push({ text: label, url: anchor.url.toString() })\n }\n anchor = null\n }\n continue\n }\n const url = resolveSiteUrl(attributes['href'], pageUrl)\n anchor = url ? { url, text: '' } : null\n if (url && isSameSite(url) && links.length < SITE_IMPORT_PAGE_LIMITS.links) {\n const key = url.toString()\n if (!linkSeen.has(key)) {\n linkSeen.add(key)\n links.push(key)\n }\n }\n continue\n }\n\n if (tag === 'img' && !closing) {\n const url = resolveSiteUrl(\n attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset']),\n pageUrl,\n )\n if (url && images.length < SITE_IMPORT_PAGE_LIMITS.images) {\n const key = url.toString()\n const alt = clip(collapse(attributes['alt'] ?? ''), 300)\n if (!imageSeen.has(key)) {\n imageSeen.add(key)\n images.push({ url: key, alt, sameSite: isSameSite(url) })\n if (navDepth === 0) {\n flush()\n if (blocks.length < SITE_IMPORT_PAGE_LIMITS.blocks) {\n blocks.push({ kind: 'image', url: key, alt, region: region() })\n }\n }\n }\n }\n continue\n }\n\n if (tag === 'nav' || tag === 'header' || tag === 'footer') {\n flush()\n const step = closing ? -1 : 1\n if (tag === 'nav') navDepth = Math.max(0, navDepth + step)\n if (tag === 'header') headerDepth = Math.max(0, headerDepth + step)\n if (tag === 'footer') footerDepth = Math.max(0, footerDepth + step)\n continue\n }\n\n const heading = /^h([1-6])$/.exec(tag)\n if (heading || BLOCK_TAGS[tag]) {\n if (closing) {\n if (current?.tag === tag) flush()\n continue\n }\n flush()\n current = heading\n ? { kind: 'heading', level: Number(heading[1]), tag }\n : { kind: BLOCK_TAGS[tag], tag }\n continue\n }\n\n if (BREAK_TAGS.has(tag)) {\n if (tag === 'br' && current) {\n buffer += ' '\n continue\n }\n flush()\n }\n }\n flush()\n\n return {\n title,\n description,\n lang,\n canonical,\n blocks,\n images,\n navLinks,\n links,\n forms: { kept: forms.kept, removed: forms.removed },\n loginForm: false,\n }\n}\n"],"names":["SITE_IMPORT_PAGE_LIMITS","blocks","blockChars","images","links","navLinks","forms","fieldsPerForm","REMOVED_WITH_CONTENT","FORM_CONTROLS","REMOVED_VOID","PASSWORD_INPUT","PAYMENT_FIELD","BLOCK_TAGS","p","li","dt","dd","td","th","address","pre","figcaption","caption","label","blockquote","BREAK_TAGS","Set","NAMED_ENTITIES","amp","lt","gt","quot","apos","nbsp","rsquo","lsquo","rdquo","ldquo","mdash","ndash","hellip","copy","reg","trade","middot","bull","eacute","egrave","aacute","ntilde","uuml","ouml","auml","deg","times","decodeHtmlEntities","text","replace","whole","body","code","parseInt","slice","Number","isFinite","String","fromCodePoint","named","toLowerCase","ATTRIBUTE","parseAttributes","source","attributes","match","matchAll","name","TAG","metaTags","html","out","push","readMetaValues","wanted","filter","map","trim","Boolean","collapse","clip","max","length","trimEnd","removeElements","tags","tag","paired","RegExp","unclosed","takeForms","kept","removed","form","test","fields","type","resolveSiteUrl","raw","base","value","url","URL","protocol","hash","firstSrcset","srcset","first","split","undefined","extractSiteImportPage","pageUrl","isSameSite","empty","title","description","lang","canonical","loginForm","metas","metaContent","key","attribute","find","m","titleMatch","exec","htmlTag","includes","toString","bodyStart","search","hasNav","imageSeen","linkSeen","navDepth","headerDepth","footerDepth","current","buffer","anchor","region","inNav","flush","kind","level","last","run","closing","has","add","alt","sameSite","step","Math","heading"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;2CAmB2C,GAE3C,iEAAiE;AA0CjE,uEAAuE,GACvE,OAAO,MAAMA,0BAA0B;IACrCC,QAAQ;IACRC,YAAY;IACZC,QAAQ;IACRC,OAAO;IACPC,UAAU;IACVC,OAAO;IACPC,eAAe;AACjB,EAAU;AAEV,kDAAkD,GAClD,MAAMC,uBAAuB;IAC3B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,6EAA6E,GAC7E,MAAMC,gBAAgB;IAAC;IAAY;IAAU;CAAS;AAEtD,gDAAgD,GAChD,MAAMC,eAAe;AAErB,MAAMC,iBAAiB;AAEvB;;;CAGC,GACD,MAAMC,gBACJ;AAEF,MAAMC,aAAkE;IACtEC,GAAG;IACHC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,SAAS;IACTC,KAAK;IACLC,YAAY;IACZC,SAAS;IACTC,OAAO;IACPC,YAAY;AACd;AAEA,wEAAwE,GACxE,MAAMC,aAAa,IAAIC,IAAI;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,MAAMC,iBAAyC;IAC7CC,KAAK;IACLC,IAAI;IACJC,IAAI;IACJC,MAAM;IACNC,MAAM;IACNC,MAAM;IACNC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,QAAQ;IACRC,MAAM;IACNC,KAAK;IACLC,OAAO;IACPC,QAAQ;IACRC,MAAM;IACNC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,MAAM;IACNC,MAAM;IACNC,KAAK;IACLC,OAAO;AACT;AAEA,0EAA0E,GAC1E,OAAO,SAASC,mBAAmBC,IAAY;IAC7C,OAAOA,KAAKC,OAAO,CAAC,yCAAyC,CAACC,OAAOC;QACnE,IAAIA,IAAI,CAAC,EAAE,KAAK,KAAK;YACnB,MAAMC,OACJD,IAAI,CAAC,EAAE,KAAK,OAAOA,IAAI,CAAC,EAAE,KAAK,MAAME,SAASF,KAAKG,KAAK,CAAC,IAAI,MAAMD,SAASF,KAAKG,KAAK,CAAC,IAAI;YAC7F,IAAI,CAACC,OAAOC,QAAQ,CAACJ,SAASA,QAAQ,KAAKA,OAAO,UAAU,OAAO;YACnE,IAAIA,OAAO,MAAMA,SAAS,KAAKA,SAAS,MAAMA,SAAS,IAAI,OAAO;YAClE,OAAOK,OAAOC,aAAa,CAACN;QAC9B;QACA,MAAMO,QAAQxC,cAAc,CAACgC,KAAKS,WAAW,GAAG;QAChD,OAAOD,gBAAAA,QAAST;IAClB;AACF;AAEA,MAAMW,YAAY;AAElB,0DAA0D,GAC1D,OAAO,SAASC,gBAAgBC,MAAc;IAC5C,MAAMC,aAAqC,CAAC;IAC5C,KAAK,MAAMC,SAASF,OAAOG,QAAQ,CAACL,WAAY;YAGRI,MAAAA,OAAAA;QAFtC,MAAME,OAAOF,KAAK,CAAC,EAAE,CAACL,WAAW;QACjC,IAAIO,QAAQH,YAAY;QACxBA,UAAU,CAACG,KAAK,GAAGpB,oBAAmBkB,QAAAA,SAAAA,UAAAA,KAAK,CAAC,EAAE,YAARA,UAAYA,KAAK,CAAC,EAAE,YAApBA,QAAwBA,KAAK,CAAC,EAAE,YAAhCA,OAAoC;IAC5E;IACA,OAAOD;AACT;AAEA,MAAMI,MAAM;AAEZ,qCAAqC,GACrC,SAASC,SAASC,IAAY;IAC5B,MAAMC,MAAgC,EAAE;IACxC,KAAK,MAAMN,SAASK,KAAKJ,QAAQ,CAAC,2CAA4C;QAC5EK,IAAIC,IAAI,CAACV,gBAAgBG,KAAK,CAAC,EAAE;IACnC;IACA,OAAOM;AACT;AAEA,6DAA6D,GAC7D,OAAO,SAASE,eAAeH,IAAY,EAAEH,IAAY;IACvD,MAAMO,SAASP,KAAKP,WAAW;IAC/B,OAAOS,SAASC,MACbK,MAAM,CAAC,CAACX;YAAgBA;eAAD,EAACA,mBAAAA,UAAU,CAAC,OAAO,YAAlBA,mBAAsB,IAAIJ,WAAW,OAAOc;OACpEE,GAAG,CAAC,CAACZ;YAAgBA;eAAD,EAACA,sBAAAA,UAAU,CAAC,UAAU,YAArBA,sBAAyB,IAAIa,IAAI;OACtDF,MAAM,CAACG;AACZ;AAEA,SAASC,SAAS/B,IAAY;IAC5B,OAAOA,KAAKC,OAAO,CAAC,QAAQ,KAAK4B,IAAI;AACvC;AAEA,SAASG,KAAKhC,IAAY,EAAEiC,GAAW;IACrC,OAAOjC,KAAKkC,MAAM,GAAGD,MAAM,GAAGjC,KAAKM,KAAK,CAAC,GAAG2B,MAAM,GAAGE,OAAO,GAAG,CAAC,CAAC,GAAGnC;AACtE;AAEA,SAASoC,eAAed,IAAY,EAAEe,IAAuB;IAC3D,IAAId,MAAMD;IACV,KAAK,MAAMgB,OAAOD,KAAM;QACtB,MAAME,SAAS,IAAIC,OAAO,CAAC,CAAC,EAAEF,IAAI,6CAA6C,EAAEA,IAAI,KAAK,CAAC,EAAE;QAC7Ff,MAAMA,IAAItB,OAAO,CAACsC,QAAQ;QAC1B,wEAAwE;QACxE,MAAME,WAAW,IAAID,OAAO,CAAC,CAAC,EAAEF,IAAI,yCAAyC,CAAC,EAAE;QAChFf,MAAMA,IAAItB,OAAO,CAACwC,UAAU;IAC9B;IACA,OAAOlB;AACT;AAEA,2EAA2E,GAC3E,SAASmB,UAAUpB,IAAY;IAC7B,MAAMqB,OAAyB,EAAE;IACjC,IAAIC,UAAU;IACd,MAAMrB,MAAMD,KAAKrB,OAAO,CAAC,kEAAkE,CAAC4C;QAC1F,IAAI1F,cAAc2F,IAAI,CAACD,OAAO;YAC5BD,WAAW;YACX,OAAO;QACT;QACA,IAAID,KAAKT,MAAM,GAAG3F,wBAAwBM,KAAK,EAAE;YAC/C,MAAMkG,SAAmC,EAAE;YAC3C,KAAK,MAAM9B,SAAS4B,KAAK3B,QAAQ,CAAC,8DAA+D;gBAC/F,MAAMF,aAAaF,gBAAgBG,KAAK,CAAC,EAAE;gBAC3C,MAAM+B,OAAO/B,KAAK,CAAC,EAAE,CAACL,WAAW,OAAO,UAAU,AAACI,CAAAA,UAAU,CAAC,OAAO,IAAI,MAAK,EAAGJ,WAAW,KAAKK,KAAK,CAAC,EAAE,CAACL,WAAW;gBACrH,IAAIoC,SAAS,YAAYA,SAAS,YAAYA,SAAS,YAAYA,SAAS,SAAS;gBACrFD,OAAOvB,IAAI,CAAC;oBAAEwB;oBAAM7B,MAAMa,KAAKhB,UAAU,CAAC,OAAO,IAAIA,UAAU,CAAC,KAAK,IAAI,IAAI;gBAAI;gBACjF,IAAI+B,OAAOb,MAAM,IAAI3F,wBAAwBO,aAAa,EAAE;YAC9D;YACA6F,KAAKnB,IAAI,CAAC;gBAAEuB;YAAO;QACrB;QACA,OAAO;IACT;IACA,OAAO;QAAEzB,MAAMC;QAAKoB;QAAMC;IAAQ;AACpC;AAEA,+EAA+E,GAC/E,OAAO,SAASK,eAAeC,GAAuB,EAAEC,IAAY;IAClE,MAAMC,QAAQ3C,OAAOyC,cAAAA,MAAO,IAAIrB,IAAI;IACpC,IAAI,CAACuB,SAAS,mDAAmDN,IAAI,CAACM,QAAQ,OAAO;IACrF,IAAI;QACF,MAAMC,MAAM,IAAIC,IAAIF,OAAOD;QAC3B,IAAIE,IAAIE,QAAQ,KAAK,YAAYF,IAAIE,QAAQ,KAAK,SAAS,OAAO;QAClEF,IAAIG,IAAI,GAAG;QACX,OAAOH;IACT,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,yCAAyC,GACzC,SAASI,YAAYC,MAA0B;QAC/BjD;IAAd,MAAMkD,SAAQlD,iBAAAA,OAAOiD,iBAAAA,SAAU,IAAIE,KAAK,CAAC,IAAI,CAAC,EAAE,qBAAlCnD,eAAoCoB,IAAI,GAAG+B,KAAK,CAAC,MAAM,CAAC,EAAE;IACxE,OAAOD,SAASE;AAClB;AAEA;;;;;CAKC,GACD,OAAO,SAASC,sBACdxC,IAAY,EACZyC,OAAe,EACfC,UAAiC;;QAqCVlD;IAnCvB,MAAMmD,QAA2B;QAC/BC,OAAO;QACPC,aAAa;QACbC,MAAM;QACNC,WAAW;QACX7H,QAAQ,EAAE;QACVE,QAAQ,EAAE;QACVE,UAAU,EAAE;QACZD,OAAO,EAAE;QACTE,OAAO;YAAE8F,MAAM,EAAE;YAAEC,SAAS;QAAE;QAC9B0B,WAAW;IACb;IACA,IAAIvD,SAASN,OAAOa,eAAAA,OAAQ,IACzBrB,OAAO,CAAC,0BAA0B,KAClCA,OAAO,CAAC,6BAA6B;IACxC,IAAI/C,eAAe4F,IAAI,CAAC/B,SAAS,OAAO,aAAKkD;QAAOK,WAAW;;IAE/DvD,SAASqB,eAAerB,QAAQhE,sBAAsBkD,OAAO,CAAChD,cAAc;IAC5E,8DAA8D;IAC9D,MAAMJ,QAAQ6F,UAAU3B;IACxBA,SAASqB,eAAevF,MAAMyE,IAAI,EAAEtE;IAEpC,MAAMuH,QAAQlD,SAASN;IACvB,MAAMyD,cAAc,CAACC,KAAaC;;YACvBH;eAATxC,kBAASwC,cAAAA,MAAMI,IAAI,CAAC,CAACC;gBAAOA;mBAAD,EAACA,eAAAA,CAAC,CAACF,UAAU,YAAZE,eAAgB,IAAIhE,WAAW,OAAO6D;+BAAzDF,WAA+D,CAAC,UAAU,mBAAI;;IACzF,MAAMM,aAAa,wCAAwCC,IAAI,CAAC/D;IAChE,MAAMmD,QAAQlC,KACZD,SAAShC,2BAAmB8E,8BAAAA,UAAY,CAAC,EAAE,mBAAI,QAAQL,YAAY,YAAY,aAC/E;IAEF,MAAML,cAAcnC,KAClBwC,YAAY,eAAe,WAAWA,YAAY,kBAAkB,aACpE;IAEF,MAAMO,UAAU,yCAAyCD,IAAI,CAAC/D;IAC9D,MAAMqD,OAAOW,UAAUjE,EAAAA,wBAAAA,gBAAgBiE,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,qBAAnCjE,sBAAqCe,IAAI,OAAM,OAAO;IAC7E,IAAIwC,YAA2B;IAC/B,KAAK,MAAMpD,SAASR,OAAOa,eAAAA,OAAQ,IAAIJ,QAAQ,CAAC,2CAA4C;YAErFF;QADL,MAAMA,aAAaF,gBAAgBG,KAAK,CAAC,EAAE;QAC3C,IAAI,EAACD,kBAAAA,UAAU,CAAC,MAAM,YAAjBA,kBAAqB,IAAIJ,WAAW,GAAGgD,KAAK,CAAC,OAAOoB,QAAQ,CAAC,cAAc;;gBAClE/B;YAAZoB,sBAAYpB,kBAAAA,eAAejC,UAAU,CAAC,OAAO,EAAE+C,6BAAnCd,gBAA6CgC,QAAQ,sBAAM;YACvE;QACF;IACF;IAEA,MAAMC,YAAYnE,OAAOoE,MAAM,CAAC;IAChC,MAAMhF,OAAO+E,aAAa,IAAInE,OAAOT,KAAK,CAAC4E,aAAanE,OAAOd,OAAO,CAAC,+BAA+B;IACtG,MAAMmF,SAAS,UAAUtC,IAAI,CAAC3C;IAE9B,MAAM3D,SAA4B,EAAE;IACpC,MAAME,SAA4B,EAAE;IACpC,MAAM2I,YAAY,IAAInH;IACtB,MAAMtB,WAAgC,EAAE;IACxC,MAAMD,QAAkB,EAAE;IAC1B,MAAM2I,WAAW,IAAIpH;IAErB,IAAIqH,WAAW;IACf,IAAIC,cAAc;IAClB,IAAIC,cAAc;IAClB,IAAIC,UAAyG;IAC7G,IAAIC,SAAS;IACb,IAAIC,SAA4C;IAEhD,MAAMC,SAAS,IACbJ,cAAc,IAAI,WAAWD,cAAc,IAAI,WAAW;IAC5D,8EAA8E;IAC9E,MAAMM,QAAQ,IAAMP,WAAW,KAAM,CAACH,UAAUI,cAAc;IAE9D,MAAMO,QAAQ;;QACZ,MAAM/F,OAAOgC,KAAKD,SAAShC,mBAAmB4F,UAAUpJ,wBAAwBE,UAAU;QAC1FkJ,SAAS;QACT,MAAMK,eAAON,2BAAAA,QAASM,IAAI,mBAAI;QAC9B,MAAMC,QAAQP,2BAAAA,QAASO,KAAK;QAC5BP,UAAU;QACV,IAAI,CAAC1F,QAAQuF,WAAW,KAAK/I,OAAO0F,MAAM,IAAI3F,wBAAwBC,MAAM,EAAE;QAC9E,MAAM0J,OAAO1J,MAAM,CAACA,OAAO0F,MAAM,GAAG,EAAE;QACtC,IAAIgE,QAAQA,KAAKF,IAAI,KAAK,WAAWE,KAAKlG,IAAI,KAAKA,MAAM;QACzD,IAAIgG,SAAS,WAAW;YACtBxJ,OAAOgF,IAAI,CAAC;gBAAEwE;gBAAMC,KAAK,EAAGA,gBAAAA,QAAS;gBAA6BjG;gBAAM6F,QAAQA;YAAS;QAC3F,OAAO;YACLrJ,OAAOgF,IAAI,CAAC;gBAAEwE;gBAAMhG;gBAAM6F,QAAQA;YAAS;QAC7C;IACF;IAEA,KAAK,MAAM5E,SAASd,KAAKe,QAAQ,CAAC,IAAIsB,OAAO,GAAGpB,IAAIL,MAAM,CAAC,UAAU,CAAC,EAAE,MAAO;YAU3BE;QATlD,MAAMjB,OAAOiB,KAAK,CAAC,EAAE;QACrB,IAAIjB,SAAS6D,aAAa5C,KAAK,CAAC,EAAE,KAAK4C,WAAW;YAChD,MAAMsC,MAAMnG,eAAAA,OAAQ;YACpB2F,UAAUQ;YACV,IAAIP,QAAQA,OAAO5F,IAAI,IAAImG;YAC3B;QACF;QACA,MAAMC,UAAUnF,KAAK,CAAC,EAAE,KAAK;QAC7B,MAAMqB,MAAMrB,KAAK,CAAC,EAAE,CAACL,WAAW;QAChC,MAAMI,aAAaoF,UAAU,CAAC,IAAItF,iBAAgBG,UAAAA,KAAK,CAAC,EAAE,YAARA,UAAY;QAE9D,IAAIqB,QAAQ,KAAK;YACf,IAAI8D,SAAS;gBACX,IAAIR,QAAQ;oBACV,MAAM7H,QAAQiE,KAAKD,SAAShC,mBAAmB6F,OAAO5F,IAAI,IAAI;oBAC9D,IAAI8F,WAAW/H,SAASnB,SAASsF,MAAM,GAAG3F,wBAAwBK,QAAQ,EAAE;wBAC1EA,SAAS4E,IAAI,CAAC;4BAAExB,MAAMjC;4BAAOsF,KAAKuC,OAAOvC,GAAG,CAAC4B,QAAQ;wBAAG;oBAC1D;oBACAW,SAAS;gBACX;gBACA;YACF;YACA,MAAMvC,MAAMJ,eAAejC,UAAU,CAAC,OAAO,EAAE+C;YAC/C6B,SAASvC,MAAM;gBAAEA;gBAAKrD,MAAM;YAAG,IAAI;YACnC,IAAIqD,OAAOW,WAAWX,QAAQ1G,MAAMuF,MAAM,GAAG3F,wBAAwBI,KAAK,EAAE;gBAC1E,MAAM8H,MAAMpB,IAAI4B,QAAQ;gBACxB,IAAI,CAACK,SAASe,GAAG,CAAC5B,MAAM;oBACtBa,SAASgB,GAAG,CAAC7B;oBACb9H,MAAM6E,IAAI,CAACiD;gBACb;YACF;YACA;QACF;QAEA,IAAInC,QAAQ,SAAS,CAAC8D,SAAS;YAC7B,MAAM/C,MAAMJ,eACVjC,UAAU,CAAC,MAAM,IAAIA,UAAU,CAAC,WAAW,IAAIyC,YAAYzC,UAAU,CAAC,SAAS,IAAIA,UAAU,CAAC,cAAc,GAC5G+C;YAEF,IAAIV,OAAO3G,OAAOwF,MAAM,GAAG3F,wBAAwBG,MAAM,EAAE;oBAE/BsE;gBAD1B,MAAMyD,MAAMpB,IAAI4B,QAAQ;gBACxB,MAAMsB,MAAMvE,KAAKD,UAASf,kBAAAA,UAAU,CAAC,MAAM,YAAjBA,kBAAqB,KAAK;gBACpD,IAAI,CAACqE,UAAUgB,GAAG,CAAC5B,MAAM;oBACvBY,UAAUiB,GAAG,CAAC7B;oBACd/H,OAAO8E,IAAI,CAAC;wBAAE6B,KAAKoB;wBAAK8B;wBAAKC,UAAUxC,WAAWX;oBAAK;oBACvD,IAAIkC,aAAa,GAAG;wBAClBQ;wBACA,IAAIvJ,OAAO0F,MAAM,GAAG3F,wBAAwBC,MAAM,EAAE;4BAClDA,OAAOgF,IAAI,CAAC;gCAAEwE,MAAM;gCAAS3C,KAAKoB;gCAAK8B;gCAAKV,QAAQA;4BAAS;wBAC/D;oBACF;gBACF;YACF;YACA;QACF;QAEA,IAAIvD,QAAQ,SAASA,QAAQ,YAAYA,QAAQ,UAAU;YACzDyD;YACA,MAAMU,OAAOL,UAAU,CAAC,IAAI;YAC5B,IAAI9D,QAAQ,OAAOiD,WAAWmB,KAAKzE,GAAG,CAAC,GAAGsD,WAAWkB;YACrD,IAAInE,QAAQ,UAAUkD,cAAckB,KAAKzE,GAAG,CAAC,GAAGuD,cAAciB;YAC9D,IAAInE,QAAQ,UAAUmD,cAAciB,KAAKzE,GAAG,CAAC,GAAGwD,cAAcgB;YAC9D;QACF;QAEA,MAAME,UAAU,aAAa7B,IAAI,CAACxC;QAClC,IAAIqE,WAAWvJ,UAAU,CAACkF,IAAI,EAAE;YAC9B,IAAI8D,SAAS;gBACX,IAAIV,CAAAA,2BAAAA,QAASpD,GAAG,MAAKA,KAAKyD;gBAC1B;YACF;YACAA;YACAL,UAAUiB,UACN;gBAAEX,MAAM;gBAAWC,OAAO1F,OAAOoG,OAAO,CAAC,EAAE;gBAAGrE;YAAI,IAClD;gBAAE0D,MAAM5I,UAAU,CAACkF,IAAI;gBAAEA;YAAI;YACjC;QACF;QAEA,IAAIrE,WAAWoI,GAAG,CAAC/D,MAAM;YACvB,IAAIA,QAAQ,QAAQoD,SAAS;gBAC3BC,UAAU;gBACV;YACF;YACAI;QACF;IACF;IACAA;IAEA,OAAO;QACL7B;QACAC;QACAC;QACAC;QACA7H;QACAE;QACAE;QACAD;QACAE,OAAO;YAAE8F,MAAM9F,MAAM8F,IAAI;YAAEC,SAAS/F,MAAM+F,OAAO;QAAC;QAClD0B,WAAW;IACb;AACF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest';
18
+ import { type PublicFetchOptions, type PublicFetchResult } from '../jobs/ai-theme-brand-inputs';
19
+ import type { AiSiteImportPage } from '../model/ai-site-import';
20
+ /** The largest picture brought in. */
21
+ export declare const SITE_IMPORT_IMAGE_MAX_BYTES: number;
22
+ /** A picture smaller than this is a spacer or a tracking pixel, not a photo. */
23
+ export declare const SITE_IMPORT_IMAGE_MIN_BYTES = 1024;
24
+ /** One picture's own clock. */
25
+ export declare const SITE_IMPORT_IMAGE_TIMEOUT_MS = 8000;
26
+ /** A picture's address as it is fetched and keyed: https, no fragment or tracking; `null` for one that is not a photo. */
27
+ export declare function siteImportImageUrl(raw: string): string | null;
28
+ /** The key a site's library holds an imported picture by. */
29
+ export declare function siteImportImageKey(url: string): string;
30
+ /** A file name for the library, from the picture's address and type. */
31
+ export declare function siteImportImageFileName(url: string, contentType: string): string;
32
+ export interface SiteImportPhotosInput {
33
+ hostId: string;
34
+ /** The member the photos are stored as: the job's creator. */
35
+ uid: string;
36
+ domain: string;
37
+ images: ReadonlyArray<{
38
+ url: string;
39
+ alt: string;
40
+ }>;
41
+ signal?: AbortSignal;
42
+ /** When the pass must stop asking for more. */
43
+ deadlineAt: number;
44
+ }
45
+ export interface SiteImportPhotosDeps {
46
+ ingest: PluginMediaIngest | null;
47
+ fetch?: (url: string, options: PublicFetchOptions) => Promise<PublicFetchResult>;
48
+ now?: () => number;
49
+ }
50
+ /**
51
+ * Brings one page's pictures into the site's library, reusing any the
52
+ * library already holds by key. Never throws for anything a picture does.
53
+ */
54
+ export declare function bringInSiteImportPhotos(input: SiteImportPhotosInput, deps: SiteImportPhotosDeps): Promise<NonNullable<AiSiteImportPage['photos']>>;