@dxos/plugin-magazine 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +105 -0
- package/PLUGIN.mdl +653 -0
- package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs +90 -0
- package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs.map +7 -0
- package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs +97 -0
- package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs.map +7 -0
- package/dist/lib/neutral/MagazineArticle-DEUTR23F.mjs +325 -0
- package/dist/lib/neutral/MagazineArticle-DEUTR23F.mjs.map +7 -0
- package/dist/lib/neutral/MagazinePlugin.mjs +62 -0
- package/dist/lib/neutral/MagazinePlugin.mjs.map +7 -0
- package/dist/lib/neutral/MagazinePlugin.workerd.mjs +12 -0
- package/dist/lib/neutral/MagazinePlugin.workerd.mjs.map +7 -0
- package/dist/lib/neutral/PostArticle-RERHXPXC.mjs +230 -0
- package/dist/lib/neutral/PostArticle-RERHXPXC.mjs.map +7 -0
- package/dist/lib/neutral/PostCard-VDGN7BBA.mjs +60 -0
- package/dist/lib/neutral/PostCard-VDGN7BBA.mjs.map +7 -0
- package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs +121 -0
- package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +21 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-3K4AQKBZ.mjs +12 -0
- package/dist/lib/neutral/chunk-3K4AQKBZ.mjs.map +7 -0
- package/dist/lib/neutral/chunk-AFPBWBTK.mjs +591 -0
- package/dist/lib/neutral/chunk-AFPBWBTK.mjs.map +7 -0
- package/dist/lib/neutral/chunk-APQRTYT3.mjs +57 -0
- package/dist/lib/neutral/chunk-APQRTYT3.mjs.map +7 -0
- package/dist/lib/neutral/chunk-GU725ZF4.mjs +11 -0
- package/dist/lib/neutral/chunk-GU725ZF4.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-JFPR6OV4.mjs +60 -0
- package/dist/lib/neutral/chunk-JFPR6OV4.mjs.map +7 -0
- package/dist/lib/neutral/chunk-KTYLUOUG.mjs +36 -0
- package/dist/lib/neutral/chunk-KTYLUOUG.mjs.map +7 -0
- package/dist/lib/neutral/chunk-MBPX7RPI.mjs +14 -0
- package/dist/lib/neutral/chunk-MBPX7RPI.mjs.map +7 -0
- package/dist/lib/neutral/chunk-MJ2AY2CK.mjs +8 -0
- package/dist/lib/neutral/chunk-MJ2AY2CK.mjs.map +7 -0
- package/dist/lib/neutral/chunk-OYCDRSWB.mjs +624 -0
- package/dist/lib/neutral/chunk-OYCDRSWB.mjs.map +7 -0
- package/dist/lib/neutral/chunk-SAWKLGJU.mjs +28 -0
- package/dist/lib/neutral/chunk-SAWKLGJU.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XCNCK47N.mjs +15 -0
- package/dist/lib/neutral/chunk-XCNCK47N.mjs.map +7 -0
- package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs +59 -0
- package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +338 -0
- package/dist/lib/neutral/components/index.mjs.map +7 -0
- package/dist/lib/neutral/containers/index.mjs +17 -0
- package/dist/lib/neutral/containers/index.mjs.map +7 -0
- package/dist/lib/neutral/create-object-SYZMRR4H.mjs +98 -0
- package/dist/lib/neutral/create-object-SYZMRR4H.mjs.map +7 -0
- package/dist/lib/neutral/curate-magazine-MA6OLK2X.mjs +184 -0
- package/dist/lib/neutral/curate-magazine-MA6OLK2X.mjs.map +7 -0
- package/dist/lib/neutral/fetch-article-content-CZO6D3QD.mjs +31 -0
- package/dist/lib/neutral/fetch-article-content-CZO6D3QD.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +18 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/load-post-content-XGL6VFF6.mjs +65 -0
- package/dist/lib/neutral/load-post-content-XGL6VFF6.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs +18 -0
- package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs.map +7 -0
- package/dist/lib/neutral/operation-handler-JE3743BU.mjs +8 -0
- package/dist/lib/neutral/operation-handler-JE3743BU.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +8 -0
- package/dist/lib/neutral/operations/index.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +16 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/plugin.workerd.mjs +16 -0
- package/dist/lib/neutral/plugin.workerd.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-TZESAMG2.mjs +60 -0
- package/dist/lib/neutral/react-surface-TZESAMG2.mjs.map +7 -0
- package/dist/lib/neutral/routine-templates-A2XTJOZO.mjs +52 -0
- package/dist/lib/neutral/routine-templates-A2XTJOZO.mjs.map +7 -0
- package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs +8 -0
- package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs.map +7 -0
- package/dist/lib/neutral/skills/index.mjs +46 -0
- package/dist/lib/neutral/skills/index.mjs.map +7 -0
- package/dist/lib/neutral/sync-feed-UZX2XHSQ.mjs +99 -0
- package/dist/lib/neutral/sync-feed-UZX2XHSQ.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +81 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +14 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/MagazinePlugin.d.ts +4 -0
- package/dist/types/src/MagazinePlugin.d.ts.map +1 -0
- package/dist/types/src/MagazinePlugin.test.d.ts +2 -0
- package/dist/types/src/MagazinePlugin.test.d.ts.map +1 -0
- package/dist/types/src/MagazinePlugin.workerd.d.ts +4 -0
- package/dist/types/src/MagazinePlugin.workerd.d.ts.map +1 -0
- package/dist/types/src/atoms/atoms.test.d.ts +2 -0
- package/dist/types/src/atoms/atoms.test.d.ts.map +1 -0
- package/dist/types/src/atoms/index.d.ts +7 -0
- package/dist/types/src/atoms/index.d.ts.map +1 -0
- package/dist/types/src/atoms/magazine-posts.d.ts +16 -0
- package/dist/types/src/atoms/magazine-posts.d.ts.map +1 -0
- package/dist/types/src/atoms/post-content.d.ts +7 -0
- package/dist/types/src/atoms/post-content.d.ts.map +1 -0
- package/dist/types/src/atoms/post-curation.d.ts +16 -0
- package/dist/types/src/atoms/post-curation.d.ts.map +1 -0
- package/dist/types/src/atoms/post-display.d.ts +22 -0
- package/dist/types/src/atoms/post-display.d.ts.map +1 -0
- package/dist/types/src/atoms/post-read.d.ts +14 -0
- package/dist/types/src/atoms/post-read.d.ts.map +1 -0
- package/dist/types/src/atoms/post-tags.d.ts +16 -0
- package/dist/types/src/atoms/post-tags.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts +5 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/create-object.d.ts +5 -0
- package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +10 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/navigation-resolver.d.ts +5 -0
- package/dist/types/src/capabilities/navigation-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/routine-templates.d.ts +5 -0
- package/dist/types/src/capabilities/routine-templates.d.ts.map +1 -0
- package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
- package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
- package/dist/types/src/components/PostContent/PostContent.d.ts +30 -0
- package/dist/types/src/components/PostContent/PostContent.d.ts.map +1 -0
- package/dist/types/src/components/PostContent/PostContent.test.d.ts +2 -0
- package/dist/types/src/components/PostContent/PostContent.test.d.ts.map +1 -0
- package/dist/types/src/components/PostContent/index.d.ts +2 -0
- package/dist/types/src/components/PostContent/index.d.ts.map +1 -0
- package/dist/types/src/components/PostStack/PostStack.d.ts +17 -0
- package/dist/types/src/components/PostStack/PostStack.d.ts.map +1 -0
- package/dist/types/src/components/PostStack/PostStack.stories.d.ts +10 -0
- package/dist/types/src/components/PostStack/PostStack.stories.d.ts.map +1 -0
- package/dist/types/src/components/PostStack/index.d.ts +2 -0
- package/dist/types/src/components/PostStack/index.d.ts.map +1 -0
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts +23 -0
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts.map +1 -0
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.stories.d.ts +8 -0
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.stories.d.ts.map +1 -0
- package/dist/types/src/components/SubscriptionStack/index.d.ts +2 -0
- package/dist/types/src/components/SubscriptionStack/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +4 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts +6 -0
- package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts.map +1 -0
- package/dist/types/src/containers/FeedArticle/FeedArticle.stories.d.ts +12 -0
- package/dist/types/src/containers/FeedArticle/FeedArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/FeedArticle/FeedToolbar.d.ts +10 -0
- package/dist/types/src/containers/FeedArticle/FeedToolbar.d.ts.map +1 -0
- package/dist/types/src/containers/FeedArticle/index.d.ts +2 -0
- package/dist/types/src/containers/FeedArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts +6 -0
- package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts.map +1 -0
- package/dist/types/src/containers/FeedProperties/index.d.ts +2 -0
- package/dist/types/src/containers/FeedProperties/index.d.ts.map +1 -0
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts +6 -0
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts.map +1 -0
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.stories.d.ts +9 -0
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts +11 -0
- package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts.map +1 -0
- package/dist/types/src/containers/MagazineArticle/index.d.ts +2 -0
- package/dist/types/src/containers/MagazineArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts +18 -0
- package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts.map +1 -0
- package/dist/types/src/containers/PostArticle/PostArticle.d.ts +6 -0
- package/dist/types/src/containers/PostArticle/PostArticle.d.ts.map +1 -0
- package/dist/types/src/containers/PostArticle/PostArticle.stories.d.ts +8 -0
- package/dist/types/src/containers/PostArticle/PostArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/PostArticle/PostToolbar.d.ts +19 -0
- package/dist/types/src/containers/PostArticle/PostToolbar.d.ts.map +1 -0
- package/dist/types/src/containers/PostArticle/index.d.ts +2 -0
- package/dist/types/src/containers/PostArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/PostCard/PostCard.d.ts +11 -0
- package/dist/types/src/containers/PostCard/PostCard.d.ts.map +1 -0
- package/dist/types/src/containers/PostCard/index.d.ts +2 -0
- package/dist/types/src/containers/PostCard/index.d.ts.map +1 -0
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts +5 -0
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts.map +1 -0
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.d.ts +8 -0
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/SubscriptionsArticle/index.d.ts +2 -0
- package/dist/types/src/containers/SubscriptionsArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +7 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +33 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/operations/clear-magazine.d.ts +10 -0
- package/dist/types/src/operations/clear-magazine.d.ts.map +1 -0
- package/dist/types/src/operations/curate-magazine.d.ts +25 -0
- package/dist/types/src/operations/curate-magazine.d.ts.map +1 -0
- package/dist/types/src/operations/curate-magazine.skill.test.d.ts +2 -0
- package/dist/types/src/operations/curate-magazine.skill.test.d.ts.map +1 -0
- package/dist/types/src/operations/curate-magazine.test.d.ts +2 -0
- package/dist/types/src/operations/curate-magazine.test.d.ts.map +1 -0
- package/dist/types/src/operations/extraction/article.d.ts +37 -0
- package/dist/types/src/operations/extraction/article.d.ts.map +1 -0
- package/dist/types/src/operations/extraction/article.test.d.ts +2 -0
- package/dist/types/src/operations/extraction/article.test.d.ts.map +1 -0
- package/dist/types/src/operations/extraction/index.d.ts +2 -0
- package/dist/types/src/operations/extraction/index.d.ts.map +1 -0
- package/dist/types/src/operations/fetch-article-content.d.ts +5 -0
- package/dist/types/src/operations/fetch-article-content.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/load-post-content.d.ts +8 -0
- package/dist/types/src/operations/load-post-content.d.ts.map +1 -0
- package/dist/types/src/operations/sources/article.d.ts +24 -0
- package/dist/types/src/operations/sources/article.d.ts.map +1 -0
- package/dist/types/src/operations/sources/cors.d.ts +12 -0
- package/dist/types/src/operations/sources/cors.d.ts.map +1 -0
- package/dist/types/src/operations/sources/feed-fetcher.d.ts +22 -0
- package/dist/types/src/operations/sources/feed-fetcher.d.ts.map +1 -0
- package/dist/types/src/operations/sources/feed-fetcher.test.d.ts +2 -0
- package/dist/types/src/operations/sources/feed-fetcher.test.d.ts.map +1 -0
- package/dist/types/src/operations/sources/http.d.ts +9 -0
- package/dist/types/src/operations/sources/http.d.ts.map +1 -0
- package/dist/types/src/operations/sources/index.d.ts +6 -0
- package/dist/types/src/operations/sources/index.d.ts.map +1 -0
- package/dist/types/src/operations/sources/rss.d.ts +4 -0
- package/dist/types/src/operations/sources/rss.d.ts.map +1 -0
- package/dist/types/src/operations/sources/rss.test.d.ts +2 -0
- package/dist/types/src/operations/sources/rss.test.d.ts.map +1 -0
- package/dist/types/src/operations/sources/standard-site.d.ts +33 -0
- package/dist/types/src/operations/sources/standard-site.d.ts.map +1 -0
- package/dist/types/src/operations/sync-feed.d.ts +5 -0
- package/dist/types/src/operations/sync-feed.d.ts.map +1 -0
- package/dist/types/src/operations/sync-feed.test.d.ts +2 -0
- package/dist/types/src/operations/sync-feed.test.d.ts.map +1 -0
- package/dist/types/src/operations/util.d.ts +25 -0
- package/dist/types/src/operations/util.d.ts.map +1 -0
- package/dist/types/src/operations/util.test.d.ts +2 -0
- package/dist/types/src/operations/util.test.d.ts.map +1 -0
- package/dist/types/src/paths.d.ts +3 -0
- package/dist/types/src/paths.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/plugin.workerd.d.ts +3 -0
- package/dist/types/src/plugin.workerd.d.ts.map +1 -0
- package/dist/types/src/skills/index.d.ts +2 -0
- package/dist/types/src/skills/index.d.ts.map +1 -0
- package/dist/types/src/skills/magazine-skill.d.ts +4 -0
- package/dist/types/src/skills/magazine-skill.d.ts.map +1 -0
- package/dist/types/src/stories/ArticleExtractor.stories.d.ts +267 -0
- package/dist/types/src/stories/ArticleExtractor.stories.d.ts.map +1 -0
- package/dist/types/src/stories/MagazineCurate.stories.d.ts +32 -0
- package/dist/types/src/stories/MagazineCurate.stories.d.ts.map +1 -0
- package/dist/types/src/stories/theregister-fixture.test.d.ts +2 -0
- package/dist/types/src/stories/theregister-fixture.test.d.ts.map +1 -0
- package/dist/types/src/templates/magazine-curation.d.ts +7 -0
- package/dist/types/src/templates/magazine-curation.d.ts.map +1 -0
- package/dist/types/src/testing/builder.d.ts +28 -0
- package/dist/types/src/testing/builder.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +247 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/CreateSubscription.d.ts +29 -0
- package/dist/types/src/types/CreateSubscription.d.ts.map +1 -0
- package/dist/types/src/types/FeedOperation.d.ts +52 -0
- package/dist/types/src/types/FeedOperation.d.ts.map +1 -0
- package/dist/types/src/types/Magazine.d.ts +87 -0
- package/dist/types/src/types/Magazine.d.ts.map +1 -0
- package/dist/types/src/types/Magazine.test.d.ts +2 -0
- package/dist/types/src/types/Magazine.test.d.ts.map +1 -0
- package/dist/types/src/types/Subscription.d.ts +144 -0
- package/dist/types/src/types/Subscription.d.ts.map +1 -0
- package/dist/types/src/types/Subscription.test.d.ts +2 -0
- package/dist/types/src/types/Subscription.test.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +5 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/util/date.d.ts +8 -0
- package/dist/types/src/util/date.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +4 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/src/util/post-content.d.ts +14 -0
- package/dist/types/src/util/post-content.d.ts.map +1 -0
- package/dist/types/src/util/text.d.ts +18 -0
- package/dist/types/src/util/text.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/dx.config.ts +48 -0
- package/package.json +164 -0
- package/src/MagazinePlugin.test.ts +36 -0
- package/src/MagazinePlugin.tsx +61 -0
- package/src/MagazinePlugin.workerd.ts +31 -0
- package/src/atoms/atoms.test.ts +323 -0
- package/src/atoms/index.ts +10 -0
- package/src/atoms/magazine-posts.ts +70 -0
- package/src/atoms/post-content.ts +34 -0
- package/src/atoms/post-curation.ts +36 -0
- package/src/atoms/post-display.ts +59 -0
- package/src/atoms/post-read.ts +40 -0
- package/src/atoms/post-tags.ts +66 -0
- package/src/capabilities/app-graph-builder.ts +125 -0
- package/src/capabilities/create-object.ts +127 -0
- package/src/capabilities/index.ts +17 -0
- package/src/capabilities/navigation-resolver.ts +21 -0
- package/src/capabilities/operation-handler.ts +16 -0
- package/src/capabilities/react-surface.tsx +53 -0
- package/src/capabilities/routine-templates.ts +16 -0
- package/src/capabilities/skill-definition.ts +14 -0
- package/src/components/PostContent/PostContent.test.ts +81 -0
- package/src/components/PostContent/PostContent.tsx +111 -0
- package/src/components/PostContent/index.ts +5 -0
- package/src/components/PostStack/PostStack.stories.tsx +39 -0
- package/src/components/PostStack/PostStack.tsx +151 -0
- package/src/components/PostStack/index.ts +5 -0
- package/src/components/SubscriptionStack/SubscriptionStack.stories.tsx +52 -0
- package/src/components/SubscriptionStack/SubscriptionStack.tsx +161 -0
- package/src/components/SubscriptionStack/index.ts +5 -0
- package/src/components/index.ts +7 -0
- package/src/containers/FeedArticle/FeedArticle.stories.tsx +102 -0
- package/src/containers/FeedArticle/FeedArticle.tsx +68 -0
- package/src/containers/FeedArticle/FeedToolbar.tsx +47 -0
- package/src/containers/FeedArticle/index.ts +5 -0
- package/src/containers/FeedProperties/FeedProperties.tsx +107 -0
- package/src/containers/FeedProperties/index.ts +5 -0
- package/src/containers/MagazineArticle/MagazineArticle.stories.tsx +244 -0
- package/src/containers/MagazineArticle/MagazineArticle.tsx +146 -0
- package/src/containers/MagazineArticle/MagazineTile.tsx +88 -0
- package/src/containers/MagazineArticle/index.ts +5 -0
- package/src/containers/MagazineArticle/useToolbar.tsx +145 -0
- package/src/containers/PostArticle/PostArticle.stories.tsx +117 -0
- package/src/containers/PostArticle/PostArticle.tsx +140 -0
- package/src/containers/PostArticle/PostToolbar.tsx +120 -0
- package/src/containers/PostArticle/index.ts +5 -0
- package/src/containers/PostCard/PostCard.tsx +70 -0
- package/src/containers/PostCard/index.ts +5 -0
- package/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.tsx +80 -0
- package/src/containers/SubscriptionsArticle/SubscriptionsArticle.tsx +99 -0
- package/src/containers/SubscriptionsArticle/index.ts +5 -0
- package/src/containers/index.ts +11 -0
- package/src/index.ts +6 -0
- package/src/meta.ts +9 -0
- package/src/operations/clear-magazine.ts +83 -0
- package/src/operations/curate-magazine.skill.conversations.json +1 -0
- package/src/operations/curate-magazine.skill.test.ts +167 -0
- package/src/operations/curate-magazine.test.ts +91 -0
- package/src/operations/curate-magazine.ts +178 -0
- package/src/operations/extraction/article.test.ts +154 -0
- package/src/operations/extraction/article.ts +270 -0
- package/src/operations/extraction/index.ts +5 -0
- package/src/operations/fetch-article-content.ts +27 -0
- package/src/operations/index.ts +13 -0
- package/src/operations/load-post-content.ts +76 -0
- package/src/operations/sources/article.ts +122 -0
- package/src/operations/sources/cors.ts +19 -0
- package/src/operations/sources/feed-fetcher.test.ts +200 -0
- package/src/operations/sources/feed-fetcher.ts +26 -0
- package/src/operations/sources/http.ts +42 -0
- package/src/operations/sources/index.ts +9 -0
- package/src/operations/sources/rss.test.ts +201 -0
- package/src/operations/sources/rss.ts +138 -0
- package/src/operations/sources/standard-site.ts +381 -0
- package/src/operations/sources/testing/feed.xml +477 -0
- package/src/operations/sync-feed.test.ts +146 -0
- package/src/operations/sync-feed.ts +140 -0
- package/src/operations/util.test.ts +67 -0
- package/src/operations/util.ts +65 -0
- package/src/paths.ts +14 -0
- package/src/plugin.ts +11 -0
- package/src/plugin.workerd.ts +6 -0
- package/src/skills/index.ts +5 -0
- package/src/skills/magazine-skill.ts +45 -0
- package/src/stories/ArticleExtractor.stories.tsx +204 -0
- package/src/stories/MagazineCurate.stories.tsx +217 -0
- package/src/stories/fixtures/theregister-ai.xml +45 -0
- package/src/stories/theregister-fixture.test.ts +35 -0
- package/src/templates/magazine-curation.ts +47 -0
- package/src/testing/builder.ts +77 -0
- package/src/testing/index.ts +5 -0
- package/src/translations.ts +81 -0
- package/src/types/CreateSubscription.ts +61 -0
- package/src/types/FeedOperation.ts +134 -0
- package/src/types/Magazine.test.ts +69 -0
- package/src/types/Magazine.ts +214 -0
- package/src/types/Subscription.test.ts +171 -0
- package/src/types/Subscription.ts +366 -0
- package/src/types/index.ts +8 -0
- package/src/util/date.ts +27 -0
- package/src/util/index.ts +7 -0
- package/src/util/post-content.ts +19 -0
- package/src/util/text.ts +98 -0
- package/src/vite-env.d.ts +10 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { EffectEx } from '@dxos/effect';
|
|
9
|
+
import { Panel, Toolbar } from '@dxos/react-ui';
|
|
10
|
+
import { withAttention } from '@dxos/react-ui-attention/testing';
|
|
11
|
+
import { withMosaic } from '@dxos/react-ui-mosaic/testing';
|
|
12
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
13
|
+
|
|
14
|
+
import { PostStack, type PostStackAction } from '#components';
|
|
15
|
+
import { generateFeed, generatePosts } from '#testing';
|
|
16
|
+
import { type Subscription } from '#types';
|
|
17
|
+
|
|
18
|
+
type FeedArticleStoryProps = {
|
|
19
|
+
feedUrl?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const useFeedData = (feedUrl?: string): { feed: Subscription.Subscription; posts: Subscription.Post[] } | undefined => {
|
|
23
|
+
const [data, setData] = useState<{ feed: Subscription.Subscription; posts: Subscription.Post[] }>();
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
let cancelled = false;
|
|
27
|
+
const load = async () => {
|
|
28
|
+
if (feedUrl) {
|
|
29
|
+
const { fetchRss } = await import('../../operations/sources/rss');
|
|
30
|
+
const result = await EffectEx.runPromise(fetchRss(feedUrl, { corsProxy: '/api/rss?url=' }));
|
|
31
|
+
if (!cancelled) {
|
|
32
|
+
setData(result);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
const feed = generateFeed();
|
|
36
|
+
const posts = generatePosts(50);
|
|
37
|
+
if (!cancelled) {
|
|
38
|
+
setData({ feed, posts });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
void load().catch(() => {});
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
};
|
|
48
|
+
}, [feedUrl]);
|
|
49
|
+
|
|
50
|
+
return data;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const DefaultStory = ({ feedUrl }: FeedArticleStoryProps) => {
|
|
54
|
+
const data = useFeedData(feedUrl);
|
|
55
|
+
const [currentPostId, setCurrentPostId] = useState<string>();
|
|
56
|
+
|
|
57
|
+
const handleAction = (action: PostStackAction) => {
|
|
58
|
+
if (action.type === 'current') {
|
|
59
|
+
setCurrentPostId(action.postId);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!data) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { feed, posts } = data;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Panel.Root role='article' className='dx-document'>
|
|
71
|
+
<Panel.Toolbar asChild>
|
|
72
|
+
<Toolbar.Root>
|
|
73
|
+
<Toolbar.Text>{feed.name}</Toolbar.Text>
|
|
74
|
+
</Toolbar.Root>
|
|
75
|
+
</Panel.Toolbar>
|
|
76
|
+
<Panel.Content>
|
|
77
|
+
<PostStack id='story-feed' posts={posts} currentId={currentPostId} onAction={handleAction} />
|
|
78
|
+
</Panel.Content>
|
|
79
|
+
</Panel.Root>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const meta: Meta<typeof DefaultStory> = {
|
|
84
|
+
title: 'plugins/plugin-magazine/containers/FeedArticle',
|
|
85
|
+
component: DefaultStory,
|
|
86
|
+
decorators: [withTheme(), withLayout({ layout: 'column' }), withAttention(), withMosaic()],
|
|
87
|
+
parameters: {
|
|
88
|
+
layout: 'fullscreen',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default meta;
|
|
93
|
+
|
|
94
|
+
type Story = StoryObj<typeof meta>;
|
|
95
|
+
|
|
96
|
+
export const Default: Story = {};
|
|
97
|
+
|
|
98
|
+
export const FromRss: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
feedUrl: 'https://hnrss.org/frontpage',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
|
+
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
9
|
+
import { Filter, Obj, Query, Ref, Scope } from '@dxos/echo';
|
|
10
|
+
import { useObject, useQuery } from '@dxos/react-client/echo';
|
|
11
|
+
import { Panel } from '@dxos/react-ui';
|
|
12
|
+
|
|
13
|
+
import { PostStack, type PostStackAction } from '#components';
|
|
14
|
+
import { meta } from '#meta';
|
|
15
|
+
import { FeedOperation } from '#types';
|
|
16
|
+
import { Subscription } from '#types';
|
|
17
|
+
|
|
18
|
+
import { FeedToolbar } from './FeedToolbar';
|
|
19
|
+
|
|
20
|
+
export type FeedArticleProps = AppSurface.ObjectArticleProps<Subscription.Subscription>;
|
|
21
|
+
|
|
22
|
+
export const FeedArticle = ({ role, subject, attendableId }: FeedArticleProps) => {
|
|
23
|
+
const { invokePromise } = useOperationInvoker();
|
|
24
|
+
const [currentPostId, setCurrentPostId] = useState<string>();
|
|
25
|
+
const [subscription] = useObject(subject);
|
|
26
|
+
// Subscribe to the backing queue via its Ref — `.target` alone does not re-render when the
|
|
27
|
+
// feed loads after navigation (same pitfall as plugin-inbox MailboxArticle).
|
|
28
|
+
const [postFeed] = useObject(subscription?.feed);
|
|
29
|
+
const db = Obj.getDatabase(subscription);
|
|
30
|
+
const posts = useQuery(
|
|
31
|
+
db,
|
|
32
|
+
postFeed
|
|
33
|
+
? Query.select(Filter.type(Subscription.Post)).from(Scope.feed(Obj.getURI(postFeed)))
|
|
34
|
+
: Query.select(Filter.nothing()),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const handleAction = useCallback((action: PostStackAction) => {
|
|
38
|
+
if (action.type === 'current') {
|
|
39
|
+
setCurrentPostId(action.postId);
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const handleSync = useCallback(() => {
|
|
44
|
+
// Failures surface as a toast via `notify`; invokePromise resolves with `{ error }`, never throws.
|
|
45
|
+
void invokePromise(
|
|
46
|
+
FeedOperation.SyncFeed,
|
|
47
|
+
{ feed: Ref.make(subject) },
|
|
48
|
+
{
|
|
49
|
+
spaceId: Obj.getDatabase(subject)?.spaceId,
|
|
50
|
+
notify: { error: ['sync-feed-error.title', { ns: meta.profile.key }] },
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
}, [subject, invokePromise]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Panel.Root role={role} className='dx-document'>
|
|
57
|
+
<FeedToolbar attendableId={attendableId} onSync={handleSync} />
|
|
58
|
+
<Panel.Content asChild>
|
|
59
|
+
<PostStack
|
|
60
|
+
id={subscription?.id ?? subject.id}
|
|
61
|
+
posts={posts}
|
|
62
|
+
currentId={currentPostId}
|
|
63
|
+
onAction={handleAction}
|
|
64
|
+
/>
|
|
65
|
+
</Panel.Content>
|
|
66
|
+
</Panel.Root>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Panel } from '@dxos/react-ui';
|
|
8
|
+
import { Menu, MenuBuilder, useMenuBuilder } from '@dxos/react-ui-menu';
|
|
9
|
+
|
|
10
|
+
import { meta } from '#meta';
|
|
11
|
+
|
|
12
|
+
export type FeedToolbarProps = {
|
|
13
|
+
attendableId?: string;
|
|
14
|
+
onSync: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reactive feed toolbar built from the menu action-graph idiom: a single Sync action. Matches the
|
|
19
|
+
* magazine/post toolbars; the feed's name is surfaced by the plank chrome, not duplicated here.
|
|
20
|
+
*/
|
|
21
|
+
export const FeedToolbar = ({ attendableId, onSync }: FeedToolbarProps) => {
|
|
22
|
+
const menuActions = useMenuBuilder(
|
|
23
|
+
() =>
|
|
24
|
+
MenuBuilder.make()
|
|
25
|
+
.action(
|
|
26
|
+
'sync',
|
|
27
|
+
{
|
|
28
|
+
label: ['sync-feed.label', { ns: meta.profile.key }],
|
|
29
|
+
icon: 'ph--arrows-clockwise--regular',
|
|
30
|
+
iconOnly: true,
|
|
31
|
+
},
|
|
32
|
+
onSync,
|
|
33
|
+
)
|
|
34
|
+
.build(),
|
|
35
|
+
[onSync],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Menu.Root {...menuActions} attendableId={attendableId}>
|
|
40
|
+
<Panel.Toolbar asChild>
|
|
41
|
+
<Menu.Toolbar />
|
|
42
|
+
</Panel.Toolbar>
|
|
43
|
+
</Menu.Root>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
FeedToolbar.displayName = 'FeedToolbar';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
|
+
import { LayoutOperation, Paths } from '@dxos/app-toolkit';
|
|
9
|
+
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
10
|
+
import { Operation, Trigger } from '@dxos/compute';
|
|
11
|
+
import { Filter, Obj, Query, Ref } from '@dxos/echo';
|
|
12
|
+
import { useObject, useQuery } from '@dxos/react-client/echo';
|
|
13
|
+
import { IconButton, Input, useTranslation } from '@dxos/react-ui';
|
|
14
|
+
import { Form } from '@dxos/react-ui-form';
|
|
15
|
+
|
|
16
|
+
import { meta } from '#meta';
|
|
17
|
+
import { FeedOperation, Subscription } from '#types';
|
|
18
|
+
|
|
19
|
+
export type FeedPropertiesProps = AppSurface.ObjectPropertiesProps<Subscription.Subscription>;
|
|
20
|
+
|
|
21
|
+
export const FeedProperties = ({ subject }: FeedPropertiesProps) => {
|
|
22
|
+
const { t } = useTranslation(meta.profile.key);
|
|
23
|
+
const { invokePromise } = useOperationInvoker();
|
|
24
|
+
const db = useMemo(() => Obj.getDatabase(subject), [subject]);
|
|
25
|
+
const [pending, setPending] = useState(false);
|
|
26
|
+
|
|
27
|
+
const childTriggers = useQuery(
|
|
28
|
+
db,
|
|
29
|
+
Query.select(Filter.and(Filter.type(Trigger.Trigger), Filter.childOf(subject, { transitive: false }))).debugLabel(
|
|
30
|
+
'plugin-magazine.FeedProperties.syncTrigger',
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const syncTrigger = useMemo(() => childTriggers.find((trigger) => trigger.spec?.kind === 'timer'), [childTriggers]);
|
|
35
|
+
|
|
36
|
+
const [syncEnabled, setSyncEnabled] = useObject(syncTrigger, 'enabled');
|
|
37
|
+
|
|
38
|
+
const handleToggleSync = useCallback(async () => {
|
|
39
|
+
if (!db) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (syncTrigger) {
|
|
44
|
+
setSyncEnabled((enabled) => !enabled);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setPending(true);
|
|
49
|
+
try {
|
|
50
|
+
const operation = await ensureSyncFeedOperation(db);
|
|
51
|
+
db.add(
|
|
52
|
+
Trigger.make({
|
|
53
|
+
[Obj.Parent]: subject,
|
|
54
|
+
enabled: true,
|
|
55
|
+
spec: Trigger.specTimer('*/5 * * * *'),
|
|
56
|
+
runnable: Ref.make(operation),
|
|
57
|
+
input: { feed: db.makeRef(Obj.getURI(subject)) },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
} finally {
|
|
61
|
+
setPending(false);
|
|
62
|
+
}
|
|
63
|
+
}, [syncTrigger, db, subject, setSyncEnabled]);
|
|
64
|
+
|
|
65
|
+
const handleViewTrigger = useCallback(() => {
|
|
66
|
+
if (!db) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void invokePromise(LayoutOperation.Open, {
|
|
71
|
+
subject: [Paths.getSpacePath(db.spaceId, 'settings', 'org.dxos.plugin.routine.routines')],
|
|
72
|
+
workspace: Paths.getSpacePath(db.spaceId),
|
|
73
|
+
});
|
|
74
|
+
}, [invokePromise, db]);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Form.Section>
|
|
78
|
+
<Input.Root>
|
|
79
|
+
<Input.Label>{t('feed-sync.label')}</Input.Label>
|
|
80
|
+
<div className='flex flex-row items-center'>
|
|
81
|
+
<Input.Switch
|
|
82
|
+
checked={syncEnabled ?? false}
|
|
83
|
+
disabled={pending}
|
|
84
|
+
onCheckedChange={() => {
|
|
85
|
+
void handleToggleSync();
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
{syncTrigger && (
|
|
89
|
+
<IconButton iconOnly icon='ph--gear--regular' label={t('view-trigger.label')} onClick={handleViewTrigger} />
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
</Input.Root>
|
|
93
|
+
</Form.Section>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const ensureSyncFeedOperation = async (db: NonNullable<ReturnType<typeof Obj.getDatabase>>) => {
|
|
98
|
+
const existing = await db
|
|
99
|
+
.query(Filter.and(Filter.type(Operation.PersistentOperation), Filter.key(FeedOperation.SyncFeed.meta.key)))
|
|
100
|
+
.run();
|
|
101
|
+
const [existingOperation] = existing;
|
|
102
|
+
if (existingOperation) {
|
|
103
|
+
return existingOperation;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return db.add(Operation.serialize(FeedOperation.SyncFeed));
|
|
107
|
+
};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
9
|
+
|
|
10
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
11
|
+
import { type Client } from '@dxos/client';
|
|
12
|
+
import { type Space } from '@dxos/client/echo';
|
|
13
|
+
import { Feed, Filter, Obj, Ref } from '@dxos/echo';
|
|
14
|
+
import { ClientPlugin } from '@dxos/plugin-client/testing';
|
|
15
|
+
import { initializeIdentity } from '@dxos/plugin-client/testing';
|
|
16
|
+
import { SpacePlugin } from '@dxos/plugin-space/testing';
|
|
17
|
+
import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
|
|
18
|
+
import { useQuery, useSpaces } from '@dxos/react-client/echo';
|
|
19
|
+
import { Loading, withLayout } from '@dxos/react-ui/testing';
|
|
20
|
+
|
|
21
|
+
import { MagazineSkill } from '#skills';
|
|
22
|
+
import { generateCuratedPost, generateFeed, generateMagazine } from '#testing';
|
|
23
|
+
import { translations } from '#translations';
|
|
24
|
+
import { Magazine, Subscription } from '#types';
|
|
25
|
+
|
|
26
|
+
import { MagazinePlugin } from '../../MagazinePlugin';
|
|
27
|
+
import { MagazineArticle } from './MagazineArticle';
|
|
28
|
+
|
|
29
|
+
const DefaultStory = () => {
|
|
30
|
+
const spaces = useSpaces();
|
|
31
|
+
const space = spaces[spaces.length - 1];
|
|
32
|
+
const magazines = useQuery(space?.db, Filter.type(Magazine.Magazine));
|
|
33
|
+
const magazine = magazines[0];
|
|
34
|
+
if (!magazine) {
|
|
35
|
+
return <Loading />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return <MagazineArticle role='article' subject={magazine} attendableId='story' />;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const seedSpace =
|
|
42
|
+
(options: { postCount: number; feedCount: number; readFraction: number }) =>
|
|
43
|
+
({ client }: { client: Client }) =>
|
|
44
|
+
Effect.gen(function* () {
|
|
45
|
+
yield* initializeIdentity(client);
|
|
46
|
+
const space = (yield* Effect.promise(() => client.spaces.create())) as Space;
|
|
47
|
+
yield* Effect.promise(() => space.waitUntilReady());
|
|
48
|
+
|
|
49
|
+
const readCount = Math.floor(options.postCount * options.readFraction);
|
|
50
|
+
const posts: Subscription.Post[] = [];
|
|
51
|
+
for (let i = 0; i < options.postCount; i++) {
|
|
52
|
+
posts.push(space.db.add(generateCuratedPost({ read: i < readCount })));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const feeds: Subscription.Subscription[] = [];
|
|
56
|
+
for (let i = 0; i < options.feedCount; i++) {
|
|
57
|
+
feeds.push(space.db.add(generateFeed()));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
space.db.add(generateMagazine({ name: 'Distributed Systems Reading', feeds, posts }));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const buildMeta = (options: {
|
|
64
|
+
postCount: number;
|
|
65
|
+
feedCount: number;
|
|
66
|
+
readFraction: number;
|
|
67
|
+
}): Meta<typeof DefaultStory> => ({
|
|
68
|
+
component: DefaultStory,
|
|
69
|
+
decorators: [
|
|
70
|
+
withLayout({ layout: 'fullscreen' }),
|
|
71
|
+
withPluginManager({
|
|
72
|
+
plugins: [
|
|
73
|
+
...corePlugins(),
|
|
74
|
+
ClientPlugin({
|
|
75
|
+
types: [Subscription.Subscription, Subscription.Post, Magazine.Magazine],
|
|
76
|
+
onClientInitialized: seedSpace(options),
|
|
77
|
+
}),
|
|
78
|
+
SpacePlugin({}),
|
|
79
|
+
StorybookPlugin({}),
|
|
80
|
+
MagazinePlugin(),
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
parameters: {
|
|
85
|
+
layout: 'fullscreen',
|
|
86
|
+
translations,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const meta: Meta<typeof DefaultStory> = {
|
|
91
|
+
title: 'plugins/plugin-magazine/containers/MagazineArticle',
|
|
92
|
+
...buildMeta({ postCount: 12, feedCount: 2, readFraction: 0.25 }),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default meta;
|
|
96
|
+
|
|
97
|
+
type Story = StoryObj<typeof meta>;
|
|
98
|
+
|
|
99
|
+
export const Default: Story = {};
|
|
100
|
+
|
|
101
|
+
//
|
|
102
|
+
// CurateFlow story
|
|
103
|
+
//
|
|
104
|
+
// Seeds a magazine + a feed whose queue is pre-populated with three Posts.
|
|
105
|
+
// Renders MagazineArticle and exercises the full Curate cycle:
|
|
106
|
+
// 1. Click Curate. CurateMagazine pulls queue items into magazine.posts;
|
|
107
|
+
// tiles appear.
|
|
108
|
+
// 2. Click Clear. magazine.posts empties; "No articles yet" placeholder
|
|
109
|
+
// appears.
|
|
110
|
+
// 3. Click Curate again. With the canonical-proxy reuse fix, this must NOT
|
|
111
|
+
// throw the `addCore` invariant — and the tiles must come back, proving
|
|
112
|
+
// magazine.posts re-renders reactively after a second Curate.
|
|
113
|
+
//
|
|
114
|
+
|
|
115
|
+
const seedSpaceWithQueueItems = ({ client }: { client: Client }) =>
|
|
116
|
+
Effect.gen(function* () {
|
|
117
|
+
yield* initializeIdentity(client);
|
|
118
|
+
const space = (yield* Effect.promise(() => client.spaces.create())) as Space;
|
|
119
|
+
yield* Effect.promise(() => space.waitUntilReady());
|
|
120
|
+
|
|
121
|
+
// No `url` set: handleCurate's `validFeeds` filter skips this feed during
|
|
122
|
+
// the SyncFeed phase, so the test doesn't depend on a real RSS endpoint.
|
|
123
|
+
// CurateMagazine still iterates `magazine.feeds` and reads the backing
|
|
124
|
+
// queue, which is what we want to exercise.
|
|
125
|
+
const subscriptionFeed = space.db.add(
|
|
126
|
+
Subscription.makeSubscription({
|
|
127
|
+
name: 'Curate Test Feed',
|
|
128
|
+
type: 'rss',
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
yield* Effect.promise(() => space.db.flush());
|
|
132
|
+
|
|
133
|
+
// Push three Posts into the feed's backing queue via db.appendToFeed.
|
|
134
|
+
// Loading the ref is necessary post-`db.add` because the inline
|
|
135
|
+
// `savedTarget` from `Ref.make(echoFeed)` is dropped when the ref is
|
|
136
|
+
// encoded for persistence.
|
|
137
|
+
const echoFeed = yield* Effect.promise(() => subscriptionFeed.feed!.load());
|
|
138
|
+
const feedRef = Ref.make(subscriptionFeed);
|
|
139
|
+
const posts = [
|
|
140
|
+
Obj.make(Subscription.Post, {
|
|
141
|
+
source: feedRef,
|
|
142
|
+
title: 'Distributed systems are hard',
|
|
143
|
+
description: 'A long-form discussion of consistency, availability, and partition tolerance.',
|
|
144
|
+
author: 'Test Author',
|
|
145
|
+
published: '2026-04-01T00:00:00Z',
|
|
146
|
+
guid: 'post-a',
|
|
147
|
+
link: 'https://example.com/post-a',
|
|
148
|
+
}),
|
|
149
|
+
Obj.make(Subscription.Post, {
|
|
150
|
+
source: feedRef,
|
|
151
|
+
title: 'Local-first is the future',
|
|
152
|
+
description: 'How CRDTs and last-writer-wins reconcile collaborative state across peers.',
|
|
153
|
+
author: 'Test Author',
|
|
154
|
+
published: '2026-04-02T00:00:00Z',
|
|
155
|
+
guid: 'post-b',
|
|
156
|
+
link: 'https://example.com/post-b',
|
|
157
|
+
}),
|
|
158
|
+
Obj.make(Subscription.Post, {
|
|
159
|
+
source: feedRef,
|
|
160
|
+
title: 'P2P discovery patterns',
|
|
161
|
+
description: 'Comparing rendezvous, DHT-based, and broadcast strategies for peer discovery.',
|
|
162
|
+
author: 'Test Author',
|
|
163
|
+
published: '2026-04-03T00:00:00Z',
|
|
164
|
+
guid: 'post-c',
|
|
165
|
+
link: 'https://example.com/post-c',
|
|
166
|
+
}),
|
|
167
|
+
];
|
|
168
|
+
yield* Effect.promise(() => space.db.appendToFeed(echoFeed, posts));
|
|
169
|
+
|
|
170
|
+
const mag = Magazine.make({
|
|
171
|
+
name: 'Curate Flow Test',
|
|
172
|
+
feeds: [Ref.make(subscriptionFeed)],
|
|
173
|
+
});
|
|
174
|
+
space.db.add(mag);
|
|
175
|
+
// Curation resolves the base methodology skill from the registry; register it here since the
|
|
176
|
+
// automation plugin (which normally syncs SkillDefinition capabilities) isn't in this story.
|
|
177
|
+
client.graph.registry.add([MagazineSkill.make()]);
|
|
178
|
+
yield* Effect.promise(() => space.db.flush());
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// TODO(jdw): Re-enable on jdw/identifiers — the seed path now uses
|
|
182
|
+
// `space.db.appendToFeed(echoFeed, posts)` but the storybook story still loses
|
|
183
|
+
// queue contents between the seed promise resolving and `magazine.posts` being
|
|
184
|
+
// read inside the `CurateMagazine` operation. The unit tests drive
|
|
185
|
+
// `curateMagazine` directly against an `EchoTestBuilder` database and pass;
|
|
186
|
+
// the fully-wired plugin container in this story does not. Default story is
|
|
187
|
+
// unaffected (it never touches the feed path).
|
|
188
|
+
export const CurateFlow: Story = {
|
|
189
|
+
tags: ['!test'],
|
|
190
|
+
decorators: [
|
|
191
|
+
withLayout({ layout: 'fullscreen' }),
|
|
192
|
+
withPluginManager({
|
|
193
|
+
plugins: [
|
|
194
|
+
...corePlugins(),
|
|
195
|
+
ClientPlugin({
|
|
196
|
+
types: [Feed.Feed, Subscription.Subscription, Subscription.Post, Magazine.Magazine],
|
|
197
|
+
onClientInitialized: seedSpaceWithQueueItems,
|
|
198
|
+
}),
|
|
199
|
+
SpacePlugin({}),
|
|
200
|
+
StorybookPlugin({}),
|
|
201
|
+
MagazinePlugin(),
|
|
202
|
+
],
|
|
203
|
+
}),
|
|
204
|
+
],
|
|
205
|
+
parameters: {
|
|
206
|
+
layout: 'fullscreen',
|
|
207
|
+
translations,
|
|
208
|
+
},
|
|
209
|
+
play: async ({ canvasElement }) => {
|
|
210
|
+
const canvas = within(canvasElement);
|
|
211
|
+
|
|
212
|
+
// Wait for the magazine to render (initially empty).
|
|
213
|
+
const empty = await canvas.findByText(/no articles yet/i, {}, { timeout: 10_000 });
|
|
214
|
+
await expect(empty).toBeVisible();
|
|
215
|
+
|
|
216
|
+
// First Curate.
|
|
217
|
+
const curateButton = await canvas.findByRole('button', { name: /^curate$/i });
|
|
218
|
+
await userEvent.click(curateButton);
|
|
219
|
+
|
|
220
|
+
await waitFor(
|
|
221
|
+
async () => {
|
|
222
|
+
const tile = await canvas.findByText('Distributed systems are hard');
|
|
223
|
+
await expect(tile).toBeVisible();
|
|
224
|
+
},
|
|
225
|
+
{ timeout: 15_000 },
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Second Curate (back-to-back, no Clear). This is the user-reported
|
|
229
|
+
// failure mode: the second invocation previously tripped ECHO's
|
|
230
|
+
// `!_objects.has(core.id)` invariant in `addCore`. With the
|
|
231
|
+
// reuse-or-add fix it must NOT throw — the tiles already on screen
|
|
232
|
+
// must remain visible (curate is purely additive, dedups by id).
|
|
233
|
+
const curateAgain = await canvas.findByRole('button', { name: /^curate$/i });
|
|
234
|
+
await userEvent.click(curateAgain);
|
|
235
|
+
|
|
236
|
+
// Give the operation enough time to run.
|
|
237
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
238
|
+
|
|
239
|
+
// The previously-shown tiles must still be present; nothing should have crashed the plank.
|
|
240
|
+
await expect(await canvas.findByText('Distributed systems are hard')).toBeVisible();
|
|
241
|
+
await expect(await canvas.findByText('Local-first is the future')).toBeVisible();
|
|
242
|
+
await expect(await canvas.findByText('P2P discovery patterns')).toBeVisible();
|
|
243
|
+
},
|
|
244
|
+
};
|