@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,70 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Data from 'effect/Data';
|
|
7
|
+
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { type Magazine, type Subscription } from '../types';
|
|
11
|
+
import { publishedTimestamp } from '../util/date';
|
|
12
|
+
import { postTagsAtom } from './post-tags';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Tile filter mode. Mutually exclusive — `default` shows everything except archived,
|
|
16
|
+
* `starred` filters to starred posts, `archived` shows only archived posts.
|
|
17
|
+
*/
|
|
18
|
+
export type MagazineView = 'default' | 'starred' | 'archived';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Ordered Posts referenced by the Magazine, derived directly from the `magazine.posts` refs — no
|
|
22
|
+
* query. Fires when the refs array changes (membership/order) or when a ref resolves. Resolving each
|
|
23
|
+
* ref here, rather than a `reference('posts').from(Scope.space(...))` query, avoids the cross-feed
|
|
24
|
+
* fan-out that returned each curated Post once per feed scope.
|
|
25
|
+
*/
|
|
26
|
+
const magazinePostsAtom = Atom.family((magazine: Magazine.Magazine) =>
|
|
27
|
+
Atom.make<Subscription.Post[]>((get) => {
|
|
28
|
+
const refs = get(Obj.atomProperty(magazine, 'posts')) ?? [];
|
|
29
|
+
const posts = refs.map((ref) => get(ref.atom)).filter((post): post is Subscription.Post => Boolean(post));
|
|
30
|
+
return [...posts].sort((postA, postB) => publishedTimestamp(postB.published) - publishedTimestamp(postA.published));
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether a Post is visible under a given view. Depends ONLY on the Post's tag slice + view, so
|
|
36
|
+
* read-state changes never wake it (and effect-atom dedupes the boolean). Keyed by a value-equal
|
|
37
|
+
* `Data.tuple([post, view])` rather than a nested family — the intermediate of a nested family is
|
|
38
|
+
* only weakly held and could be GC'd out from under its mounted leaf atoms.
|
|
39
|
+
*/
|
|
40
|
+
const postVisibilityAtom = Atom.family((key: readonly [Subscription.Post, MagazineView]) =>
|
|
41
|
+
Atom.make<boolean>((get) => {
|
|
42
|
+
const [post, view] = key;
|
|
43
|
+
const { starred, archived } = get(postTagsAtom(post));
|
|
44
|
+
switch (view) {
|
|
45
|
+
case 'archived':
|
|
46
|
+
return archived;
|
|
47
|
+
case 'starred':
|
|
48
|
+
return !archived && starred;
|
|
49
|
+
default:
|
|
50
|
+
return !archived;
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Ordered, view-filtered Posts for a Magazine. Re-runs on membership change OR a Post crossing the
|
|
57
|
+
* view's filter boundary (star/archive); never on read-state changes. Keyed by a value-equal
|
|
58
|
+
* `Data.tuple([magazine, view])`.
|
|
59
|
+
*/
|
|
60
|
+
export const visibleMagazinePostsAtom = Atom.family((key: readonly [Magazine.Magazine, MagazineView]) =>
|
|
61
|
+
Atom.make<Subscription.Post[]>((get) => {
|
|
62
|
+
const [magazine, view] = key;
|
|
63
|
+
const posts = get(magazinePostsAtom(magazine));
|
|
64
|
+
return posts.filter((post) => get(postVisibilityAtom(Data.tuple(post, view))));
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
/** Ordered, view-filtered Posts for a Magazine. */
|
|
69
|
+
export const useVisibleMagazinePosts = (magazine: Magazine.Magazine, view: MagazineView): Subscription.Post[] =>
|
|
70
|
+
useAtomValue(visibleMagazinePostsAtom(Data.tuple(magazine, view)));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
|
|
7
|
+
import { Obj } from '@dxos/echo';
|
|
8
|
+
|
|
9
|
+
import { Subscription } from '../types';
|
|
10
|
+
|
|
11
|
+
/** This Post's fetched body (newest {@link Subscription.PostContent} entry), or undefined. */
|
|
12
|
+
export const postContentAtom = Atom.family((post: Subscription.Post) =>
|
|
13
|
+
Atom.make<Subscription.PostContent | undefined>((get) => {
|
|
14
|
+
const ref = post.source;
|
|
15
|
+
if (!ref) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const subscription = get(ref.atom);
|
|
19
|
+
if (!subscription) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const db = Obj.getDatabase(subscription);
|
|
23
|
+
const query = Subscription.queryPostContentForPost(subscription, post);
|
|
24
|
+
if (!db || !query) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const entries = get(db.query(query).atom);
|
|
28
|
+
return Subscription.pickLatestPostContent(entries);
|
|
29
|
+
}).pipe(Atom.keepAlive),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/** This Post's fetched body, reactively. */
|
|
33
|
+
export const usePostContentAtom = (post: Subscription.Post): Subscription.PostContent | undefined =>
|
|
34
|
+
useAtomValue(postContentAtom(post));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Data from 'effect/Data';
|
|
7
|
+
|
|
8
|
+
import { StateMap } from '@dxos/schema';
|
|
9
|
+
|
|
10
|
+
import { Magazine, type Subscription } from '../types';
|
|
11
|
+
|
|
12
|
+
/** Per-Post magazine-scoped curation slice (agent-written snippet/hero image). */
|
|
13
|
+
export type CurationSlice = { snippet: string | undefined; imageUrl: string | undefined };
|
|
14
|
+
|
|
15
|
+
const EMPTY_CURATION_SLICE: CurationSlice = { snippet: undefined, imageUrl: undefined };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* This Post's magazine-scoped curation slice (snippet/imageUrl), sliced off the Magazine's
|
|
19
|
+
* `postState`. Fires only when this Post's slice changes — sibling Posts' curation mutations are
|
|
20
|
+
* discarded without propagating. Keyed by a value-equal `Data.tuple([post, magazine])`.
|
|
21
|
+
*/
|
|
22
|
+
export const postCurationAtom = Atom.family((key: readonly [Subscription.Post, Magazine.Magazine]) =>
|
|
23
|
+
Atom.make<CurationSlice>((get) => {
|
|
24
|
+
const [post, magazine] = key;
|
|
25
|
+
const stateMap = get(magazine.postState.atom);
|
|
26
|
+
if (!stateMap) {
|
|
27
|
+
return EMPTY_CURATION_SLICE;
|
|
28
|
+
}
|
|
29
|
+
const state = get(StateMap.atom<Magazine.PostState>(stateMap, post.id));
|
|
30
|
+
return { snippet: state.snippet, imageUrl: state.imageUrl };
|
|
31
|
+
}).pipe(Atom.keepAlive),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/** This Post's magazine-scoped curation slice. */
|
|
35
|
+
export const usePostCuration = (post: Subscription.Post, magazine: Magazine.Magazine): CurationSlice =>
|
|
36
|
+
useAtomValue(postCurationAtom(Data.tuple(post, magazine)));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Data from 'effect/Data';
|
|
7
|
+
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { type Magazine, type Subscription } from '../types';
|
|
11
|
+
import { getImageUrl, getSnippet } from '../util/post-content';
|
|
12
|
+
import { postCurationAtom } from './post-curation';
|
|
13
|
+
import { postReadAtom } from './post-read';
|
|
14
|
+
import { postTagsAtom } from './post-tags';
|
|
15
|
+
|
|
16
|
+
/** Aggregate per-Post display data folded from the Post + its source Subscription's per-Post slice. */
|
|
17
|
+
export type MagazinePostData = {
|
|
18
|
+
post: Obj.Snapshot<Subscription.Post>;
|
|
19
|
+
feedName: string | undefined;
|
|
20
|
+
read: boolean;
|
|
21
|
+
starred: boolean;
|
|
22
|
+
snippet: string;
|
|
23
|
+
imageUrl: string | undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Aggregate display data for one tile within a magazine. Fires only on this Post's
|
|
28
|
+
* read/star/feed-name/curation changes. Keyed by a value-equal `Data.tuple([post, magazine])`.
|
|
29
|
+
*
|
|
30
|
+
* Snippet and imageUrl precedence: agent-written postState > RSS-derived description fallback.
|
|
31
|
+
*/
|
|
32
|
+
export const postDisplayAtom = Atom.family((key: readonly [Subscription.Post, Magazine.Magazine]) =>
|
|
33
|
+
Atom.make<MagazinePostData>((get) => {
|
|
34
|
+
const [post, magazine] = key;
|
|
35
|
+
const snapshot = get(Obj.atom(post));
|
|
36
|
+
const ref = post.source;
|
|
37
|
+
const subscription = ref ? get(ref.atom) : undefined;
|
|
38
|
+
// `name`/`url` via property atoms — fire only on name/url change (e.g. after first sync).
|
|
39
|
+
const feedName = subscription
|
|
40
|
+
? get(Obj.atomProperty(subscription, 'name')) || get(Obj.atomProperty(subscription, 'url'))
|
|
41
|
+
: undefined;
|
|
42
|
+
const { readAt } = get(postReadAtom(post));
|
|
43
|
+
const { starred } = get(postTagsAtom(post));
|
|
44
|
+
// Agent-written snippet/imageUrl (granular per-Post slice); fall back to description derivation.
|
|
45
|
+
const curation = get(postCurationAtom(Data.tuple(post, magazine)));
|
|
46
|
+
return {
|
|
47
|
+
post: snapshot,
|
|
48
|
+
feedName,
|
|
49
|
+
read: readAt !== undefined,
|
|
50
|
+
starred,
|
|
51
|
+
snippet: curation.snippet ?? getSnippet(snapshot),
|
|
52
|
+
imageUrl: curation.imageUrl ?? getImageUrl(snapshot),
|
|
53
|
+
};
|
|
54
|
+
}).pipe(Atom.keepAlive),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/** Aggregate per-Post display data for a magazine tile. */
|
|
58
|
+
export const useMagazinePostData = (post: Subscription.Post, magazine: Magazine.Magazine): MagazinePostData =>
|
|
59
|
+
useAtomValue(postDisplayAtom(Data.tuple(post, magazine)));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
|
|
7
|
+
import { StateMap } from '@dxos/schema';
|
|
8
|
+
|
|
9
|
+
import { Subscription } from '../types';
|
|
10
|
+
|
|
11
|
+
/** Per-Post read slice. */
|
|
12
|
+
export type ReadSlice = { readAt: string | undefined };
|
|
13
|
+
|
|
14
|
+
const EMPTY_READ_SLICE: ReadSlice = { readAt: undefined };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This Post's `readAt`, sliced off its source Subscription's `postState`. Fires only when this
|
|
18
|
+
* Post's `readAt` changes — sibling Posts' mutations are discarded without propagating.
|
|
19
|
+
*/
|
|
20
|
+
export const postReadAtom = Atom.family((post: Subscription.Post) =>
|
|
21
|
+
Atom.make<ReadSlice>((get) => {
|
|
22
|
+
const ref = post.source;
|
|
23
|
+
if (!ref) {
|
|
24
|
+
return EMPTY_READ_SLICE;
|
|
25
|
+
}
|
|
26
|
+
const subscription = get(ref.atom);
|
|
27
|
+
if (!subscription) {
|
|
28
|
+
return EMPTY_READ_SLICE;
|
|
29
|
+
}
|
|
30
|
+
const stateMap = get(subscription.postState.atom);
|
|
31
|
+
if (!stateMap) {
|
|
32
|
+
return EMPTY_READ_SLICE;
|
|
33
|
+
}
|
|
34
|
+
const state = get(StateMap.atom<Subscription.PostState>(stateMap, post.id));
|
|
35
|
+
return { readAt: state.readAt };
|
|
36
|
+
}).pipe(Atom.keepAlive),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/** This Post's read slice. */
|
|
40
|
+
export const useReadState = (post: Subscription.Post): ReadSlice => useAtomValue(postReadAtom(post));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
|
|
7
|
+
import { type Database, Filter, Obj, Tag } from '@dxos/echo';
|
|
8
|
+
import { TagIndex } from '@dxos/schema';
|
|
9
|
+
|
|
10
|
+
import { Subscription } from '../types';
|
|
11
|
+
|
|
12
|
+
/** Per-Post tag slice (star/archive membership). */
|
|
13
|
+
export type TagSlice = { starred: boolean; archived: boolean };
|
|
14
|
+
|
|
15
|
+
const EMPTY_TAG_SLICE: TagSlice = { starred: false, archived: false };
|
|
16
|
+
const EMPTY_TAG_URIS = { starredUri: undefined, archivedUri: undefined } as const;
|
|
17
|
+
|
|
18
|
+
/** Resolve the uri of a system Tag by foreign key (mirrors `useSystemTags`). */
|
|
19
|
+
const uriForTag = (tags: readonly Tag.Tag[], key: { source: string; id: string }): string | undefined => {
|
|
20
|
+
const match = tags.find((tag) => Obj.getKeys(tag, key.source).some((foreignKey) => foreignKey.id === key.id));
|
|
21
|
+
return match ? Obj.getURI(match).toString() : undefined;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolved uris of the `starred` / `archived` system Tags, keyed by database. Fires only when the
|
|
26
|
+
* Tag set changes (rare — on first star/archive). Shared by every per-Post tag slice.
|
|
27
|
+
*/
|
|
28
|
+
const tagUrisAtom = Atom.family((db: Database.Database) =>
|
|
29
|
+
db.query(Filter.type(Tag.Tag)).atom.pipe(
|
|
30
|
+
Atom.map((tags) => ({
|
|
31
|
+
starredUri: uriForTag(tags, Subscription.SYSTEM_TAGS.starred.key),
|
|
32
|
+
archivedUri: uriForTag(tags, Subscription.SYSTEM_TAGS.archived.key),
|
|
33
|
+
})),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* This Post's `{ starred, archived }`, sliced off its source Subscription's `tags`. Fires only
|
|
39
|
+
* when this Post's tag membership flips — sibling Posts' tag mutations are discarded without
|
|
40
|
+
* propagating. Re-initialises its tag subscription when tag uris change (first star ever in the space).
|
|
41
|
+
*/
|
|
42
|
+
export const postTagsAtom = Atom.family((post: Subscription.Post) =>
|
|
43
|
+
Atom.make<TagSlice>((get) => {
|
|
44
|
+
const ref = post.source;
|
|
45
|
+
if (!ref) {
|
|
46
|
+
return EMPTY_TAG_SLICE;
|
|
47
|
+
}
|
|
48
|
+
const subscription = get(ref.atom);
|
|
49
|
+
if (!subscription) {
|
|
50
|
+
return EMPTY_TAG_SLICE;
|
|
51
|
+
}
|
|
52
|
+
const tagIndex = get(subscription.tags.atom);
|
|
53
|
+
if (!tagIndex) {
|
|
54
|
+
return EMPTY_TAG_SLICE;
|
|
55
|
+
}
|
|
56
|
+
const db = Obj.getDatabase(subscription);
|
|
57
|
+
const { starredUri, archivedUri } = db ? get(tagUrisAtom(db)) : EMPTY_TAG_URIS;
|
|
58
|
+
return {
|
|
59
|
+
starred: get(TagIndex.atom(tagIndex, post.id, starredUri)),
|
|
60
|
+
archived: get(TagIndex.atom(tagIndex, post.id, archivedUri)),
|
|
61
|
+
};
|
|
62
|
+
}).pipe(Atom.keepAlive),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/** This Post's tag slice (star/archive). */
|
|
66
|
+
export const useTagState = (post: Subscription.Post): TagSlice => useAtomValue(postTagsAtom(post));
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
|
|
9
|
+
import { Capability } from '@dxos/app-framework';
|
|
10
|
+
import { AppCapabilities, AppNode, AppNodeMatcher, Paths, TypeSection } from '@dxos/app-toolkit';
|
|
11
|
+
import { Operation } from '@dxos/compute';
|
|
12
|
+
import { Obj, Ref, Type } from '@dxos/echo';
|
|
13
|
+
import { AttentionCapabilities } from '@dxos/plugin-attention';
|
|
14
|
+
import { GraphBuilder } from '@dxos/plugin-graph';
|
|
15
|
+
import { SpaceOperation } from '@dxos/plugin-space';
|
|
16
|
+
import { linkedSegment, selectionAspect } from '@dxos/react-ui-attention';
|
|
17
|
+
|
|
18
|
+
import { meta } from '#meta';
|
|
19
|
+
import { FeedOperation } from '#types';
|
|
20
|
+
import { Magazine, Subscription } from '#types';
|
|
21
|
+
|
|
22
|
+
import { getMagazinesPath } from '../paths';
|
|
23
|
+
|
|
24
|
+
export default Capability.makeModule(
|
|
25
|
+
Effect.fnUntraced(function* () {
|
|
26
|
+
const viewState = yield* Capability.get(AttentionCapabilities.ViewState);
|
|
27
|
+
const selectedId = Atom.family((nodeId: string) =>
|
|
28
|
+
Atom.make((get) => {
|
|
29
|
+
const selection = get(viewState.atom(selectionAspect, nodeId));
|
|
30
|
+
return selection.mode === 'single' ? selection.id : undefined;
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const extensions = yield* Effect.all([
|
|
35
|
+
// Magazine type section in the content group.
|
|
36
|
+
TypeSection.createTypeSectionExtension(Magazine.Magazine, {
|
|
37
|
+
match: AppNodeMatcher.whenNavTreeGroup(Paths.GroupTypes.content),
|
|
38
|
+
createObject: (space) =>
|
|
39
|
+
Operation.invoke(SpaceOperation.OpenCreateObject, {
|
|
40
|
+
target: space.db,
|
|
41
|
+
typename: Type.getTypename(Magazine.Magazine),
|
|
42
|
+
targetNodeId: getMagazinesPath(space.db.spaceId),
|
|
43
|
+
}),
|
|
44
|
+
}),
|
|
45
|
+
|
|
46
|
+
// Feeds as children under each Magazine node.
|
|
47
|
+
GraphBuilder.createExtension({
|
|
48
|
+
id: 'magazineFeeds',
|
|
49
|
+
match: (node) => (Magazine.instanceOf(node.data) ? Option.some(node.data as Magazine.Magazine) : Option.none()),
|
|
50
|
+
connector: (magazine, get) => {
|
|
51
|
+
const db = Obj.getDatabase(magazine);
|
|
52
|
+
if (!db) {
|
|
53
|
+
return Effect.succeed([]);
|
|
54
|
+
}
|
|
55
|
+
const feedRefs = get(Obj.atomProperty(magazine, 'feeds'));
|
|
56
|
+
const feeds = feedRefs
|
|
57
|
+
.map((ref) => get(ref.atom))
|
|
58
|
+
.filter((feed): feed is Subscription.Subscription => Subscription.instanceOf(feed));
|
|
59
|
+
return Effect.succeed(
|
|
60
|
+
feeds
|
|
61
|
+
.map((feed) => AppNode.makeObject({ get, db, object: feed }))
|
|
62
|
+
.filter((node): node is NonNullable<typeof node> => node !== null),
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
// Companion panel: resolve the selected Post under a Magazine node.
|
|
68
|
+
GraphBuilder.createExtension({
|
|
69
|
+
id: 'magazinePost',
|
|
70
|
+
match: (node) =>
|
|
71
|
+
Magazine.instanceOf(node.data)
|
|
72
|
+
? Option.some({ magazine: node.data as Magazine.Magazine, nodeId: node.id })
|
|
73
|
+
: Option.none(),
|
|
74
|
+
connector: (matched, get) => {
|
|
75
|
+
const { magazine, nodeId } = matched;
|
|
76
|
+
const postId = get(selectedId(nodeId));
|
|
77
|
+
let post: Subscription.Post | undefined;
|
|
78
|
+
if (postId) {
|
|
79
|
+
for (const ref of magazine.posts) {
|
|
80
|
+
const resolved = get(ref.atom) as Subscription.Post | undefined;
|
|
81
|
+
if (resolved?.id === postId) {
|
|
82
|
+
post = resolved;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return Effect.succeed([
|
|
88
|
+
AppNode.makeCompanion({
|
|
89
|
+
id: linkedSegment('post'),
|
|
90
|
+
label: ['post-companion.label', { ns: meta.profile.key }],
|
|
91
|
+
icon: 'ph--article--regular',
|
|
92
|
+
data: post,
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
|
|
98
|
+
// Actions on each Subscription.Subscription node.
|
|
99
|
+
GraphBuilder.createExtension({
|
|
100
|
+
id: 'feedActions',
|
|
101
|
+
match: (node) =>
|
|
102
|
+
Subscription.instanceOf(node.data) ? Option.some(node.data as Subscription.Subscription) : Option.none(),
|
|
103
|
+
actions: (feed) =>
|
|
104
|
+
Effect.succeed([
|
|
105
|
+
{
|
|
106
|
+
id: 'sync',
|
|
107
|
+
data: () =>
|
|
108
|
+
Operation.invoke(
|
|
109
|
+
FeedOperation.SyncFeed,
|
|
110
|
+
{ feed: Ref.make(feed) },
|
|
111
|
+
{ spaceId: Obj.getDatabase(feed)?.spaceId },
|
|
112
|
+
),
|
|
113
|
+
properties: {
|
|
114
|
+
label: ['sync-feed.label', { ns: meta.profile.key }],
|
|
115
|
+
icon: 'ph--arrows-clockwise--regular',
|
|
116
|
+
disposition: 'list-item',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
]),
|
|
120
|
+
}),
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
return Capability.contributes(AppCapabilities.AppGraphBuilder, extensions);
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
|
|
8
|
+
import { Capability } from '@dxos/app-framework';
|
|
9
|
+
import { Operation } from '@dxos/compute';
|
|
10
|
+
import { Obj, Ref, Type } from '@dxos/echo';
|
|
11
|
+
import { SpaceOperation } from '@dxos/plugin-space';
|
|
12
|
+
import { SpaceCapabilities } from '@dxos/plugin-space';
|
|
13
|
+
import { AutofillAnnotation, OptionsLookupAnnotation, autofill, optionsLookup } from '@dxos/react-ui-form';
|
|
14
|
+
|
|
15
|
+
import { CreateSubscription, FeedOperation, Magazine, Subscription } from '#types';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
browserCorsProxy,
|
|
19
|
+
fetchRss,
|
|
20
|
+
listStandardSitePublications,
|
|
21
|
+
searchStandardSiteHandles,
|
|
22
|
+
} from '../operations/sources';
|
|
23
|
+
import { getMagazinesPath } from '../paths';
|
|
24
|
+
|
|
25
|
+
const StandardSiteCreate = Schema.Struct({
|
|
26
|
+
...CreateSubscription.StandardSiteCreateBase.fields,
|
|
27
|
+
// Handle is a combobox: typing queries known handles (typeahead); the typed text stays selectable.
|
|
28
|
+
handle: CreateSubscription.StandardSiteCreateBase.fields.handle.pipe(
|
|
29
|
+
OptionsLookupAnnotation.set(
|
|
30
|
+
optionsLookup<CreateSubscription.StandardSiteValues>()(
|
|
31
|
+
['handle'],
|
|
32
|
+
({ handle }) =>
|
|
33
|
+
searchStandardSiteHandles(handle ?? '', { corsProxy: browserCorsProxy() }).pipe(
|
|
34
|
+
Effect.map((suggestions) =>
|
|
35
|
+
suggestions.map((suggestion) => ({
|
|
36
|
+
value: suggestion.handle,
|
|
37
|
+
label: suggestion.handle,
|
|
38
|
+
secondaryLabel: suggestion.displayName,
|
|
39
|
+
})),
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
{ combobox: true },
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
// Publication options are looked up from the entered `handle`.
|
|
47
|
+
publication: CreateSubscription.StandardSiteCreateBase.fields.publication.pipe(
|
|
48
|
+
OptionsLookupAnnotation.set(
|
|
49
|
+
optionsLookup<CreateSubscription.StandardSiteValues>()(['handle'], ({ handle }) =>
|
|
50
|
+
CreateSubscription.isHandle(handle)
|
|
51
|
+
? listStandardSitePublications(handle, { corsProxy: browserCorsProxy() }).pipe(
|
|
52
|
+
Effect.map((publications) =>
|
|
53
|
+
publications.map((publication) => ({
|
|
54
|
+
value: publication.site,
|
|
55
|
+
label: publication.name ?? publication.url ?? publication.site,
|
|
56
|
+
})),
|
|
57
|
+
),
|
|
58
|
+
Effect.orElseSucceed(() => []),
|
|
59
|
+
)
|
|
60
|
+
: Effect.succeed([]),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const RssCreate = Schema.Struct({
|
|
67
|
+
...CreateSubscription.RssCreateBase.fields,
|
|
68
|
+
// Name is pre-filled from the feed title once the `url` is a valid feed URL.
|
|
69
|
+
name: Schema.optional(
|
|
70
|
+
Schema.String.pipe(
|
|
71
|
+
AutofillAnnotation.set(
|
|
72
|
+
autofill<CreateSubscription.RssValues>()(['url'], ({ url }) =>
|
|
73
|
+
CreateSubscription.isUrl(url)
|
|
74
|
+
? fetchRss(url, { corsProxy: browserCorsProxy() }).pipe(
|
|
75
|
+
Effect.map((result) => result.feed.name),
|
|
76
|
+
Effect.orElseSucceed(() => undefined),
|
|
77
|
+
)
|
|
78
|
+
: Effect.succeed(undefined),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
).annotations({ title: 'Name' }),
|
|
82
|
+
),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const CreateSubscriptionSchema = Schema.Union(StandardSiteCreate, RssCreate);
|
|
86
|
+
|
|
87
|
+
export default Capability.makeModule(
|
|
88
|
+
Effect.fnUntraced(function* () {
|
|
89
|
+
return [
|
|
90
|
+
Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
|
|
91
|
+
id: Type.getTypename(Subscription.Subscription),
|
|
92
|
+
inputSchema: CreateSubscriptionSchema,
|
|
93
|
+
createObject: (props, options) =>
|
|
94
|
+
Effect.gen(function* () {
|
|
95
|
+
const object = CreateSubscription.makeSubscriptionFromCreate(props);
|
|
96
|
+
const result = yield* Operation.invoke(SpaceOperation.AddObject, {
|
|
97
|
+
object,
|
|
98
|
+
target: options.target,
|
|
99
|
+
targetNodeId: options.targetNodeId,
|
|
100
|
+
});
|
|
101
|
+
// Auto-sync after creation if URL is provided.
|
|
102
|
+
if (object.url) {
|
|
103
|
+
yield* Operation.schedule(
|
|
104
|
+
FeedOperation.SyncFeed,
|
|
105
|
+
{ feed: Ref.make(object) },
|
|
106
|
+
{ spaceId: Obj.getDatabase(object)?.spaceId },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}),
|
|
111
|
+
}),
|
|
112
|
+
Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
|
|
113
|
+
id: Type.getTypename(Magazine.Magazine),
|
|
114
|
+
inputSchema: Magazine.CreateMagazineSchema,
|
|
115
|
+
createObject: (props, options) =>
|
|
116
|
+
Effect.gen(function* () {
|
|
117
|
+
const magazine = Magazine.make(props);
|
|
118
|
+
return yield* Operation.invoke(SpaceOperation.AddObject, {
|
|
119
|
+
object: magazine,
|
|
120
|
+
target: options.target,
|
|
121
|
+
targetNodeId: getMagazinesPath(options.db.spaceId),
|
|
122
|
+
});
|
|
123
|
+
}),
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Capability } from '@dxos/app-framework';
|
|
6
|
+
import type { OperationHandlerSet } from '@dxos/compute';
|
|
7
|
+
|
|
8
|
+
export const AppGraphBuilder = Capability.lazy('AppGraphBuilder', () => import('./app-graph-builder'));
|
|
9
|
+
export const RoutineTemplates = Capability.lazy('RoutineTemplates', () => import('./routine-templates'));
|
|
10
|
+
export const SkillDefinition = Capability.lazy('SkillDefinition', () => import('./skill-definition'));
|
|
11
|
+
export const CreateObject = Capability.lazy('CreateObject', () => import('./create-object'));
|
|
12
|
+
export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
|
|
13
|
+
'OperationHandler',
|
|
14
|
+
() => import('./operation-handler'),
|
|
15
|
+
);
|
|
16
|
+
export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
|
|
17
|
+
export const NavigationResolver = Capability.lazy('NavigationResolver', () => import('./navigation-resolver'));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { Capability } from '@dxos/app-framework';
|
|
8
|
+
import { AppCapabilities, Paths, TypeSection } from '@dxos/app-toolkit';
|
|
9
|
+
|
|
10
|
+
import { Magazine } from '#types';
|
|
11
|
+
|
|
12
|
+
export default Capability.makeModule(
|
|
13
|
+
Effect.fnUntraced(function* () {
|
|
14
|
+
return [
|
|
15
|
+
Capability.contributes(
|
|
16
|
+
AppCapabilities.NavigationPathResolver,
|
|
17
|
+
TypeSection.createTypeSectionPathResolver(Magazine.Magazine, { groupId: Paths.GroupSegments.content }),
|
|
18
|
+
),
|
|
19
|
+
];
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
8
|
+
import type { OperationHandlerSet } from '@dxos/compute';
|
|
9
|
+
|
|
10
|
+
import { MagazineOperationHandlerSet } from '#operations';
|
|
11
|
+
|
|
12
|
+
export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
|
|
13
|
+
Effect.fnUntraced(function* () {
|
|
14
|
+
return Capability.contributes(Capabilities.OperationHandler, MagazineOperationHandlerSet);
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
9
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
10
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
11
|
+
import { Position } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
import { FeedArticle, FeedProperties, MagazineArticle, PostArticle, PostCard } from '#containers';
|
|
14
|
+
import { Magazine, Subscription } from '#types';
|
|
15
|
+
|
|
16
|
+
export default Capability.makeModule(() =>
|
|
17
|
+
Effect.succeed(
|
|
18
|
+
Capability.contributes(Capabilities.ReactSurface, [
|
|
19
|
+
Surface.create({
|
|
20
|
+
id: 'magazineArticle',
|
|
21
|
+
filter: AppSurface.object(AppSurface.Article, Magazine.Magazine),
|
|
22
|
+
component: ({ data, role }) => (
|
|
23
|
+
<MagazineArticle role={role} subject={data.subject} attendableId={data.attendableId} />
|
|
24
|
+
),
|
|
25
|
+
}),
|
|
26
|
+
Surface.create({
|
|
27
|
+
id: 'feedArticle',
|
|
28
|
+
filter: AppSurface.object(AppSurface.Article, Subscription.Subscription),
|
|
29
|
+
component: ({ data, role }) => (
|
|
30
|
+
<FeedArticle role={role} subject={data.subject} attendableId={data.attendableId} />
|
|
31
|
+
),
|
|
32
|
+
}),
|
|
33
|
+
Surface.create({
|
|
34
|
+
id: 'postArticle',
|
|
35
|
+
filter: AppSurface.object(AppSurface.Article, Subscription.Post),
|
|
36
|
+
component: ({ data, role }) => (
|
|
37
|
+
<PostArticle role={role} subject={data.subject} attendableId={data.attendableId} />
|
|
38
|
+
),
|
|
39
|
+
}),
|
|
40
|
+
Surface.create({
|
|
41
|
+
id: 'postCard',
|
|
42
|
+
position: Position.first,
|
|
43
|
+
filter: AppSurface.object(AppSurface.CardContent, Subscription.Post),
|
|
44
|
+
component: ({ data, role }) => <PostCard role={role} subject={data.subject} />,
|
|
45
|
+
}),
|
|
46
|
+
Surface.create({
|
|
47
|
+
id: 'feedProperties',
|
|
48
|
+
filter: AppSurface.object(AppSurface.ObjectProperties, Subscription.Subscription),
|
|
49
|
+
component: ({ data }) => <FeedProperties subject={data.subject} />,
|
|
50
|
+
}),
|
|
51
|
+
]),
|
|
52
|
+
),
|
|
53
|
+
);
|