@aglyn/aglyn 1.0.0-beta.220 → 1.0.0-beta.222
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.
- package/package.json +15 -11
- package/src/lib/app-utils/agent-openapi.js +1 -1
- package/src/lib/app-utils/agent-openapi.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +3 -2
- package/src/lib/app-utils/analytics-events.js +3 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/api-plugins.d.ts +30 -0
- package/src/lib/app-utils/api-plugins.js +24 -1
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/author-html.d.ts +1 -1
- package/src/lib/app-utils/author-html.js.map +1 -1
- package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
- package/src/lib/app-utils/bandwidth-cap.js +17 -1
- package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
- package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
- package/src/lib/app-utils/compose-reusable-components.js +116 -26
- package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +4 -0
- package/src/lib/app-utils/console-routes.js +3 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +71 -0
- package/src/lib/app-utils/contacts.js +55 -0
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/contexts.d.ts +1 -0
- package/src/lib/app-utils/contexts.js +1 -0
- package/src/lib/app-utils/contexts.js.map +1 -1
- package/src/lib/app-utils/crm-email-templates.js +21 -4
- package/src/lib/app-utils/crm-email-templates.js.map +1 -1
- package/src/lib/app-utils/crm-kinds.d.ts +2 -0
- package/src/lib/app-utils/crm-kinds.js +1 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +1199 -67
- package/src/lib/app-utils/crm.js +1951 -310
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/csv-import.d.ts +10 -38
- package/src/lib/app-utils/csv-import.js +17 -42
- package/src/lib/app-utils/csv-import.js.map +1 -1
- package/src/lib/app-utils/csv.d.ts +32 -11
- package/src/lib/app-utils/csv.js +43 -5
- package/src/lib/app-utils/csv.js.map +1 -1
- package/src/lib/app-utils/default-site.d.ts +116 -0
- package/src/lib/app-utils/default-site.js +1024 -0
- package/src/lib/app-utils/default-site.js.map +1 -0
- package/src/lib/app-utils/docs-help.generated.d.ts +23 -23
- package/src/lib/app-utils/docs-help.generated.js +47 -10
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +746 -151
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
- package/src/lib/app-utils/draft-placeholders.js +115 -0
- package/src/lib/app-utils/draft-placeholders.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
- package/src/lib/app-utils/entity-picker-context.js +4 -4
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
- package/src/lib/app-utils/expand-repeatables.js +88 -14
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.d.ts +33 -22
- package/src/lib/app-utils/forms.js +47 -28
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/full-use-cost.d.ts +277 -0
- package/src/lib/app-utils/full-use-cost.js +421 -0
- package/src/lib/app-utils/full-use-cost.js.map +1 -0
- package/src/lib/app-utils/health-report.js +33 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/leaving-notice.d.ts +96 -0
- package/src/lib/app-utils/leaving-notice.js +157 -0
- package/src/lib/app-utils/leaving-notice.js.map +1 -0
- package/src/lib/app-utils/llms-txt.d.ts +20 -0
- package/src/lib/app-utils/llms-txt.js +7 -1
- package/src/lib/app-utils/llms-txt.js.map +1 -1
- package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
- package/src/lib/app-utils/media-asset-facts.js +40 -13
- package/src/lib/app-utils/media-asset-facts.js.map +1 -1
- package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
- package/src/lib/app-utils/media-bandwidth.js +94 -0
- package/src/lib/app-utils/media-bandwidth.js.map +1 -0
- package/src/lib/app-utils/media-ref.d.ts +241 -33
- package/src/lib/app-utils/media-ref.js +324 -43
- package/src/lib/app-utils/media-ref.js.map +1 -1
- package/src/lib/app-utils/media-src.d.ts +8 -8
- package/src/lib/app-utils/media-src.js +27 -11
- package/src/lib/app-utils/media-src.js.map +1 -1
- package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
- package/src/lib/app-utils/media-takedown-reach.js +6 -3
- package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +1 -1
- package/src/lib/app-utils/notifications.js +2 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.js +17 -0
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/organizations.d.ts +51 -2
- package/src/lib/app-utils/organizations.js +56 -3
- package/src/lib/app-utils/organizations.js.map +1 -1
- package/src/lib/app-utils/page-record-sources.d.ts +87 -0
- package/src/lib/app-utils/page-record-sources.js +53 -0
- package/src/lib/app-utils/page-record-sources.js.map +1 -0
- package/src/lib/app-utils/paid-media-source.d.ts +3 -7
- package/src/lib/app-utils/paid-media-source.js +5 -19
- package/src/lib/app-utils/paid-media-source.js.map +1 -1
- package/src/lib/app-utils/person-erasure.d.ts +5 -2
- package/src/lib/app-utils/person-erasure.js.map +1 -1
- package/src/lib/app-utils/picklists.d.ts +187 -0
- package/src/lib/app-utils/picklists.js +320 -0
- package/src/lib/app-utils/picklists.js.map +1 -0
- package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
- package/src/lib/app-utils/plan-entitlements.js +309 -115
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +1 -1
- package/src/lib/app-utils/release-flags.js +11 -0
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/rendered-widths.d.ts +121 -0
- package/src/lib/app-utils/rendered-widths.js +211 -0
- package/src/lib/app-utils/rendered-widths.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +2 -1
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/screen-link-context-value.d.ts +7 -0
- package/src/lib/app-utils/screen-link-context-value.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +14 -2
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +157 -36
- package/src/lib/app-utils/screen-route.js +191 -41
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +11 -1
- package/src/lib/app-utils/seo-audit.js +33 -13
- package/src/lib/app-utils/seo-audit.js.map +1 -1
- package/src/lib/app-utils/seo-keywords.d.ts +11 -0
- package/src/lib/app-utils/seo-keywords.js +19 -5
- package/src/lib/app-utils/seo-keywords.js.map +1 -1
- package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
- package/src/lib/app-utils/seo-page-facts.js +136 -31
- package/src/lib/app-utils/seo-page-facts.js.map +1 -1
- package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
- package/src/lib/app-utils/seo-site-scan.js +113 -12
- package/src/lib/app-utils/seo-site-scan.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +5 -4
- package/src/lib/app-utils/server.js +14 -5
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-icon-set.d.ts +195 -0
- package/src/lib/app-utils/site-icon-set.js +351 -0
- package/src/lib/app-utils/site-icon-set.js.map +1 -0
- package/src/lib/app-utils/site-interactions.d.ts +101 -4
- package/src/lib/app-utils/site-interactions.js +120 -4
- package/src/lib/app-utils/site-interactions.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
- package/src/lib/app-utils/starter-template-nodes.js +3 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +5 -19
- package/src/lib/app-utils/starter-templates.js +5 -49
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
- package/src/lib/app-utils/transfer-launcher-context.js +23 -0
- package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
- package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
- package/src/lib/app-utils/transfer-resume-import.js +128 -0
- package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
- package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
- package/src/lib/app-utils/upgrade-proposal.js +95 -0
- package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
- package/src/lib/app-utils/utc-month.d.ts +33 -0
- package/src/lib/app-utils/utc-month.js +35 -0
- package/src/lib/app-utils/utc-month.js.map +1 -0
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/app-utils/wire-nodes.d.ts +39 -19
- package/src/lib/app-utils/wire-nodes.js +120 -34
- package/src/lib/app-utils/wire-nodes.js.map +1 -1
- package/src/lib/data-transfer/access.d.ts +74 -0
- package/src/lib/data-transfer/access.js +93 -0
- package/src/lib/data-transfer/access.js.map +1 -0
- package/src/lib/data-transfer/derive.d.ts +246 -0
- package/src/lib/data-transfer/derive.js +1007 -0
- package/src/lib/data-transfer/derive.js.map +1 -0
- package/src/lib/data-transfer/export-file.d.ts +46 -0
- package/src/lib/data-transfer/export-file.js +146 -0
- package/src/lib/data-transfer/export-file.js.map +1 -0
- package/src/lib/data-transfer/field-catalog.d.ts +134 -0
- package/src/lib/data-transfer/field-catalog.js +278 -0
- package/src/lib/data-transfer/field-catalog.js.map +1 -0
- package/src/lib/data-transfer/header-match.d.ts +129 -0
- package/src/lib/data-transfer/header-match.js +367 -0
- package/src/lib/data-transfer/header-match.js.map +1 -0
- package/src/lib/data-transfer/index.d.ts +34 -0
- package/src/lib/data-transfer/index.js +40 -0
- package/src/lib/data-transfer/index.js.map +1 -0
- package/src/lib/data-transfer/job.d.ts +190 -0
- package/src/lib/data-transfer/job.js +229 -0
- package/src/lib/data-transfer/job.js.map +1 -0
- package/src/lib/data-transfer/lookup.d.ts +45 -0
- package/src/lib/data-transfer/lookup.js +71 -0
- package/src/lib/data-transfer/lookup.js.map +1 -0
- package/src/lib/data-transfer/match.d.ts +99 -0
- package/src/lib/data-transfer/match.js +244 -0
- package/src/lib/data-transfer/match.js.map +1 -0
- package/src/lib/data-transfer/package-plan.d.ts +205 -0
- package/src/lib/data-transfer/package-plan.js +475 -0
- package/src/lib/data-transfer/package-plan.js.map +1 -0
- package/src/lib/data-transfer/package.d.ts +181 -0
- package/src/lib/data-transfer/package.js +426 -0
- package/src/lib/data-transfer/package.js.map +1 -0
- package/src/lib/data-transfer/picklist-map.d.ts +139 -0
- package/src/lib/data-transfer/picklist-map.js +271 -0
- package/src/lib/data-transfer/picklist-map.js.map +1 -0
- package/src/lib/data-transfer/plan.d.ts +176 -0
- package/src/lib/data-transfer/plan.js +451 -0
- package/src/lib/data-transfer/plan.js.map +1 -0
- package/src/lib/data-transfer/policy.d.ts +170 -0
- package/src/lib/data-transfer/policy.js +378 -0
- package/src/lib/data-transfer/policy.js.map +1 -0
- package/src/lib/data-transfer/resource.d.ts +211 -0
- package/src/lib/data-transfer/resource.js +241 -0
- package/src/lib/data-transfer/resource.js.map +1 -0
- package/src/lib/data-transfer/review.d.ts +70 -0
- package/src/lib/data-transfer/review.js +233 -0
- package/src/lib/data-transfer/review.js.map +1 -0
- package/src/lib/data-transfer/similarity.d.ts +40 -0
- package/src/lib/data-transfer/similarity.js +106 -0
- package/src/lib/data-transfer/similarity.js.map +1 -0
- package/src/lib/data-transfer/site-package.d.ts +365 -0
- package/src/lib/data-transfer/site-package.js +1077 -0
- package/src/lib/data-transfer/site-package.js.map +1 -0
- package/src/lib/data-transfer/source.d.ts +74 -0
- package/src/lib/data-transfer/source.js +293 -0
- package/src/lib/data-transfer/source.js.map +1 -0
- package/src/lib/data-transfer/transfer-api.d.ts +898 -0
- package/src/lib/data-transfer/transfer-api.js +107 -0
- package/src/lib/data-transfer/transfer-api.js.map +1 -0
- package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +91 -2
- package/src/lib/plugin-manager/feature-plugins.js +48 -3
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +56 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +1132 -27
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/index.d.ts +0 -1
- package/src/lib/plugin-manager/index.js +0 -1
- package/src/lib/plugin-manager/index.js.map +1 -1
- package/src/lib/plugin-manager/interaction-recipes.d.ts +142 -0
- package/src/lib/plugin-manager/interaction-recipes.js +104 -0
- package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
- package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
- package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
- package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
- package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
- package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
- package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-jobs.js +7 -7
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
- package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
- package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
- package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
- package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
- package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
- package/src/lib/plugin-manager/plugin-person-records.js +107 -0
- package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
- package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
- package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
- package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
- package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
- package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
- package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
- package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
- package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
- package/src/lib/plugin-manager/plugin-revocations.js +47 -0
- package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
- package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
- package/src/lib/plugin-manager/plugin-server-steps.d.ts +170 -0
- package/src/lib/plugin-manager/plugin-server-steps.js +116 -0
- package/src/lib/plugin-manager/plugin-server-steps.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
- package/src/lib/plugin-manager/plugin-site-export.js +23 -10
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
- package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
- package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +5 -6
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
- package/src/lib/app-utils/actions.d.ts +0 -310
- package/src/lib/app-utils/actions.js +0 -506
- package/src/lib/app-utils/actions.js.map +0 -1
- package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
- package/src/lib/app-utils/automation-placeholders.js +0 -207
- package/src/lib/app-utils/automation-placeholders.js.map +0 -1
- package/src/lib/app-utils/dataset-models.d.ts +0 -339
- package/src/lib/app-utils/dataset-models.js +0 -722
- package/src/lib/app-utils/dataset-models.js.map +0 -1
- package/src/lib/app-utils/datasets.d.ts +0 -171
- package/src/lib/app-utils/datasets.js +0 -227
- package/src/lib/app-utils/datasets.js.map +0 -1
- package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
- package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
- package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
- package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
- package/src/lib/plugin-manager/order-fulfilment.js +0 -104
- package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-keywords.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 * Target keywords: the words a site owner wants a page found for, and where\n * the page already says them.\n *\n * The SEO check (`seo-audit.ts`) reports a keyword a page never says, and\n * anything that writes a listing holds its answer to the same count, so\n * \"the page says it\" means one thing everywhere: the keyword as whole words,\n * in any case, never inside a longer word.\n */\n\n/** Target keywords one page may name. */\nexport const SEO_MAX_KEYWORDS = 5\n\n/** The longest target keyword text a site's lines run to: a line a page, for a large site. */\nexport const SEO_KEYWORD_LINES_MAX_CHARS = 3_000\n\n/** The longest one keyword is kept. */\nconst SEO_KEYWORD_MAX_CHARS = 60\n\n/** Whether, and where, a page already says a target keyword. */\nexport interface SeoKeywordCoverage {\n keyword: string\n inTitle: boolean\n inDescription: boolean\n inH1: boolean\n inBody: boolean\n}\n\nconst escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/** How many times `keyword` appears in `text` as whole words, any case. */\nexport function seoKeywordCount(text: string | null | undefined, keyword: string): number {\n const needle = keyword.trim().toLowerCase()\n if (!needle || !text) return 0\n const pattern = new RegExp(\n `(^|[^\\\\p{L}\\\\p{N}])${escapeRegExp(needle).replace(/\\s+/g, '\\\\s+')}(?=$|[^\\\\p{L}\\\\p{N}])`,\n 'giu',\n )\n return [...text.toLowerCase().matchAll(pattern)].length\n}\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-keywords.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 * Target keywords: the words a site owner wants a page found for, and where\n * the page already says them.\n *\n * The SEO check (`seo-audit.ts`) reports a keyword a page never says, and\n * anything that writes a listing holds its answer to the same count, so\n * \"the page says it\" means one thing everywhere: the keyword as whole words,\n * in any case, never inside a longer word.\n */\n\n/** Target keywords one page may name. */\nexport const SEO_MAX_KEYWORDS = 5\n\n/** The longest target keyword text a site's lines run to: a line a page, for a large site. */\nexport const SEO_KEYWORD_LINES_MAX_CHARS = 3_000\n\n/** The longest one keyword is kept. */\nconst SEO_KEYWORD_MAX_CHARS = 60\n\n/** Whether, and where, a page already says a target keyword. */\nexport interface SeoKeywordCoverage {\n keyword: string\n inTitle: boolean\n inDescription: boolean\n inH1: boolean\n inBody: boolean\n}\n\nconst escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/** How many times `keyword` appears in `text` as whole words, any case. */\nexport function seoKeywordCount(text: string | null | undefined, keyword: string): number {\n const needle = keyword.trim().toLowerCase()\n if (!needle || !text) return 0\n const pattern = new RegExp(\n `(^|[^\\\\p{L}\\\\p{N}])${escapeRegExp(needle).replace(/\\s+/g, '\\\\s+')}(?=$|[^\\\\p{L}\\\\p{N}])`,\n 'giu',\n )\n return [...text.toLowerCase().matchAll(pattern)].length\n}\n\n/**\n * Keywords as a person typed them — trimmed, deduplicated — split at\n * {@link SEO_MAX_KEYWORDS}: the ones a page is checked for, and the distinct\n * ones past the limit, which a caller names rather than drops in silence.\n */\nexport function seoKeywordSplit(raw: unknown): { keywords: string[]; unchecked: string[] } {\n const parts = Array.isArray(raw) ? raw.map(String) : String(raw ?? '').split(/[,\\n]/)\n const seen = new Set<string>()\n const keywords: string[] = []\n const unchecked: string[] = []\n for (const part of parts) {\n const keyword = part.replace(/\\s+/g, ' ').trim().slice(0, SEO_KEYWORD_MAX_CHARS)\n const key = keyword.toLowerCase()\n if (!keyword || seen.has(key)) continue\n seen.add(key)\n if (keywords.length < SEO_MAX_KEYWORDS) keywords.push(keyword)\n else unchecked.push(keyword)\n }\n return { keywords, unchecked }\n}\n\n/** Keywords as a person typed them: trimmed, deduplicated, capped at {@link SEO_MAX_KEYWORDS}. */\nexport function seoKeywordList(raw: unknown): string[] {\n return seoKeywordSplit(raw).keywords\n}\n\n/** How a note names keywords: each quoted, in the order they were typed. */\nexport function seoQuotedKeywords(keywords: readonly string[]): string {\n return keywords.map((keyword) => `“${keyword}”`).join(', ')\n}\n\n/** Where a page already says each target keyword. */\nexport function seoKeywordCoverage(\n keywords: readonly string[],\n sources: { title?: string | null; description?: string | null; h1?: string | null; body?: string | null },\n): SeoKeywordCoverage[] {\n return keywords.map((keyword) => ({\n keyword,\n inTitle: seoKeywordCount(sources.title, keyword) > 0,\n inDescription: seoKeywordCount(sources.description, keyword) > 0,\n inH1: seoKeywordCount(sources.h1, keyword) > 0,\n inBody: seoKeywordCount(sources.body, keyword) > 0,\n }))\n}\n"],"names":["SEO_MAX_KEYWORDS","SEO_KEYWORD_LINES_MAX_CHARS","SEO_KEYWORD_MAX_CHARS","escapeRegExp","value","replace","seoKeywordCount","text","keyword","needle","trim","toLowerCase","pattern","RegExp","matchAll","length","seoKeywordSplit","raw","parts","Array","isArray","map","String","split","seen","Set","keywords","unchecked","part","slice","key","has","add","push","seoKeywordList","seoQuotedKeywords","join","seoKeywordCoverage","sources","inTitle","title","inDescription","description","inH1","h1","inBody","body"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;CAQC,GAED,uCAAuC,GACvC,OAAO,MAAMA,mBAAmB,EAAC;AAEjC,4FAA4F,GAC5F,OAAO,MAAMC,8BAA8B,KAAK;AAEhD,qCAAqC,GACrC,MAAMC,wBAAwB;AAW9B,MAAMC,eAAe,CAACC,QAA0BA,MAAMC,OAAO,CAAC,uBAAuB;AAErF,yEAAyE,GACzE,OAAO,SAASC,gBAAgBC,IAA+B,EAAEC,OAAe;IAC9E,MAAMC,SAASD,QAAQE,IAAI,GAAGC,WAAW;IACzC,IAAI,CAACF,UAAU,CAACF,MAAM,OAAO;IAC7B,MAAMK,UAAU,IAAIC,OAClB,CAAC,mBAAmB,EAAEV,aAAaM,QAAQJ,OAAO,CAAC,QAAQ,QAAQ,qBAAqB,CAAC,EACzF;IAEF,OAAO;WAAIE,KAAKI,WAAW,GAAGG,QAAQ,CAACF;KAAS,CAACG,MAAM;AACzD;AAEA;;;;CAIC,GACD,OAAO,SAASC,gBAAgBC,GAAY;IAC1C,MAAMC,QAAQC,MAAMC,OAAO,CAACH,OAAOA,IAAII,GAAG,CAACC,UAAUA,OAAOL,cAAAA,MAAO,IAAIM,KAAK,CAAC;IAC7E,MAAMC,OAAO,IAAIC;IACjB,MAAMC,WAAqB,EAAE;IAC7B,MAAMC,YAAsB,EAAE;IAC9B,KAAK,MAAMC,QAAQV,MAAO;QACxB,MAAMV,UAAUoB,KAAKvB,OAAO,CAAC,QAAQ,KAAKK,IAAI,GAAGmB,KAAK,CAAC,GAAG3B;QAC1D,MAAM4B,MAAMtB,QAAQG,WAAW;QAC/B,IAAI,CAACH,WAAWgB,KAAKO,GAAG,CAACD,MAAM;QAC/BN,KAAKQ,GAAG,CAACF;QACT,IAAIJ,SAASX,MAAM,GAAGf,kBAAkB0B,SAASO,IAAI,CAACzB;aACjDmB,UAAUM,IAAI,CAACzB;IACtB;IACA,OAAO;QAAEkB;QAAUC;IAAU;AAC/B;AAEA,gGAAgG,GAChG,OAAO,SAASO,eAAejB,GAAY;IACzC,OAAOD,gBAAgBC,KAAKS,QAAQ;AACtC;AAEA,0EAA0E,GAC1E,OAAO,SAASS,kBAAkBT,QAA2B;IAC3D,OAAOA,SAASL,GAAG,CAAC,CAACb,UAAY,CAAC,CAAC,EAAEA,QAAQ,CAAC,CAAC,EAAE4B,IAAI,CAAC;AACxD;AAEA,mDAAmD,GACnD,OAAO,SAASC,mBACdX,QAA2B,EAC3BY,OAAyG;IAEzG,OAAOZ,SAASL,GAAG,CAAC,CAACb,UAAa,CAAA;YAChCA;YACA+B,SAASjC,gBAAgBgC,QAAQE,KAAK,EAAEhC,WAAW;YACnDiC,eAAenC,gBAAgBgC,QAAQI,WAAW,EAAElC,WAAW;YAC/DmC,MAAMrC,gBAAgBgC,QAAQM,EAAE,EAAEpC,WAAW;YAC7CqC,QAAQvC,gBAAgBgC,QAAQQ,IAAI,EAAEtC,WAAW;QACnD,CAAA;AACF"}
|
|
@@ -27,6 +27,22 @@ import { type PageMarkdownNodes } from './page-markdown';
|
|
|
27
27
|
*
|
|
28
28
|
* The text is the page's Markdown representation, the one an agent is
|
|
29
29
|
* served, so what the listing summarizes is what a reader of the page gets.
|
|
30
|
+
*
|
|
31
|
+
* ## The page as it publishes (AGL-3501)
|
|
32
|
+
*
|
|
33
|
+
* Hand it the COMPOSED node map — reusable components grafted, repeats
|
|
34
|
+
* expanded (`seo-site-scan.ts` composes one) — and the page's own map as
|
|
35
|
+
* `pageNodes`. A heading a component renders is the page's heading; reading
|
|
36
|
+
* the raw map instead missed every one of them. The page's own map is what
|
|
37
|
+
* says where a fact can be fixed: an element a component renders points at
|
|
38
|
+
* the component's placement on the page and is not `editable` (a page fix
|
|
39
|
+
* cannot rewrite a component's text), and so is a copy a repeat makes, whose
|
|
40
|
+
* text is a row's.
|
|
41
|
+
*
|
|
42
|
+
* A `{{…}}` token is not copy. A token the composition did not resolve —
|
|
43
|
+
* a repeat whose rows were not read, a site variable the head resolves — is
|
|
44
|
+
* left out of the text and the headings, and a heading or a description
|
|
45
|
+
* made of one is neither judged by what it says nor offered to a fix.
|
|
30
46
|
* The walk starts where that serializer starts — the content region — and
|
|
31
47
|
* skips what it skips, through the same chrome predicate: a heading in the
|
|
32
48
|
* site's navigation is not the page's heading.
|
|
@@ -34,17 +50,37 @@ import { type PageMarkdownNodes } from './page-markdown';
|
|
|
34
50
|
/** How much of a page's text the facts carry. A listing summarizes; it does not need the page. */
|
|
35
51
|
export declare const SEO_PAGE_TEXT_MAX_CHARS = 2000;
|
|
36
52
|
export interface SeoHeading {
|
|
37
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The element on the page — for one a component renders, the component's
|
|
55
|
+
* placement — or `null` for a heading inside the page's rich text a fix
|
|
56
|
+
* cannot address.
|
|
57
|
+
*/
|
|
38
58
|
nodeId: string | null;
|
|
39
59
|
level: number;
|
|
60
|
+
/** What it says, without the tokens it resolves at render: `''` for a heading made of one. */
|
|
40
61
|
text: string;
|
|
41
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Whether a content fix can change it: a text component on the page itself
|
|
64
|
+
* with plain text and no token — not one a component or a repeat renders.
|
|
65
|
+
*/
|
|
42
66
|
editable: boolean;
|
|
67
|
+
/** Rendered by a reusable component placed on the page; `nodeId` is the placement. */
|
|
68
|
+
inComponent?: boolean;
|
|
43
69
|
}
|
|
44
70
|
export interface SeoImage {
|
|
71
|
+
/** The element on the page, or the component placement that renders it. */
|
|
45
72
|
nodeId: string;
|
|
46
73
|
src: string;
|
|
47
74
|
alt: string;
|
|
75
|
+
/** Whether a content fix can set its description: an image on the page itself, not a component's or a repeat's. */
|
|
76
|
+
editable: boolean;
|
|
77
|
+
/** Rendered by a reusable component placed on the page; `nodeId` is the placement. */
|
|
78
|
+
inComponent?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* The author marked it decorative (AGL-1305), so its empty description is
|
|
81
|
+
* the accessible answer rather than a gap.
|
|
82
|
+
*/
|
|
83
|
+
decorative: boolean;
|
|
48
84
|
/** The nearest heading or text before it, which is what the image illustrates. */
|
|
49
85
|
context: string;
|
|
50
86
|
}
|
|
@@ -56,11 +92,18 @@ export interface SeoPageFacts {
|
|
|
56
92
|
h1s: SeoHeading[];
|
|
57
93
|
images: SeoImage[];
|
|
58
94
|
imagesMissingAlt: SeoImage[];
|
|
59
|
-
/** The node the content region starts at; a new heading goes first inside it. */
|
|
95
|
+
/** The page's own node the content region starts at; a new heading goes first inside it. */
|
|
60
96
|
contentRootId: string | null;
|
|
61
97
|
}
|
|
62
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Read the facts a listing and the SEO check need from one page's node map.
|
|
100
|
+
*
|
|
101
|
+
* `nodes` is the page as it publishes, composed; `pageNodes` the page's own
|
|
102
|
+
* map it was composed from, which decides what a fact points at and whether
|
|
103
|
+
* a fix may change it. Without `pageNodes` the map is read as the page's own.
|
|
104
|
+
*/
|
|
63
105
|
export declare function seoPageFacts(nodes: PageMarkdownNodes | null | undefined, options?: {
|
|
64
106
|
rootId?: string;
|
|
65
107
|
maxChars?: number;
|
|
108
|
+
pageNodes?: PageMarkdownNodes | null;
|
|
66
109
|
}): SeoPageFacts;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
1
2
|
/**
|
|
2
3
|
* @license
|
|
3
4
|
* Copyright 2026 Aglyn LLC
|
|
@@ -26,6 +27,22 @@
|
|
|
26
27
|
*
|
|
27
28
|
* The text is the page's Markdown representation, the one an agent is
|
|
28
29
|
* served, so what the listing summarizes is what a reader of the page gets.
|
|
30
|
+
*
|
|
31
|
+
* ## The page as it publishes (AGL-3501)
|
|
32
|
+
*
|
|
33
|
+
* Hand it the COMPOSED node map — reusable components grafted, repeats
|
|
34
|
+
* expanded (`seo-site-scan.ts` composes one) — and the page's own map as
|
|
35
|
+
* `pageNodes`. A heading a component renders is the page's heading; reading
|
|
36
|
+
* the raw map instead missed every one of them. The page's own map is what
|
|
37
|
+
* says where a fact can be fixed: an element a component renders points at
|
|
38
|
+
* the component's placement on the page and is not `editable` (a page fix
|
|
39
|
+
* cannot rewrite a component's text), and so is a copy a repeat makes, whose
|
|
40
|
+
* text is a row's.
|
|
41
|
+
*
|
|
42
|
+
* A `{{…}}` token is not copy. A token the composition did not resolve —
|
|
43
|
+
* a repeat whose rows were not read, a site variable the head resolves — is
|
|
44
|
+
* left out of the text and the headings, and a heading or a description
|
|
45
|
+
* made of one is neither judged by what it says nor offered to a fix.
|
|
29
46
|
* The walk starts where that serializer starts — the content region — and
|
|
30
47
|
* skips what it skips, through the same chrome predicate: a heading in the
|
|
31
48
|
* site's navigation is not the page's heading.
|
|
@@ -37,7 +54,13 @@
|
|
|
37
54
|
'muiInlineText',
|
|
38
55
|
'muiListItemText'
|
|
39
56
|
]);
|
|
57
|
+
/** A reusable-component placement, as the page's own map stores it (`reusable-component-keys.ts`). */ const REUSABLE_INSTANCE = 'reusableInstance';
|
|
58
|
+
/** The prefix a component's grafted elements carry (`reusable-component-keys.ts`): `cmp__{placementId}__{id}`. */ const COMPONENT_GRAFT_PREFIX = 'cmp__';
|
|
59
|
+
/** The prefix a repeat's copies carry (`expand-repeatables.ts`): `rep__{repeatId}__{index}__{templateId}`. */ const REPEAT_COPY_PREFIX = 'rep__';
|
|
60
|
+
/** A `{{…}}` token: a value something else resolves, never copy of its own. */ const TOKEN_PATTERN = /\{\{[^{}]*\}\}/g;
|
|
40
61
|
const collapse = (value)=>value.replace(/\s+/g, ' ').trim();
|
|
62
|
+
const hasToken = (value)=>value.search(TOKEN_PATTERN) >= 0;
|
|
63
|
+
/** A value as copy: its unresolved tokens left out. */ const withoutTokens = (value)=>collapse(value.replace(TOKEN_PATTERN, ' '));
|
|
41
64
|
/** Author HTML to its text, for a heading stored as rich text. */ function htmlText(html) {
|
|
42
65
|
return collapse(html.replace(/<[^>]*>/g, ' ').replace(/ /g, ' ').replace(/&/g, '&'));
|
|
43
66
|
}
|
|
@@ -57,8 +80,67 @@ const collapse = (value)=>value.replace(/\s+/g, ' ').trim();
|
|
|
57
80
|
};
|
|
58
81
|
}
|
|
59
82
|
const childIdsOf = (node)=>Array.isArray(node == null ? void 0 : node.nodes) ? (node == null ? void 0 : node.nodes).filter((id)=>typeof id === 'string') : [];
|
|
60
|
-
/**
|
|
61
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Maps an element of the composed map to the page's own map: itself when the
|
|
85
|
+
* page holds it, its placement when a component renders it (the placement
|
|
86
|
+
* keeps its id as the component's root; what the component holds is grafted
|
|
87
|
+
* under the placement's prefix), and the template a repeat copied it from —
|
|
88
|
+
* with the copy's text a row's, so not editable.
|
|
89
|
+
*/ function originResolver(pageNodes) {
|
|
90
|
+
const placementIds = Object.keys(pageNodes).filter((id)=>{
|
|
91
|
+
var _pageNodes_id;
|
|
92
|
+
return ((_pageNodes_id = pageNodes[id]) == null ? void 0 : _pageNodes_id.componentId) === REUSABLE_INSTANCE;
|
|
93
|
+
});
|
|
94
|
+
const repeatIds = Object.keys(pageNodes).filter((id)=>{
|
|
95
|
+
var _pageNodes_id_props, _pageNodes_id;
|
|
96
|
+
const key = (_pageNodes_id = pageNodes[id]) == null ? void 0 : (_pageNodes_id_props = _pageNodes_id.props) == null ? void 0 : _pageNodes_id_props['repeatDataset'];
|
|
97
|
+
return typeof key === 'string' && key.trim() !== '';
|
|
98
|
+
});
|
|
99
|
+
const resolve = (id, depth)=>{
|
|
100
|
+
if (depth > MAX_DEPTH) return null;
|
|
101
|
+
const own = pageNodes[id];
|
|
102
|
+
if (own) {
|
|
103
|
+
return own.componentId === REUSABLE_INSTANCE ? {
|
|
104
|
+
nodeId: id,
|
|
105
|
+
editable: false,
|
|
106
|
+
inComponent: true
|
|
107
|
+
} : {
|
|
108
|
+
nodeId: id,
|
|
109
|
+
editable: true,
|
|
110
|
+
inComponent: false
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (id.startsWith(COMPONENT_GRAFT_PREFIX)) {
|
|
114
|
+
const placementId = placementIds.find((placement)=>id.startsWith(`${COMPONENT_GRAFT_PREFIX}${placement}__`));
|
|
115
|
+
return placementId ? {
|
|
116
|
+
nodeId: placementId,
|
|
117
|
+
editable: false,
|
|
118
|
+
inComponent: true
|
|
119
|
+
} : null;
|
|
120
|
+
}
|
|
121
|
+
if (!id.startsWith(REPEAT_COPY_PREFIX)) return null;
|
|
122
|
+
for (const repeatId of repeatIds){
|
|
123
|
+
var _exec;
|
|
124
|
+
const prefix = `${REPEAT_COPY_PREFIX}${repeatId}__`;
|
|
125
|
+
if (!id.startsWith(prefix)) continue;
|
|
126
|
+
const template = (_exec = /^\d+__(.+)$/.exec(id.slice(prefix.length))) == null ? void 0 : _exec[1];
|
|
127
|
+
const origin = template ? resolve(template, depth + 1) : null;
|
|
128
|
+
if (origin) return _extends({}, origin, {
|
|
129
|
+
editable: false
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
};
|
|
134
|
+
return (id)=>resolve(id, 0);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read the facts a listing and the SEO check need from one page's node map.
|
|
138
|
+
*
|
|
139
|
+
* `nodes` is the page as it publishes, composed; `pageNodes` the page's own
|
|
140
|
+
* map it was composed from, which decides what a fact points at and whether
|
|
141
|
+
* a fix may change it. Without `pageNodes` the map is read as the page's own.
|
|
142
|
+
*/ export function seoPageFacts(nodes, options = {}) {
|
|
143
|
+
var _options_maxChars, _options_pageNodes;
|
|
62
144
|
const empty = {
|
|
63
145
|
text: '',
|
|
64
146
|
wordCount: 0,
|
|
@@ -74,81 +156,104 @@ const childIdsOf = (node)=>Array.isArray(node == null ? void 0 : node.nodes) ? (
|
|
|
74
156
|
const markdown = buildPageMarkdown({
|
|
75
157
|
nodes,
|
|
76
158
|
rootId: options.rootId
|
|
77
|
-
});
|
|
159
|
+
}).replace(TOKEN_PATTERN, '').replace(/[ \t]{2,}/g, ' ');
|
|
78
160
|
const text = markdown.trim().slice(0, (_options_maxChars = options.maxChars) != null ? _options_maxChars : SEO_PAGE_TEXT_MAX_CHARS);
|
|
79
161
|
const headings = [];
|
|
80
162
|
const images = [];
|
|
163
|
+
/** Images whose description is a token: described at render, by a value this read cannot see. */ const boundAlt = new Set();
|
|
164
|
+
const originOf = originResolver((_options_pageNodes = options.pageNodes) != null ? _options_pageNodes : nodes);
|
|
81
165
|
let lastText = '';
|
|
82
166
|
const seen = new Set();
|
|
83
|
-
const visit = (id, depth)=>{
|
|
84
|
-
var _node_props, _node_componentId, _props_html;
|
|
167
|
+
const visit = (id, depth, owner)=>{
|
|
168
|
+
var _ref, _node_props, _node_componentId, _props_html;
|
|
85
169
|
if (depth > MAX_DEPTH || seen.has(id)) return;
|
|
86
170
|
const node = nodes[id];
|
|
87
171
|
if (!node) return;
|
|
88
172
|
seen.add(id);
|
|
89
173
|
if (isPageChromeNode(node)) return;
|
|
174
|
+
// Inside a placement everything is the component's; elsewhere the page's own map says.
|
|
175
|
+
const origin = (_ref = owner != null ? owner : originOf(id)) != null ? _ref : {
|
|
176
|
+
nodeId: id,
|
|
177
|
+
editable: false,
|
|
178
|
+
inComponent: false
|
|
179
|
+
};
|
|
180
|
+
const tag = origin.inComponent ? {
|
|
181
|
+
inComponent: true
|
|
182
|
+
} : {};
|
|
90
183
|
const props = (_node_props = node.props) != null ? _node_props : {};
|
|
91
184
|
const componentId = String((_node_componentId = node.componentId) != null ? _node_componentId : '');
|
|
92
185
|
if (TEXT_COMPONENTS.has(componentId)) {
|
|
93
|
-
const { text:
|
|
186
|
+
const { text: raw, plain } = textOf(props);
|
|
187
|
+
const own = withoutTokens(raw);
|
|
188
|
+
const bound = hasToken(raw);
|
|
94
189
|
const level = headingLevelOf(props);
|
|
95
|
-
if (own) {
|
|
96
|
-
if (level > 0)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
190
|
+
if (own || bound) {
|
|
191
|
+
if (level > 0) {
|
|
192
|
+
headings.push(_extends({
|
|
193
|
+
nodeId: origin.nodeId,
|
|
194
|
+
level,
|
|
195
|
+
text: own,
|
|
196
|
+
editable: plain && !bound && origin.editable
|
|
197
|
+
}, tag));
|
|
198
|
+
}
|
|
199
|
+
if (own) lastText = own;
|
|
103
200
|
}
|
|
104
201
|
} else if (componentId === 'markdown' && typeof props['content'] === 'string') {
|
|
105
202
|
for (const line of String(props['content']).split('\n')){
|
|
106
203
|
const match = /^(#{1,6})\s+(.+)$/.exec(line.trim());
|
|
107
204
|
if (match) {
|
|
108
|
-
headings.push({
|
|
109
|
-
nodeId: null,
|
|
205
|
+
headings.push(_extends({
|
|
206
|
+
nodeId: origin.inComponent ? origin.nodeId : null,
|
|
110
207
|
level: match[1].length,
|
|
111
|
-
text:
|
|
208
|
+
text: withoutTokens(match[2]),
|
|
112
209
|
editable: false
|
|
113
|
-
});
|
|
210
|
+
}, tag));
|
|
114
211
|
}
|
|
115
212
|
}
|
|
116
|
-
lastText =
|
|
213
|
+
lastText = withoutTokens(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS);
|
|
117
214
|
} else if (componentId === 'custom-html' && typeof ((_props_html = props['html']) != null ? _props_html : props['children']) === 'string') {
|
|
118
215
|
var _props_html1;
|
|
119
216
|
const html = String((_props_html1 = props['html']) != null ? _props_html1 : props['children']);
|
|
120
217
|
for (const match of html.matchAll(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi)){
|
|
121
|
-
headings.push({
|
|
122
|
-
nodeId: null,
|
|
218
|
+
headings.push(_extends({
|
|
219
|
+
nodeId: origin.inComponent ? origin.nodeId : null,
|
|
123
220
|
level: Number(match[1]),
|
|
124
|
-
text: htmlText(match[2]),
|
|
221
|
+
text: withoutTokens(htmlText(match[2])),
|
|
125
222
|
editable: false
|
|
126
|
-
});
|
|
223
|
+
}, tag));
|
|
127
224
|
}
|
|
128
225
|
} else if (componentId === 'image') {
|
|
129
226
|
var _props_src;
|
|
130
227
|
const src = String((_props_src = props['src']) != null ? _props_src : '').trim();
|
|
131
228
|
if (src) {
|
|
132
229
|
var _props_alt;
|
|
133
|
-
|
|
134
|
-
|
|
230
|
+
const rawAlt = String((_props_alt = props['alt']) != null ? _props_alt : '');
|
|
231
|
+
const image = _extends({
|
|
232
|
+
nodeId: origin.nodeId,
|
|
135
233
|
src,
|
|
136
|
-
alt:
|
|
234
|
+
alt: withoutTokens(rawAlt),
|
|
235
|
+
decorative: props['decorative'] === true,
|
|
236
|
+
editable: origin.editable && !hasToken(rawAlt),
|
|
137
237
|
context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS)
|
|
138
|
-
});
|
|
238
|
+
}, tag);
|
|
239
|
+
images.push(image);
|
|
240
|
+
if (hasToken(rawAlt)) boundAlt.add(image);
|
|
139
241
|
}
|
|
140
242
|
}
|
|
141
|
-
|
|
243
|
+
const inherited = owner != null ? owner : origin.inComponent ? origin : null;
|
|
244
|
+
for (const childId of childIdsOf(node))visit(childId, depth + 1, inherited);
|
|
142
245
|
};
|
|
143
|
-
visit(contentRootId, 0);
|
|
246
|
+
visit(contentRootId, 0, null);
|
|
144
247
|
return {
|
|
145
248
|
text,
|
|
146
249
|
wordCount: text ? text.split(/\s+/).filter(Boolean).length : 0,
|
|
147
250
|
headings,
|
|
148
251
|
h1s: headings.filter((heading)=>heading.level === 1),
|
|
149
252
|
images,
|
|
150
|
-
imagesMissingAlt: images.filter((image)=>!image.alt),
|
|
151
|
-
|
|
253
|
+
imagesMissingAlt: images.filter((image)=>!image.alt && !image.decorative && !boundAlt.has(image)),
|
|
254
|
+
// Where a fix adds a heading is the page's own content region, which a
|
|
255
|
+
// composed map can start inside a component.
|
|
256
|
+
contentRootId: options.pageNodes ? pageContentRootId(options.pageNodes, options.rootId) : contentRootId
|
|
152
257
|
};
|
|
153
258
|
}
|
|
154
259
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-page-facts.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\nimport {\n buildPageMarkdown,\n headingLevelOf,\n isPageChromeNode,\n pageContentRootId,\n type PageMarkdownNode,\n type PageMarkdownNodes,\n} from './page-markdown'\n\n/**\n * What a page SAYS, read off its node tree (AGL-2910): the text a search\n * listing summarizes, the headings, and the images — each heading and image\n * by node id, so a finding can name the element it is about and a fix can\n * address the element it changes.\n *\n * Pure, and the platform's: the SEO check (`seo-audit.ts`) judges a page by\n * these facts, and anything that proposes a listing or a content fix reads\n * the same ones rather than walking the tree its own way.\n *\n * The text is the page's Markdown representation, the one an agent is\n * served, so what the listing summarizes is what a reader of the page gets.\n * The walk starts where that serializer starts — the content region — and\n * skips what it skips, through the same chrome predicate: a heading in the\n * site's navigation is not the page's heading.\n */\n\n/** How much of a page's text the facts carry. A listing summarizes; it does not need the page. */\nexport const SEO_PAGE_TEXT_MAX_CHARS = 2_000\n\n/** How much text around an image describes it. */\nconst IMAGE_CONTEXT_MAX_CHARS = 160\n\n/** The deepest a walk goes, and it stops at a cycle — a node map is data. */\nconst MAX_DEPTH = 64\n\n/** The text components whose heading level `headingLevelOf` reads. */\nconst TEXT_COMPONENTS: ReadonlySet<string> = new Set(['muiTypography', 'muiInlineText', 'muiListItemText'])\n\nexport interface SeoHeading {\n /** The element, or `null` for a heading inside rich text a fix cannot address. */\n nodeId: string | null\n level: number\n text: string\n /** Whether a content fix can change it: a text component with plain text. */\n editable: boolean\n}\n\nexport interface SeoImage {\n nodeId: string\n src: string\n alt: string\n /** The nearest heading or text before it, which is what the image illustrates. */\n context: string\n}\n\nexport interface SeoPageFacts {\n /** The page's Markdown body, capped. */\n text: string\n wordCount: number\n headings: SeoHeading[]\n h1s: SeoHeading[]\n images: SeoImage[]\n imagesMissingAlt: SeoImage[]\n /** The node the content region starts at; a new heading goes first inside it. */\n contentRootId: string | null\n}\n\nconst collapse = (value: string): string => value.replace(/\\s+/g, ' ').trim()\n\n/** Author HTML to its text, for a heading stored as rich text. */\nfunction htmlText(html: string): string {\n return collapse(html.replace(/<[^>]*>/g, ' ').replace(/ /g, ' ').replace(/&/g, '&'))\n}\n\n/** A text component's own text, and whether it is plain (a fix can rewrite it). */\nfunction textOf(props: Record<string, unknown>): { text: string; plain: boolean } {\n const html = props['html']\n if (typeof html === 'string' && html.trim()) return { text: htmlText(html), plain: false }\n const children = props['children']\n return typeof children === 'string' ? { text: collapse(children), plain: true } : { text: '', plain: true }\n}\n\nconst childIdsOf = (node: PageMarkdownNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node?.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\n/** Read the facts a listing and the SEO check need from one page's node map. */\nexport function seoPageFacts(\n nodes: PageMarkdownNodes | null | undefined,\n options: { rootId?: string; maxChars?: number } = {},\n): SeoPageFacts {\n const empty: SeoPageFacts = {\n text: '',\n wordCount: 0,\n headings: [],\n h1s: [],\n images: [],\n imagesMissingAlt: [],\n contentRootId: null,\n }\n if (!nodes) return empty\n const contentRootId = pageContentRootId(nodes, options.rootId)\n if (!contentRootId) return empty\n\n const markdown = buildPageMarkdown({ nodes, rootId: options.rootId })\n const text = markdown.trim().slice(0, options.maxChars ?? SEO_PAGE_TEXT_MAX_CHARS)\n const headings: SeoHeading[] = []\n const images: SeoImage[] = []\n let lastText = ''\n\n const seen = new Set<string>()\n const visit = (id: string, depth: number): void => {\n if (depth > MAX_DEPTH || seen.has(id)) return\n const node = nodes[id]\n if (!node) return\n seen.add(id)\n if (isPageChromeNode(node)) return\n const props = (node.props ?? {}) as Record<string, unknown>\n const componentId = String(node.componentId ?? '')\n if (TEXT_COMPONENTS.has(componentId)) {\n const { text: own, plain } = textOf(props)\n const level = headingLevelOf(props)\n if (own) {\n if (level > 0) headings.push({ nodeId: id, level, text: own, editable: plain })\n lastText = own\n }\n } else if (componentId === 'markdown' && typeof props['content'] === 'string') {\n for (const line of String(props['content']).split('\\n')) {\n const match = /^(#{1,6})\\s+(.+)$/.exec(line.trim())\n if (match) {\n headings.push({ nodeId: null, level: match[1].length, text: collapse(match[2]), editable: false })\n }\n }\n lastText = collapse(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS)\n } else if (componentId === 'custom-html' && typeof (props['html'] ?? props['children']) === 'string') {\n const html = String(props['html'] ?? props['children'])\n for (const match of html.matchAll(/<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi)) {\n headings.push({ nodeId: null, level: Number(match[1]), text: htmlText(match[2]), editable: false })\n }\n } else if (componentId === 'image') {\n const src = String(props['src'] ?? '').trim()\n if (src) {\n images.push({\n nodeId: id,\n src,\n alt: collapse(String(props['alt'] ?? '')),\n context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS),\n })\n }\n }\n for (const childId of childIdsOf(node)) visit(childId, depth + 1)\n }\n visit(contentRootId, 0)\n\n return {\n text,\n wordCount: text ? text.split(/\\s+/).filter(Boolean).length : 0,\n headings,\n h1s: headings.filter((heading) => heading.level === 1),\n images,\n imagesMissingAlt: images.filter((image) => !image.alt),\n contentRootId,\n }\n}\n"],"names":["buildPageMarkdown","headingLevelOf","isPageChromeNode","pageContentRootId","SEO_PAGE_TEXT_MAX_CHARS","IMAGE_CONTEXT_MAX_CHARS","MAX_DEPTH","TEXT_COMPONENTS","Set","collapse","value","replace","trim","htmlText","html","textOf","props","text","plain","children","childIdsOf","node","Array","isArray","nodes","filter","id","seoPageFacts","options","empty","wordCount","headings","h1s","images","imagesMissingAlt","contentRootId","rootId","markdown","slice","maxChars","lastText","seen","visit","depth","has","add","componentId","String","own","level","push","nodeId","editable","line","split","match","exec","length","matchAll","Number","src","alt","context","childId","Boolean","heading","image"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-page-facts.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\nimport {\n buildPageMarkdown,\n headingLevelOf,\n isPageChromeNode,\n pageContentRootId,\n type PageMarkdownNode,\n type PageMarkdownNodes,\n} from './page-markdown'\n\n/**\n * What a page SAYS, read off its node tree (AGL-2910): the text a search\n * listing summarizes, the headings, and the images — each heading and image\n * by node id, so a finding can name the element it is about and a fix can\n * address the element it changes.\n *\n * Pure, and the platform's: the SEO check (`seo-audit.ts`) judges a page by\n * these facts, and anything that proposes a listing or a content fix reads\n * the same ones rather than walking the tree its own way.\n *\n * The text is the page's Markdown representation, the one an agent is\n * served, so what the listing summarizes is what a reader of the page gets.\n *\n * ## The page as it publishes (AGL-3501)\n *\n * Hand it the COMPOSED node map — reusable components grafted, repeats\n * expanded (`seo-site-scan.ts` composes one) — and the page's own map as\n * `pageNodes`. A heading a component renders is the page's heading; reading\n * the raw map instead missed every one of them. The page's own map is what\n * says where a fact can be fixed: an element a component renders points at\n * the component's placement on the page and is not `editable` (a page fix\n * cannot rewrite a component's text), and so is a copy a repeat makes, whose\n * text is a row's.\n *\n * A `{{…}}` token is not copy. A token the composition did not resolve —\n * a repeat whose rows were not read, a site variable the head resolves — is\n * left out of the text and the headings, and a heading or a description\n * made of one is neither judged by what it says nor offered to a fix.\n * The walk starts where that serializer starts — the content region — and\n * skips what it skips, through the same chrome predicate: a heading in the\n * site's navigation is not the page's heading.\n */\n\n/** How much of a page's text the facts carry. A listing summarizes; it does not need the page. */\nexport const SEO_PAGE_TEXT_MAX_CHARS = 2_000\n\n/** How much text around an image describes it. */\nconst IMAGE_CONTEXT_MAX_CHARS = 160\n\n/** The deepest a walk goes, and it stops at a cycle — a node map is data. */\nconst MAX_DEPTH = 64\n\n/** The text components whose heading level `headingLevelOf` reads. */\nconst TEXT_COMPONENTS: ReadonlySet<string> = new Set(['muiTypography', 'muiInlineText', 'muiListItemText'])\n\n/** A reusable-component placement, as the page's own map stores it (`reusable-component-keys.ts`). */\nconst REUSABLE_INSTANCE = 'reusableInstance'\n\n/** The prefix a component's grafted elements carry (`reusable-component-keys.ts`): `cmp__{placementId}__{id}`. */\nconst COMPONENT_GRAFT_PREFIX = 'cmp__'\n\n/** The prefix a repeat's copies carry (`expand-repeatables.ts`): `rep__{repeatId}__{index}__{templateId}`. */\nconst REPEAT_COPY_PREFIX = 'rep__'\n\n/** A `{{…}}` token: a value something else resolves, never copy of its own. */\nconst TOKEN_PATTERN = /\\{\\{[^{}]*\\}\\}/g\n\nexport interface SeoHeading {\n /**\n * The element on the page — for one a component renders, the component's\n * placement — or `null` for a heading inside the page's rich text a fix\n * cannot address.\n */\n nodeId: string | null\n level: number\n /** What it says, without the tokens it resolves at render: `''` for a heading made of one. */\n text: string\n /**\n * Whether a content fix can change it: a text component on the page itself\n * with plain text and no token — not one a component or a repeat renders.\n */\n editable: boolean\n /** Rendered by a reusable component placed on the page; `nodeId` is the placement. */\n inComponent?: boolean\n}\n\nexport interface SeoImage {\n /** The element on the page, or the component placement that renders it. */\n nodeId: string\n src: string\n alt: string\n /** Whether a content fix can set its description: an image on the page itself, not a component's or a repeat's. */\n editable: boolean\n /** Rendered by a reusable component placed on the page; `nodeId` is the placement. */\n inComponent?: boolean\n /**\n * The author marked it decorative (AGL-1305), so its empty description is\n * the accessible answer rather than a gap.\n */\n decorative: boolean\n /** The nearest heading or text before it, which is what the image illustrates. */\n context: string\n}\n\nexport interface SeoPageFacts {\n /** The page's Markdown body, capped. */\n text: string\n wordCount: number\n headings: SeoHeading[]\n h1s: SeoHeading[]\n images: SeoImage[]\n imagesMissingAlt: SeoImage[]\n /** The page's own node the content region starts at; a new heading goes first inside it. */\n contentRootId: string | null\n}\n\nconst collapse = (value: string): string => value.replace(/\\s+/g, ' ').trim()\n\nconst hasToken = (value: string): boolean => value.search(TOKEN_PATTERN) >= 0\n\n/** A value as copy: its unresolved tokens left out. */\nconst withoutTokens = (value: string): string => collapse(value.replace(TOKEN_PATTERN, ' '))\n\n/** Author HTML to its text, for a heading stored as rich text. */\nfunction htmlText(html: string): string {\n return collapse(html.replace(/<[^>]*>/g, ' ').replace(/ /g, ' ').replace(/&/g, '&'))\n}\n\n/** A text component's own text, and whether it is plain (a fix can rewrite it). */\nfunction textOf(props: Record<string, unknown>): { text: string; plain: boolean } {\n const html = props['html']\n if (typeof html === 'string' && html.trim()) return { text: htmlText(html), plain: false }\n const children = props['children']\n return typeof children === 'string' ? { text: collapse(children), plain: true } : { text: '', plain: true }\n}\n\nconst childIdsOf = (node: PageMarkdownNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node?.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\n/** Where a fact found on a composed page can be addressed on the page itself. */\ninterface FactOrigin {\n nodeId: string\n editable: boolean\n inComponent: boolean\n}\n\n/**\n * Maps an element of the composed map to the page's own map: itself when the\n * page holds it, its placement when a component renders it (the placement\n * keeps its id as the component's root; what the component holds is grafted\n * under the placement's prefix), and the template a repeat copied it from —\n * with the copy's text a row's, so not editable.\n */\nfunction originResolver(\n pageNodes: PageMarkdownNodes,\n): (id: string) => FactOrigin | null {\n const placementIds = Object.keys(pageNodes).filter((id) => pageNodes[id]?.componentId === REUSABLE_INSTANCE)\n const repeatIds = Object.keys(pageNodes).filter((id) => {\n const key = (pageNodes[id]?.props as Record<string, unknown> | null | undefined)?.['repeatDataset']\n return typeof key === 'string' && key.trim() !== ''\n })\n const resolve = (id: string, depth: number): FactOrigin | null => {\n if (depth > MAX_DEPTH) return null\n const own = pageNodes[id]\n if (own) {\n return own.componentId === REUSABLE_INSTANCE\n ? { nodeId: id, editable: false, inComponent: true }\n : { nodeId: id, editable: true, inComponent: false }\n }\n if (id.startsWith(COMPONENT_GRAFT_PREFIX)) {\n const placementId = placementIds.find((placement) => id.startsWith(`${COMPONENT_GRAFT_PREFIX}${placement}__`))\n return placementId ? { nodeId: placementId, editable: false, inComponent: true } : null\n }\n if (!id.startsWith(REPEAT_COPY_PREFIX)) return null\n for (const repeatId of repeatIds) {\n const prefix = `${REPEAT_COPY_PREFIX}${repeatId}__`\n if (!id.startsWith(prefix)) continue\n const template = /^\\d+__(.+)$/.exec(id.slice(prefix.length))?.[1]\n const origin = template ? resolve(template, depth + 1) : null\n if (origin) return { ...origin, editable: false }\n }\n return null\n }\n return (id) => resolve(id, 0)\n}\n\n/**\n * Read the facts a listing and the SEO check need from one page's node map.\n *\n * `nodes` is the page as it publishes, composed; `pageNodes` the page's own\n * map it was composed from, which decides what a fact points at and whether\n * a fix may change it. Without `pageNodes` the map is read as the page's own.\n */\nexport function seoPageFacts(\n nodes: PageMarkdownNodes | null | undefined,\n options: { rootId?: string; maxChars?: number; pageNodes?: PageMarkdownNodes | null } = {},\n): SeoPageFacts {\n const empty: SeoPageFacts = {\n text: '',\n wordCount: 0,\n headings: [],\n h1s: [],\n images: [],\n imagesMissingAlt: [],\n contentRootId: null,\n }\n if (!nodes) return empty\n const contentRootId = pageContentRootId(nodes, options.rootId)\n if (!contentRootId) return empty\n\n const markdown = buildPageMarkdown({ nodes, rootId: options.rootId })\n .replace(TOKEN_PATTERN, '')\n .replace(/[ \\t]{2,}/g, ' ')\n const text = markdown.trim().slice(0, options.maxChars ?? SEO_PAGE_TEXT_MAX_CHARS)\n const headings: SeoHeading[] = []\n const images: SeoImage[] = []\n /** Images whose description is a token: described at render, by a value this read cannot see. */\n const boundAlt = new Set<SeoImage>()\n const originOf = originResolver(options.pageNodes ?? nodes)\n let lastText = ''\n\n const seen = new Set<string>()\n const visit = (id: string, depth: number, owner: FactOrigin | null): void => {\n if (depth > MAX_DEPTH || seen.has(id)) return\n const node = nodes[id]\n if (!node) return\n seen.add(id)\n if (isPageChromeNode(node)) return\n // Inside a placement everything is the component's; elsewhere the page's own map says.\n const origin: FactOrigin = owner ?? originOf(id) ?? { nodeId: id, editable: false, inComponent: false }\n const tag = origin.inComponent ? { inComponent: true as const } : {}\n const props = (node.props ?? {}) as Record<string, unknown>\n const componentId = String(node.componentId ?? '')\n if (TEXT_COMPONENTS.has(componentId)) {\n const { text: raw, plain } = textOf(props)\n const own = withoutTokens(raw)\n const bound = hasToken(raw)\n const level = headingLevelOf(props)\n if (own || bound) {\n if (level > 0) {\n headings.push({ nodeId: origin.nodeId, level, text: own, editable: plain && !bound && origin.editable, ...tag })\n }\n if (own) lastText = own\n }\n } else if (componentId === 'markdown' && typeof props['content'] === 'string') {\n for (const line of String(props['content']).split('\\n')) {\n const match = /^(#{1,6})\\s+(.+)$/.exec(line.trim())\n if (match) {\n headings.push({\n nodeId: origin.inComponent ? origin.nodeId : null,\n level: match[1].length,\n text: withoutTokens(match[2]),\n editable: false,\n ...tag,\n })\n }\n }\n lastText = withoutTokens(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS)\n } else if (componentId === 'custom-html' && typeof (props['html'] ?? props['children']) === 'string') {\n const html = String(props['html'] ?? props['children'])\n for (const match of html.matchAll(/<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi)) {\n headings.push({\n nodeId: origin.inComponent ? origin.nodeId : null,\n level: Number(match[1]),\n text: withoutTokens(htmlText(match[2])),\n editable: false,\n ...tag,\n })\n }\n } else if (componentId === 'image') {\n const src = String(props['src'] ?? '').trim()\n if (src) {\n const rawAlt = String(props['alt'] ?? '')\n const image: SeoImage = {\n nodeId: origin.nodeId,\n src,\n alt: withoutTokens(rawAlt),\n decorative: props['decorative'] === true,\n editable: origin.editable && !hasToken(rawAlt),\n context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS),\n ...tag,\n }\n images.push(image)\n if (hasToken(rawAlt)) boundAlt.add(image)\n }\n }\n const inherited = owner ?? (origin.inComponent ? origin : null)\n for (const childId of childIdsOf(node)) visit(childId, depth + 1, inherited)\n }\n visit(contentRootId, 0, null)\n\n return {\n text,\n wordCount: text ? text.split(/\\s+/).filter(Boolean).length : 0,\n headings,\n h1s: headings.filter((heading) => heading.level === 1),\n images,\n imagesMissingAlt: images.filter((image) => !image.alt && !image.decorative && !boundAlt.has(image)),\n // Where a fix adds a heading is the page's own content region, which a\n // composed map can start inside a component.\n contentRootId: options.pageNodes ? pageContentRootId(options.pageNodes, options.rootId) : contentRootId,\n }\n}\n"],"names":["buildPageMarkdown","headingLevelOf","isPageChromeNode","pageContentRootId","SEO_PAGE_TEXT_MAX_CHARS","IMAGE_CONTEXT_MAX_CHARS","MAX_DEPTH","TEXT_COMPONENTS","Set","REUSABLE_INSTANCE","COMPONENT_GRAFT_PREFIX","REPEAT_COPY_PREFIX","TOKEN_PATTERN","collapse","value","replace","trim","hasToken","search","withoutTokens","htmlText","html","textOf","props","text","plain","children","childIdsOf","node","Array","isArray","nodes","filter","id","originResolver","pageNodes","placementIds","Object","keys","componentId","repeatIds","key","resolve","depth","own","nodeId","editable","inComponent","startsWith","placementId","find","placement","repeatId","prefix","template","exec","slice","length","origin","seoPageFacts","options","empty","wordCount","headings","h1s","images","imagesMissingAlt","contentRootId","rootId","markdown","maxChars","boundAlt","originOf","lastText","seen","visit","owner","has","add","tag","String","raw","bound","level","push","line","split","match","matchAll","Number","src","rawAlt","image","alt","decorative","context","inherited","childId","Boolean","heading"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,iBAAiB,EACjBC,cAAc,EACdC,gBAAgB,EAChBC,iBAAiB,QAGZ,qBAAiB;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BC,GAED,gGAAgG,GAChG,OAAO,MAAMC,0BAA0B,KAAK;AAE5C,gDAAgD,GAChD,MAAMC,0BAA0B;AAEhC,2EAA2E,GAC3E,MAAMC,YAAY;AAElB,oEAAoE,GACpE,MAAMC,kBAAuC,IAAIC,IAAI;IAAC;IAAiB;IAAiB;CAAkB;AAE1G,oGAAoG,GACpG,MAAMC,oBAAoB;AAE1B,gHAAgH,GAChH,MAAMC,yBAAyB;AAE/B,4GAA4G,GAC5G,MAAMC,qBAAqB;AAE3B,6EAA6E,GAC7E,MAAMC,gBAAgB;AAmDtB,MAAMC,WAAW,CAACC,QAA0BA,MAAMC,OAAO,CAAC,QAAQ,KAAKC,IAAI;AAE3E,MAAMC,WAAW,CAACH,QAA2BA,MAAMI,MAAM,CAACN,kBAAkB;AAE5E,qDAAqD,GACrD,MAAMO,gBAAgB,CAACL,QAA0BD,SAASC,MAAMC,OAAO,CAACH,eAAe;AAEvF,gEAAgE,GAChE,SAASQ,SAASC,IAAY;IAC5B,OAAOR,SAASQ,KAAKN,OAAO,CAAC,YAAY,KAAKA,OAAO,CAAC,WAAW,KAAKA,OAAO,CAAC,UAAU;AAC1F;AAEA,iFAAiF,GACjF,SAASO,OAAOC,KAA8B;IAC5C,MAAMF,OAAOE,KAAK,CAAC,OAAO;IAC1B,IAAI,OAAOF,SAAS,YAAYA,KAAKL,IAAI,IAAI,OAAO;QAAEQ,MAAMJ,SAASC;QAAOI,OAAO;IAAM;IACzF,MAAMC,WAAWH,KAAK,CAAC,WAAW;IAClC,OAAO,OAAOG,aAAa,WAAW;QAAEF,MAAMX,SAASa;QAAWD,OAAO;IAAK,IAAI;QAAED,MAAM;QAAIC,OAAO;IAAK;AAC5G;AAEA,MAAME,aAAa,CAACC,OAClBC,MAAMC,OAAO,CAACF,wBAAAA,KAAMG,KAAK,IAAI,CAACH,wBAAAA,KAAMG,KAAK,EAAeC,MAAM,CAAC,CAACC,KAAqB,OAAOA,OAAO,YAAY,EAAE;AASnH;;;;;;CAMC,GACD,SAASC,eACPC,SAA4B;IAE5B,MAAMC,eAAeC,OAAOC,IAAI,CAACH,WAAWH,MAAM,CAAC,CAACC;YAAOE;eAAAA,EAAAA,gBAAAA,SAAS,CAACF,GAAG,qBAAbE,cAAeI,WAAW,MAAK9B;;IAC1F,MAAM+B,YAAYH,OAAOC,IAAI,CAACH,WAAWH,MAAM,CAAC,CAACC;YAClCE,qBAAAA;QAAb,MAAMM,OAAON,gBAAAA,SAAS,CAACF,GAAG,sBAAbE,sBAAAA,cAAeZ,KAAK,qBAArB,AAACY,mBAAqE,CAAC,gBAAgB;QACnG,OAAO,OAAOM,QAAQ,YAAYA,IAAIzB,IAAI,OAAO;IACnD;IACA,MAAM0B,UAAU,CAACT,IAAYU;QAC3B,IAAIA,QAAQrC,WAAW,OAAO;QAC9B,MAAMsC,MAAMT,SAAS,CAACF,GAAG;QACzB,IAAIW,KAAK;YACP,OAAOA,IAAIL,WAAW,KAAK9B,oBACvB;gBAAEoC,QAAQZ;gBAAIa,UAAU;gBAAOC,aAAa;YAAK,IACjD;gBAAEF,QAAQZ;gBAAIa,UAAU;gBAAMC,aAAa;YAAM;QACvD;QACA,IAAId,GAAGe,UAAU,CAACtC,yBAAyB;YACzC,MAAMuC,cAAcb,aAAac,IAAI,CAAC,CAACC,YAAclB,GAAGe,UAAU,CAAC,GAAGtC,yBAAyByC,UAAU,EAAE,CAAC;YAC5G,OAAOF,cAAc;gBAAEJ,QAAQI;gBAAaH,UAAU;gBAAOC,aAAa;YAAK,IAAI;QACrF;QACA,IAAI,CAACd,GAAGe,UAAU,CAACrC,qBAAqB,OAAO;QAC/C,KAAK,MAAMyC,YAAYZ,UAAW;gBAGf;YAFjB,MAAMa,SAAS,GAAG1C,qBAAqByC,SAAS,EAAE,CAAC;YACnD,IAAI,CAACnB,GAAGe,UAAU,CAACK,SAAS;YAC5B,MAAMC,YAAW,QAAA,cAAcC,IAAI,CAACtB,GAAGuB,KAAK,CAACH,OAAOI,MAAM,uBAAzC,KAA6C,CAAC,EAAE;YACjE,MAAMC,SAASJ,WAAWZ,QAAQY,UAAUX,QAAQ,KAAK;YACzD,IAAIe,QAAQ,OAAO,aAAKA;gBAAQZ,UAAU;;QAC5C;QACA,OAAO;IACT;IACA,OAAO,CAACb,KAAOS,QAAQT,IAAI;AAC7B;AAEA;;;;;;CAMC,GACD,OAAO,SAAS0B,aACd5B,KAA2C,EAC3C6B,UAAwF,CAAC,CAAC;QAkBpDA,mBAKNA;IArBhC,MAAMC,QAAsB;QAC1BrC,MAAM;QACNsC,WAAW;QACXC,UAAU,EAAE;QACZC,KAAK,EAAE;QACPC,QAAQ,EAAE;QACVC,kBAAkB,EAAE;QACpBC,eAAe;IACjB;IACA,IAAI,CAACpC,OAAO,OAAO8B;IACnB,MAAMM,gBAAgBhE,kBAAkB4B,OAAO6B,QAAQQ,MAAM;IAC7D,IAAI,CAACD,eAAe,OAAON;IAE3B,MAAMQ,WAAWrE,kBAAkB;QAAE+B;QAAOqC,QAAQR,QAAQQ,MAAM;IAAC,GAChErD,OAAO,CAACH,eAAe,IACvBG,OAAO,CAAC,cAAc;IACzB,MAAMS,OAAO6C,SAASrD,IAAI,GAAGwC,KAAK,CAAC,IAAGI,oBAAAA,QAAQU,QAAQ,YAAhBV,oBAAoBxD;IAC1D,MAAM2D,WAAyB,EAAE;IACjC,MAAME,SAAqB,EAAE;IAC7B,+FAA+F,GAC/F,MAAMM,WAAW,IAAI/D;IACrB,MAAMgE,WAAWtC,gBAAe0B,qBAAAA,QAAQzB,SAAS,YAAjByB,qBAAqB7B;IACrD,IAAI0C,WAAW;IAEf,MAAMC,OAAO,IAAIlE;IACjB,MAAMmE,QAAQ,CAAC1C,IAAYU,OAAeiC;YAObA,MAEZhD,aACYA,mBA0ByBL;QAnCpD,IAAIoB,QAAQrC,aAAaoE,KAAKG,GAAG,CAAC5C,KAAK;QACvC,MAAML,OAAOG,KAAK,CAACE,GAAG;QACtB,IAAI,CAACL,MAAM;QACX8C,KAAKI,GAAG,CAAC7C;QACT,IAAI/B,iBAAiB0B,OAAO;QAC5B,uFAAuF;QACvF,MAAM8B,UAAqBkB,OAAAA,gBAAAA,QAASJ,SAASvC,eAAlB2C,OAAyB;YAAE/B,QAAQZ;YAAIa,UAAU;YAAOC,aAAa;QAAM;QACtG,MAAMgC,MAAMrB,OAAOX,WAAW,GAAG;YAAEA,aAAa;QAAc,IAAI,CAAC;QACnE,MAAMxB,SAASK,cAAAA,KAAKL,KAAK,YAAVK,cAAc,CAAC;QAC9B,MAAMW,cAAcyC,QAAOpD,oBAAAA,KAAKW,WAAW,YAAhBX,oBAAoB;QAC/C,IAAIrB,gBAAgBsE,GAAG,CAACtC,cAAc;YACpC,MAAM,EAAEf,MAAMyD,GAAG,EAAExD,KAAK,EAAE,GAAGH,OAAOC;YACpC,MAAMqB,MAAMzB,cAAc8D;YAC1B,MAAMC,QAAQjE,SAASgE;YACvB,MAAME,QAAQlF,eAAesB;YAC7B,IAAIqB,OAAOsC,OAAO;gBAChB,IAAIC,QAAQ,GAAG;oBACbpB,SAASqB,IAAI,CAAC;wBAAEvC,QAAQa,OAAOb,MAAM;wBAAEsC;wBAAO3D,MAAMoB;wBAAKE,UAAUrB,SAAS,CAACyD,SAASxB,OAAOZ,QAAQ;uBAAKiC;gBAC5G;gBACA,IAAInC,KAAK6B,WAAW7B;YACtB;QACF,OAAO,IAAIL,gBAAgB,cAAc,OAAOhB,KAAK,CAAC,UAAU,KAAK,UAAU;YAC7E,KAAK,MAAM8D,QAAQL,OAAOzD,KAAK,CAAC,UAAU,EAAE+D,KAAK,CAAC,MAAO;gBACvD,MAAMC,QAAQ,oBAAoBhC,IAAI,CAAC8B,KAAKrE,IAAI;gBAChD,IAAIuE,OAAO;oBACTxB,SAASqB,IAAI,CAAC;wBACZvC,QAAQa,OAAOX,WAAW,GAAGW,OAAOb,MAAM,GAAG;wBAC7CsC,OAAOI,KAAK,CAAC,EAAE,CAAC9B,MAAM;wBACtBjC,MAAML,cAAcoE,KAAK,CAAC,EAAE;wBAC5BzC,UAAU;uBACPiC;gBAEP;YACF;YACAN,WAAWtD,cAAc6D,OAAOzD,KAAK,CAAC,UAAU,GAAGiC,KAAK,CAAC,GAAGnD;QAC9D,OAAO,IAAIkC,gBAAgB,iBAAiB,SAAQhB,cAAAA,KAAK,CAAC,OAAO,YAAbA,cAAiBA,KAAK,CAAC,WAAW,MAAM,UAAU;gBAChFA;YAApB,MAAMF,OAAO2D,QAAOzD,eAAAA,KAAK,CAAC,OAAO,YAAbA,eAAiBA,KAAK,CAAC,WAAW;YACtD,KAAK,MAAMgE,SAASlE,KAAKmE,QAAQ,CAAC,sCAAuC;gBACvEzB,SAASqB,IAAI,CAAC;oBACZvC,QAAQa,OAAOX,WAAW,GAAGW,OAAOb,MAAM,GAAG;oBAC7CsC,OAAOM,OAAOF,KAAK,CAAC,EAAE;oBACtB/D,MAAML,cAAcC,SAASmE,KAAK,CAAC,EAAE;oBACrCzC,UAAU;mBACPiC;YAEP;QACF,OAAO,IAAIxC,gBAAgB,SAAS;gBACfhB;YAAnB,MAAMmE,MAAMV,QAAOzD,aAAAA,KAAK,CAAC,MAAM,YAAZA,aAAgB,IAAIP,IAAI;YAC3C,IAAI0E,KAAK;oBACenE;gBAAtB,MAAMoE,SAASX,QAAOzD,aAAAA,KAAK,CAAC,MAAM,YAAZA,aAAgB;gBACtC,MAAMqE,QAAkB;oBACtB/C,QAAQa,OAAOb,MAAM;oBACrB6C;oBACAG,KAAK1E,cAAcwE;oBACnBG,YAAYvE,KAAK,CAAC,aAAa,KAAK;oBACpCuB,UAAUY,OAAOZ,QAAQ,IAAI,CAAC7B,SAAS0E;oBACvCI,SAAStB,SAASjB,KAAK,CAAC,GAAGnD;mBACxB0E;gBAELd,OAAOmB,IAAI,CAACQ;gBACZ,IAAI3E,SAAS0E,SAASpB,SAASO,GAAG,CAACc;YACrC;QACF;QACA,MAAMI,YAAYpB,gBAAAA,QAAUlB,OAAOX,WAAW,GAAGW,SAAS;QAC1D,KAAK,MAAMuC,WAAWtE,WAAWC,MAAO+C,MAAMsB,SAAStD,QAAQ,GAAGqD;IACpE;IACArB,MAAMR,eAAe,GAAG;IAExB,OAAO;QACL3C;QACAsC,WAAWtC,OAAOA,KAAK8D,KAAK,CAAC,OAAOtD,MAAM,CAACkE,SAASzC,MAAM,GAAG;QAC7DM;QACAC,KAAKD,SAAS/B,MAAM,CAAC,CAACmE,UAAYA,QAAQhB,KAAK,KAAK;QACpDlB;QACAC,kBAAkBD,OAAOjC,MAAM,CAAC,CAAC4D,QAAU,CAACA,MAAMC,GAAG,IAAI,CAACD,MAAME,UAAU,IAAI,CAACvB,SAASM,GAAG,CAACe;QAC5F,uEAAuE;QACvE,6CAA6C;QAC7CzB,eAAeP,QAAQzB,SAAS,GAAGhC,kBAAkByD,QAAQzB,SAAS,EAAEyB,QAAQQ,MAAM,IAAID;IAC5F;AACF"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import { type SeoAuditPage, type SeoAuditSite } from './seo-audit';
|
|
18
|
+
import { type RepeatableDataset } from './expand-repeatables';
|
|
18
19
|
/**
|
|
19
20
|
* Read a site for the SEO check (`seo-audit.ts`) the way its sitemap lists
|
|
20
21
|
* it: every screen the routing map publishes, less the template screens, the
|
|
@@ -29,9 +30,16 @@ import { type SeoAuditPage, type SeoAuditSite } from './seo-audit';
|
|
|
29
30
|
* template screen ids, which the tenant runtime resolves; so this module stays
|
|
30
31
|
* pure enough to import from anywhere, and a spec hands it a fake.
|
|
31
32
|
*
|
|
33
|
+
* Each page is checked as it publishes (AGL-3501): {@link composeSeoPages}
|
|
34
|
+
* grafts the reusable components it places, with each placement's values,
|
|
35
|
+
* and expands its repeats over their rows when the caller hands over the
|
|
36
|
+
* repeat-rows reader — the composition the tenant runs before it renders.
|
|
37
|
+
*
|
|
32
38
|
* Reads: one projected query of the screens, one of the layouts, and one
|
|
33
39
|
* version document per checked page and per layout — at most
|
|
34
|
-
* {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2
|
|
40
|
+
* {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2 — then one
|
|
41
|
+
* document per distinct component those place, and one repeat-rows request
|
|
42
|
+
* for every key they repeat over.
|
|
35
43
|
*/
|
|
36
44
|
/** Shared layouts read for the links they carry. */
|
|
37
45
|
export declare const SEO_SCAN_LAYOUT_LIMIT = 20;
|
|
@@ -100,7 +108,7 @@ interface SeoScreenDocument {
|
|
|
100
108
|
export interface SeoScannedPage extends SeoAuditPage {
|
|
101
109
|
/** The listing as the screen document stores it: a title's variables unresolved. */
|
|
102
110
|
storedSeo: NonNullable<SeoScreenDocument['seo']>;
|
|
103
|
-
/** The page's node map as
|
|
111
|
+
/** The page's own node map, as its published version stores it — not composed. */
|
|
104
112
|
nodes: Record<string, unknown> | null;
|
|
105
113
|
}
|
|
106
114
|
export interface SeoSiteScan {
|
|
@@ -110,6 +118,34 @@ export interface SeoSiteScan {
|
|
|
110
118
|
/** Customer-safe lines: a keyword line naming a page the site does not publish. */
|
|
111
119
|
notes: string[];
|
|
112
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The rows a site's repeats render, by key — the repeat-rows contract
|
|
123
|
+
* (`plugin-manager/repeat-rows`), which is server-only and so handed in
|
|
124
|
+
* rather than imported here.
|
|
125
|
+
*/
|
|
126
|
+
export type SeoRepeatRowsReader = (keys: readonly string[]) => Promise<Record<string, RepeatableDataset>>;
|
|
127
|
+
export interface SeoCompositionOptions {
|
|
128
|
+
/** Without it a repeat is read once, as written, its `{{item.*}}` tokens left out of the facts. */
|
|
129
|
+
readRepeatRows?: SeoRepeatRowsReader;
|
|
130
|
+
}
|
|
131
|
+
export interface SeoComposedPages {
|
|
132
|
+
/** Each node map as it publishes, in the order given; `null` stays `null`. */
|
|
133
|
+
composed: (Record<string, unknown> | null)[];
|
|
134
|
+
/** The repeats' rows were asked for and could not be read, so each repeat was read once, as written. */
|
|
135
|
+
rowsUnread: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Node maps of `hostId` as they publish (AGL-3501): each reusable component
|
|
139
|
+
* placed grafted from its published definition with the placement's values
|
|
140
|
+
* (`load-referenced-components.ts`, the graft the tenant runs), then each
|
|
141
|
+
* repeat expanded over its rows when `readRepeatRows` is given — one request
|
|
142
|
+
* for every key the maps repeat over.
|
|
143
|
+
*
|
|
144
|
+
* A component that does not load leaves its placement as authored, as on the
|
|
145
|
+
* page; rows that cannot be read leave each repeat once, as written, and say
|
|
146
|
+
* so through `rowsUnread` rather than failing the check.
|
|
147
|
+
*/
|
|
148
|
+
export declare function composeSeoPages(store: SeoScanStore, hostId: string, maps: readonly (Record<string, unknown> | null | undefined)[], options?: SeoCompositionOptions): Promise<SeoComposedPages>;
|
|
113
149
|
/** The site as the check reads it, from its host document. */
|
|
114
150
|
export declare function seoAuditSiteOf(host: SeoSiteHost): SeoAuditSite;
|
|
115
151
|
/**
|
|
@@ -117,10 +153,12 @@ export declare function seoAuditSiteOf(host: SeoSiteHost): SeoAuditSite;
|
|
|
117
153
|
*
|
|
118
154
|
* `keywords` are the target keyword lines a person typed (`/path: a, b`);
|
|
119
155
|
* `templateScreenIds` are the screens a collection, the store or the host
|
|
120
|
-
* renders other URLs through, which are not pages of their own
|
|
156
|
+
* renders other URLs through, which are not pages of their own;
|
|
157
|
+
* `readRepeatRows` reads the rows the pages repeat over
|
|
158
|
+
* ({@link composeSeoPages}).
|
|
121
159
|
*/
|
|
122
160
|
export declare function scanSeoSite(store: SeoScanStore, hostId: string, host: SeoSiteHost, options?: {
|
|
123
161
|
keywords?: unknown;
|
|
124
162
|
templateScreenIds?: Iterable<string>;
|
|
125
|
-
}): Promise<SeoSiteScan>;
|
|
163
|
+
} & SeoCompositionOptions): Promise<SeoSiteScan>;
|
|
126
164
|
export {};
|