@dxos/plugin-magazine 0.10.0 → 0.11.1
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/dist/lib/MagazinePlugin.mjs +36 -0
- package/dist/lib/MagazinePlugin.mjs.map +1 -0
- package/dist/lib/MagazinePlugin.workerd.mjs +26 -0
- package/dist/lib/MagazinePlugin.workerd.mjs.map +1 -0
- package/dist/lib/atoms.mjs +175 -0
- package/dist/lib/atoms.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +12 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-FeedArticle.mjs +70 -0
- package/dist/lib/chunk-FeedArticle.mjs.map +1 -0
- package/dist/lib/chunk-FeedProperties.mjs +78 -0
- package/dist/lib/chunk-FeedProperties.mjs.map +1 -0
- package/dist/lib/chunk-MagazineArticle.mjs +267 -0
- package/dist/lib/chunk-MagazineArticle.mjs.map +1 -0
- package/dist/lib/chunk-PostArticle.mjs +174 -0
- package/dist/lib/chunk-PostArticle.mjs.map +1 -0
- package/dist/lib/chunk-PostCard.mjs +62 -0
- package/dist/lib/chunk-PostCard.mjs.map +1 -0
- package/dist/lib/chunk-app-graph-builder.mjs +91 -0
- package/dist/lib/chunk-app-graph-builder.mjs.map +1 -0
- package/dist/lib/chunk-clear-magazine.mjs +37 -0
- package/dist/lib/chunk-clear-magazine.mjs.map +1 -0
- package/dist/lib/chunk-create-object.mjs +59 -0
- package/dist/lib/chunk-create-object.mjs.map +1 -0
- package/dist/lib/chunk-curate-magazine.mjs +164 -0
- package/dist/lib/chunk-curate-magazine.mjs.map +1 -0
- package/dist/lib/chunk-date.mjs +25 -0
- package/dist/lib/chunk-date.mjs.map +1 -0
- package/dist/lib/chunk-fetch-article-content.mjs +26 -0
- package/dist/lib/chunk-fetch-article-content.mjs.map +1 -0
- package/dist/lib/chunk-load-post-content.mjs +57 -0
- package/dist/lib/chunk-load-post-content.mjs.map +1 -0
- package/dist/lib/{neutral/chunk-JFPR6OV4.mjs → chunk-meta.mjs} +19 -26
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +13 -0
- package/dist/lib/chunk-operation-handler.mjs.map +1 -0
- package/dist/lib/chunk-paths.mjs +8 -0
- package/dist/lib/chunk-paths.mjs.map +1 -0
- package/dist/lib/chunk-post-content.mjs +14 -0
- package/dist/lib/chunk-post-content.mjs.map +1 -0
- package/dist/lib/chunk-react-surface.mjs +60 -0
- package/dist/lib/chunk-react-surface.mjs.map +1 -0
- package/dist/lib/chunk-routine-templates.mjs +52 -0
- package/dist/lib/chunk-routine-templates.mjs.map +1 -0
- package/dist/lib/chunk-skill-definition.mjs +12 -0
- package/dist/lib/chunk-skill-definition.mjs.map +1 -0
- package/dist/lib/chunk-sources.mjs +576 -0
- package/dist/lib/chunk-sources.mjs.map +1 -0
- package/dist/lib/chunk-sync-feed.mjs +86 -0
- package/dist/lib/chunk-sync-feed.mjs.map +1 -0
- package/dist/lib/chunk-text.mjs +52 -0
- package/dist/lib/chunk-text.mjs.map +1 -0
- package/dist/lib/chunk-types.mjs +607 -0
- package/dist/lib/chunk-types.mjs.map +1 -0
- package/dist/lib/components.mjs +323 -0
- package/dist/lib/components.mjs.map +1 -0
- package/dist/lib/containers.mjs +11 -0
- package/dist/lib/containers.mjs.map +1 -0
- package/dist/lib/index.mjs +3 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/operations.mjs +7 -0
- package/dist/lib/operations.mjs.map +1 -0
- package/dist/lib/plugin.mjs +9 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/plugin.workerd.mjs +3 -0
- package/dist/lib/{neutral/skills/index.mjs → skills.mjs} +15 -28
- package/dist/lib/skills.mjs.map +1 -0
- package/dist/lib/testing.mjs +71 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/translations.mjs +74 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/types/src/MagazinePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
- package/dist/types/src/capabilities/create-object.d.ts +1 -1
- package/dist/types/src/capabilities/index.d.ts +0 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/routine-templates.d.ts +1 -1
- package/dist/types/src/components/PostContent/PostContent.d.ts +1 -13
- package/dist/types/src/components/PostContent/PostContent.d.ts.map +1 -1
- package/dist/types/src/components/PostContent/dedupe-images.d.ts +13 -0
- package/dist/types/src/components/PostContent/dedupe-images.d.ts.map +1 -0
- package/dist/types/src/components/PostContent/index.d.ts +1 -0
- package/dist/types/src/components/PostContent/index.d.ts.map +1 -1
- package/dist/types/src/components/PostStack/PostStack.d.ts +1 -1
- package/dist/types/src/components/PostStack/PostStack.d.ts.map +1 -1
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts +1 -1
- package/dist/types/src/components/SubscriptionStack/SubscriptionStack.d.ts.map +1 -1
- package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts +4 -1
- package/dist/types/src/containers/FeedArticle/FeedArticle.d.ts.map +1 -1
- package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts +4 -1
- package/dist/types/src/containers/FeedProperties/FeedProperties.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts +4 -1
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/MagazineArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts +4 -1
- package/dist/types/src/containers/MagazineArticle/MagazineTile.d.ts.map +1 -1
- package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts +4 -8
- package/dist/types/src/containers/MagazineArticle/useToolbar.d.ts.map +1 -1
- package/dist/types/src/containers/PostArticle/PostArticle.d.ts +4 -1
- package/dist/types/src/containers/PostArticle/PostArticle.d.ts.map +1 -1
- package/dist/types/src/containers/PostArticle/PostArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/PostArticle/PostToolbar.d.ts +1 -1
- package/dist/types/src/containers/PostArticle/PostToolbar.d.ts.map +1 -1
- package/dist/types/src/containers/PostCard/PostCard.d.ts +4 -6
- package/dist/types/src/containers/PostCard/PostCard.d.ts.map +1 -1
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts +4 -1
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.d.ts.map +1 -1
- package/dist/types/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.d.ts.map +1 -1
- package/dist/types/src/stories/ArticleExtractor.stories.d.ts +1 -0
- package/dist/types/src/stories/ArticleExtractor.stories.d.ts.map +1 -1
- package/dist/types/src/stories/MagazineCurate.stories.d.ts.map +1 -1
- package/dist/types/src/templates/magazine-curation.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +1 -1
- package/package.json +57 -60
- package/src/MagazinePlugin.tsx +0 -3
- package/src/atoms/atoms.test.ts +5 -4
- package/src/capabilities/app-graph-builder.ts +8 -6
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/{react-surface.tsx → react-surface.ts} +10 -12
- package/src/components/PostContent/PostContent.test.ts +1 -1
- package/src/components/PostContent/PostContent.tsx +1 -41
- package/src/components/PostContent/dedupe-images.ts +48 -0
- package/src/components/PostContent/index.ts +1 -0
- package/src/containers/FeedArticle/FeedArticle.stories.tsx +2 -2
- package/src/containers/FeedArticle/FeedArticle.tsx +4 -2
- package/src/containers/FeedProperties/FeedProperties.tsx +6 -4
- package/src/containers/MagazineArticle/MagazineArticle.stories.tsx +2 -1
- package/src/containers/MagazineArticle/MagazineArticle.tsx +8 -6
- package/src/containers/MagazineArticle/MagazineTile.tsx +2 -0
- package/src/containers/MagazineArticle/useToolbar.tsx +2 -0
- package/src/containers/PostArticle/PostArticle.stories.tsx +2 -1
- package/src/containers/PostArticle/PostArticle.tsx +6 -3
- package/src/containers/PostArticle/PostToolbar.tsx +1 -1
- package/src/containers/PostCard/PostCard.tsx +3 -1
- package/src/containers/SubscriptionsArticle/SubscriptionsArticle.stories.tsx +2 -1
- package/src/containers/SubscriptionsArticle/SubscriptionsArticle.tsx +5 -3
- package/src/operations/curate-magazine.test.ts +108 -4
- package/src/operations/sync-feed.test.ts +1 -1
- package/src/operations/sync-feed.ts +1 -1
- package/src/paths.ts +3 -3
- package/src/stories/ArticleExtractor.stories.tsx +1 -1
- package/src/stories/MagazineCurate.stories.tsx +3 -2
- package/src/templates/magazine-curation.ts +3 -2
- package/src/types/Magazine.test.ts +1 -1
- package/src/types/Subscription.ts +1 -1
- package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs +0 -90
- package/dist/lib/neutral/FeedArticle-PEWCFFSP.mjs.map +0 -7
- package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs +0 -97
- package/dist/lib/neutral/FeedProperties-EIO7IZOA.mjs.map +0 -7
- package/dist/lib/neutral/MagazineArticle-5T7RTIPS.mjs +0 -325
- package/dist/lib/neutral/MagazineArticle-5T7RTIPS.mjs.map +0 -7
- package/dist/lib/neutral/MagazinePlugin.mjs +0 -62
- package/dist/lib/neutral/MagazinePlugin.mjs.map +0 -7
- package/dist/lib/neutral/MagazinePlugin.workerd.mjs +0 -12
- package/dist/lib/neutral/MagazinePlugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/PostArticle-BGIPU7XA.mjs +0 -230
- package/dist/lib/neutral/PostArticle-BGIPU7XA.mjs.map +0 -7
- package/dist/lib/neutral/PostCard-VDGN7BBA.mjs +0 -60
- package/dist/lib/neutral/PostCard-VDGN7BBA.mjs.map +0 -7
- package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs +0 -121
- package/dist/lib/neutral/app-graph-builder-JDPRBVHL.mjs.map +0 -7
- package/dist/lib/neutral/capabilities/index.mjs +0 -21
- package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3K4AQKBZ.mjs +0 -12
- package/dist/lib/neutral/chunk-3K4AQKBZ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-AFPBWBTK.mjs +0 -591
- package/dist/lib/neutral/chunk-AFPBWBTK.mjs.map +0 -7
- package/dist/lib/neutral/chunk-APQRTYT3.mjs +0 -57
- package/dist/lib/neutral/chunk-APQRTYT3.mjs.map +0 -7
- package/dist/lib/neutral/chunk-GU725ZF4.mjs +0 -11
- package/dist/lib/neutral/chunk-GU725ZF4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-JFPR6OV4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-KTYLUOUG.mjs +0 -36
- package/dist/lib/neutral/chunk-KTYLUOUG.mjs.map +0 -7
- package/dist/lib/neutral/chunk-LFSQMLPK.mjs +0 -8
- package/dist/lib/neutral/chunk-LFSQMLPK.mjs.map +0 -7
- package/dist/lib/neutral/chunk-MBPX7RPI.mjs +0 -14
- package/dist/lib/neutral/chunk-MBPX7RPI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-OYCDRSWB.mjs +0 -624
- package/dist/lib/neutral/chunk-OYCDRSWB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SAWKLGJU.mjs +0 -28
- package/dist/lib/neutral/chunk-SAWKLGJU.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XCNCK47N.mjs +0 -15
- package/dist/lib/neutral/chunk-XCNCK47N.mjs.map +0 -7
- package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs +0 -59
- package/dist/lib/neutral/clear-magazine-KNY4OWY4.mjs.map +0 -7
- package/dist/lib/neutral/components/index.mjs +0 -338
- package/dist/lib/neutral/components/index.mjs.map +0 -7
- package/dist/lib/neutral/containers/index.mjs +0 -17
- package/dist/lib/neutral/containers/index.mjs.map +0 -7
- package/dist/lib/neutral/create-object-SYZMRR4H.mjs +0 -98
- package/dist/lib/neutral/create-object-SYZMRR4H.mjs.map +0 -7
- package/dist/lib/neutral/curate-magazine-7RMKSJC5.mjs +0 -184
- package/dist/lib/neutral/curate-magazine-7RMKSJC5.mjs.map +0 -7
- package/dist/lib/neutral/fetch-article-content-CXPKXIMR.mjs +0 -31
- package/dist/lib/neutral/fetch-article-content-CXPKXIMR.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -18
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/load-post-content-H3S4TKVM.mjs +0 -65
- package/dist/lib/neutral/load-post-content-H3S4TKVM.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/meta.mjs +0 -8
- package/dist/lib/neutral/meta.mjs.map +0 -7
- package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs +0 -18
- package/dist/lib/neutral/navigation-resolver-IN4OMCDR.mjs.map +0 -7
- package/dist/lib/neutral/operation-handler-JE3743BU.mjs +0 -8
- package/dist/lib/neutral/operation-handler-JE3743BU.mjs.map +0 -7
- package/dist/lib/neutral/operations/index.mjs +0 -8
- package/dist/lib/neutral/operations/index.mjs.map +0 -7
- package/dist/lib/neutral/plugin.mjs +0 -16
- package/dist/lib/neutral/plugin.mjs.map +0 -7
- package/dist/lib/neutral/plugin.workerd.mjs +0 -16
- package/dist/lib/neutral/plugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-TZESAMG2.mjs +0 -60
- package/dist/lib/neutral/react-surface-TZESAMG2.mjs.map +0 -7
- package/dist/lib/neutral/routine-templates-D5A2L4KL.mjs +0 -52
- package/dist/lib/neutral/routine-templates-D5A2L4KL.mjs.map +0 -7
- package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs +0 -8
- package/dist/lib/neutral/skill-definition-3KLMUEC5.mjs.map +0 -7
- package/dist/lib/neutral/skills/index.mjs.map +0 -7
- package/dist/lib/neutral/sync-feed-SDXYF7HU.mjs +0 -99
- package/dist/lib/neutral/sync-feed-SDXYF7HU.mjs.map +0 -7
- package/dist/lib/neutral/translations.mjs +0 -81
- package/dist/lib/neutral/translations.mjs.map +0 -7
- package/dist/lib/neutral/types/index.mjs +0 -14
- package/dist/lib/neutral/types/index.mjs.map +0 -7
- package/dist/types/src/capabilities/navigation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/navigation-resolver.d.ts.map +0 -1
- package/dist/types/src/operations/curate-magazine.skill.test.d.ts +0 -2
- package/dist/types/src/operations/curate-magazine.skill.test.d.ts.map +0 -1
- package/src/capabilities/navigation-resolver.ts +0 -21
- package/src/operations/curate-magazine.skill.conversations.json +0 -1
- package/src/operations/curate-magazine.skill.test.ts +0 -167
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { t as getMagazinesPath } from "./chunk-paths.mjs";
|
|
2
|
+
import { Capability } from "@dxos/app-framework";
|
|
3
|
+
import { Obj, Ref, Type } from "@dxos/echo";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { Operation } from "@dxos/compute";
|
|
6
|
+
import { meta } from "#meta";
|
|
7
|
+
import { AppCapabilities, AppNode, AppNodeMatcher, GraphPath, TypeSection } from "@dxos/app-toolkit";
|
|
8
|
+
import { AttentionCapabilities } from "@dxos/plugin-attention";
|
|
9
|
+
import { FeedOperation, Magazine, Subscription } from "#types";
|
|
10
|
+
import { Atom } from "@effect-atom/atom";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
import { GraphBuilder } from "@dxos/plugin-graph";
|
|
13
|
+
import { SpaceOperation } from "@dxos/plugin-space";
|
|
14
|
+
import { Selection } from "@dxos/react-ui-attention";
|
|
15
|
+
//#region src/capabilities/app-graph-builder.ts
|
|
16
|
+
var app_graph_builder_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
17
|
+
const viewState = yield* Capability.get(AttentionCapabilities.ViewState);
|
|
18
|
+
const selectedId = Atom.family((nodeId) => Atom.make((get) => {
|
|
19
|
+
const selection = get(viewState.atom(Selection.aspect, nodeId));
|
|
20
|
+
return selection.mode === "single" ? selection.id : void 0;
|
|
21
|
+
}));
|
|
22
|
+
const extensions = yield* Effect.all([
|
|
23
|
+
TypeSection.createTypeSectionExtension(Magazine.Magazine, {
|
|
24
|
+
urlKey: "magazine",
|
|
25
|
+
match: AppNodeMatcher.whenNavTreeGroup(GraphPath.GroupTypes.content),
|
|
26
|
+
groupSegment: GraphPath.GroupSegments.content,
|
|
27
|
+
createObject: (space) => Operation.invoke(SpaceOperation.OpenCreateObject, {
|
|
28
|
+
target: space.db,
|
|
29
|
+
typename: Type.getTypename(Magazine.Magazine),
|
|
30
|
+
targetNodeId: getMagazinesPath(space.db.spaceId)
|
|
31
|
+
})
|
|
32
|
+
}),
|
|
33
|
+
GraphBuilder.createExtension({
|
|
34
|
+
id: "magazineFeeds",
|
|
35
|
+
match: (node) => Magazine.instanceOf(node.data) ? Option.some(node.data) : Option.none(),
|
|
36
|
+
connector: (magazine, get) => {
|
|
37
|
+
const db = Obj.getDatabase(magazine);
|
|
38
|
+
if (!db) return Effect.succeed([]);
|
|
39
|
+
const feeds = get(Obj.atomProperty(magazine, "feeds")).map((ref) => get(ref.atom)).filter((feed) => Subscription.instanceOf(feed));
|
|
40
|
+
return Effect.succeed(feeds.map((feed) => AppNode.makeObject({
|
|
41
|
+
get,
|
|
42
|
+
db,
|
|
43
|
+
object: feed
|
|
44
|
+
})).filter((node) => node !== null));
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
47
|
+
GraphBuilder.createExtension({
|
|
48
|
+
id: "magazinePost",
|
|
49
|
+
match: (node) => Magazine.instanceOf(node.data) ? Option.some({
|
|
50
|
+
magazine: node.data,
|
|
51
|
+
nodeId: node.id
|
|
52
|
+
}) : Option.none(),
|
|
53
|
+
connector: (matched, get) => {
|
|
54
|
+
const { magazine, nodeId } = matched;
|
|
55
|
+
const postId = get(selectedId(nodeId));
|
|
56
|
+
let post;
|
|
57
|
+
if (postId) for (const ref of magazine.posts) {
|
|
58
|
+
const resolved = get(ref.atom);
|
|
59
|
+
if (resolved?.id === postId) {
|
|
60
|
+
post = resolved;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return Effect.succeed([AppNode.makeCompanion({
|
|
65
|
+
variant: "post",
|
|
66
|
+
label: ["post-companion.label", { ns: meta.profile.key }],
|
|
67
|
+
icon: "ph--article--regular",
|
|
68
|
+
data: post
|
|
69
|
+
})]);
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
GraphBuilder.createExtension({
|
|
73
|
+
id: "feedActions",
|
|
74
|
+
match: (node) => Subscription.instanceOf(node.data) ? Option.some(node.data) : Option.none(),
|
|
75
|
+
actions: (feed) => Effect.succeed([{
|
|
76
|
+
id: "sync",
|
|
77
|
+
data: () => Operation.invoke(FeedOperation.SyncFeed, { feed: Ref.make(feed) }, { spaceId: Obj.getDatabase(feed)?.spaceId }),
|
|
78
|
+
properties: {
|
|
79
|
+
label: ["sync-feed.label", { ns: meta.profile.key }],
|
|
80
|
+
icon: "ph--arrows-clockwise--regular",
|
|
81
|
+
disposition: "list-item"
|
|
82
|
+
}
|
|
83
|
+
}])
|
|
84
|
+
})
|
|
85
|
+
]);
|
|
86
|
+
return Capability.contributes(AppCapabilities.AppGraphBuilder, extensions);
|
|
87
|
+
}));
|
|
88
|
+
//#endregion
|
|
89
|
+
export { app_graph_builder_default as default };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=chunk-app-graph-builder.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-app-graph-builder.mjs","names":[],"sources":["../../src/capabilities/app-graph-builder.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Atom } from '@effect-atom/atom';\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\n\nimport { Capability } from '@dxos/app-framework';\nimport { AppCapabilities, AppNode, AppNodeMatcher, GraphPath, TypeSection } from '@dxos/app-toolkit';\nimport { Operation } from '@dxos/compute';\nimport { Obj, Ref, Type } from '@dxos/echo';\nimport { AttentionCapabilities } from '@dxos/plugin-attention';\nimport { GraphBuilder } from '@dxos/plugin-graph';\nimport { SpaceOperation } from '@dxos/plugin-space';\nimport { Selection } from '@dxos/react-ui-attention';\n\nimport { meta } from '#meta';\nimport { FeedOperation } from '#types';\nimport { Magazine, Subscription } from '#types';\n\nimport { getMagazinesPath } from '../paths';\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n const viewState = yield* Capability.get(AttentionCapabilities.ViewState);\n const selectedId = Atom.family((nodeId: string) =>\n Atom.make((get) => {\n const selection = get(viewState.atom(Selection.aspect, nodeId));\n return selection.mode === 'single' ? selection.id : undefined;\n }),\n );\n\n const extensions = yield* Effect.all([\n // Magazine type section in the content group.\n TypeSection.createTypeSectionExtension(Magazine.Magazine, {\n urlKey: 'magazine',\n match: AppNodeMatcher.whenNavTreeGroup(GraphPath.GroupTypes.content),\n groupSegment: GraphPath.GroupSegments.content,\n createObject: (space) =>\n Operation.invoke(SpaceOperation.OpenCreateObject, {\n target: space.db,\n typename: Type.getTypename(Magazine.Magazine),\n targetNodeId: getMagazinesPath(space.db.spaceId),\n }),\n }),\n\n // Feeds as children under each Magazine node.\n GraphBuilder.createExtension({\n id: 'magazineFeeds',\n match: (node) => (Magazine.instanceOf(node.data) ? Option.some(node.data as Magazine.Magazine) : Option.none()),\n connector: (magazine, get) => {\n const db = Obj.getDatabase(magazine);\n if (!db) {\n return Effect.succeed([]);\n }\n const feedRefs = get(Obj.atomProperty(magazine, 'feeds'));\n const feeds = feedRefs\n .map((ref) => get(ref.atom))\n .filter((feed): feed is Subscription.Subscription => Subscription.instanceOf(feed));\n return Effect.succeed(\n feeds\n .map((feed) => AppNode.makeObject({ get, db, object: feed }))\n .filter((node): node is NonNullable<typeof node> => node !== null),\n );\n },\n }),\n\n // Companion panel: resolve the selected Post under a Magazine node.\n GraphBuilder.createExtension({\n id: 'magazinePost',\n match: (node) =>\n Magazine.instanceOf(node.data)\n ? Option.some({ magazine: node.data as Magazine.Magazine, nodeId: node.id })\n : Option.none(),\n connector: (matched, get) => {\n const { magazine, nodeId } = matched;\n const postId = get(selectedId(nodeId));\n let post: Subscription.Post | undefined;\n if (postId) {\n for (const ref of magazine.posts) {\n const resolved = get(ref.atom) as Subscription.Post | undefined;\n if (resolved?.id === postId) {\n post = resolved;\n break;\n }\n }\n }\n return Effect.succeed([\n AppNode.makeCompanion({\n variant: 'post',\n label: ['post-companion.label', { ns: meta.profile.key }],\n icon: 'ph--article--regular',\n data: post,\n }),\n ]);\n },\n }),\n\n // Actions on each Subscription.Subscription node.\n GraphBuilder.createExtension({\n id: 'feedActions',\n match: (node) =>\n Subscription.instanceOf(node.data) ? Option.some(node.data as Subscription.Subscription) : Option.none(),\n actions: (feed) =>\n Effect.succeed([\n {\n id: 'sync',\n data: () =>\n Operation.invoke(\n FeedOperation.SyncFeed,\n { feed: Ref.make(feed) },\n { spaceId: Obj.getDatabase(feed)?.spaceId },\n ),\n properties: {\n label: ['sync-feed.label', { ns: meta.profile.key }],\n icon: 'ph--arrows-clockwise--regular',\n disposition: 'list-item',\n },\n },\n ]),\n }),\n ]);\n\n return Capability.contributes(AppCapabilities.AppGraphBuilder, extensions);\n }),\n);\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,IAAA,4BAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,MAAM,YAAY,OAAO,WAAW,IAAI,sBAAsB,SAAS;CACvE,MAAM,aAAa,KAAK,QAAQ,WAC9B,KAAK,MAAM,QAAQ;EACjB,MAAM,YAAY,IAAI,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC;EAC9D,OAAO,UAAU,SAAS,WAAW,UAAU,KAAK,KAAA;CACtD,CAAC,CACH;CAEA,MAAM,aAAa,OAAO,OAAO,IAAI;EAEnC,YAAY,2BAA2B,SAAS,UAAU;GACxD,QAAQ;GACR,OAAO,eAAe,iBAAiB,UAAU,WAAW,OAAO;GACnE,cAAc,UAAU,cAAc;GACtC,eAAe,UACb,UAAU,OAAO,eAAe,kBAAkB;IAChD,QAAQ,MAAM;IACd,UAAU,KAAK,YAAY,SAAS,QAAQ;IAC5C,cAAc,iBAAiB,MAAM,GAAG,OAAO;GACjD,CAAC;EACL,CAAC;EAGD,aAAa,gBAAgB;GAC3B,IAAI;GACJ,QAAQ,SAAU,SAAS,WAAW,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,IAAyB,IAAI,OAAO,KAAK;GAC7G,YAAY,UAAU,QAAQ;IAC5B,MAAM,KAAK,IAAI,YAAY,QAAQ;IACnC,IAAI,CAAC,IACH,OAAO,OAAO,QAAQ,CAAC,CAAC;IAG1B,MAAM,QADW,IAAI,IAAI,aAAa,UAAU,OAAO,CACzC,CAAA,CACX,KAAK,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAA,CAC1B,QAAQ,SAA4C,aAAa,WAAW,IAAI,CAAC;IACpF,OAAO,OAAO,QACZ,MACG,KAAK,SAAS,QAAQ,WAAW;KAAE;KAAK;KAAI,QAAQ;IAAK,CAAC,CAAC,CAAA,CAC3D,QAAQ,SAA2C,SAAS,IAAI,CACrE;GACF;EACF,CAAC;EAGD,aAAa,gBAAgB;GAC3B,IAAI;GACJ,QAAQ,SACN,SAAS,WAAW,KAAK,IAAI,IACzB,OAAO,KAAK;IAAE,UAAU,KAAK;IAA2B,QAAQ,KAAK;GAAG,CAAC,IACzE,OAAO,KAAK;GAClB,YAAY,SAAS,QAAQ;IAC3B,MAAM,EAAE,UAAU,WAAW;IAC7B,MAAM,SAAS,IAAI,WAAW,MAAM,CAAC;IACrC,IAAI;IACJ,IAAI,QACF,KAAK,MAAM,OAAO,SAAS,OAAO;KAChC,MAAM,WAAW,IAAI,IAAI,IAAI;KAC7B,IAAI,UAAU,OAAO,QAAQ;MAC3B,OAAO;MACP;KACF;IACF;IAEF,OAAO,OAAO,QAAQ,CACpB,QAAQ,cAAc;KACpB,SAAS;KACT,OAAO,CAAC,wBAAwB,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC;KACxD,MAAM;KACN,MAAM;IACR,CAAC,CACH,CAAC;GACH;EACF,CAAC;EAGD,aAAa,gBAAgB;GAC3B,IAAI;GACJ,QAAQ,SACN,aAAa,WAAW,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,IAAiC,IAAI,OAAO,KAAK;GACzG,UAAU,SACR,OAAO,QAAQ,CACb;IACE,IAAI;IACJ,YACE,UAAU,OACR,cAAc,UACd,EAAE,MAAM,IAAI,KAAK,IAAI,EAAE,GACvB,EAAE,SAAS,IAAI,YAAY,IAAI,CAAC,EAAE,QAAQ,CAC5C;IACF,YAAY;KACV,OAAO,CAAC,mBAAmB,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC;KACnD,MAAM;KACN,aAAa;IACf;GACF,CACF,CAAC;EACL,CAAC;CACH,CAAC;CAED,OAAO,WAAW,YAAY,gBAAgB,iBAAiB,UAAU;AAC3E,CAAC,CACH"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { _ as hasTag, g as findSystemTagUri, t as ClearMagazine } from "./chunk-types.mjs";
|
|
2
|
+
import { Database, Obj, Ref } from "@dxos/echo";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { Operation } from "@dxos/compute";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import { EID } from "@dxos/keys";
|
|
7
|
+
//#region src/operations/clear-magazine.ts
|
|
8
|
+
/**
|
|
9
|
+
* Clears a Magazine's curated posts, preserving any that are starred. With no `starred` tag in the
|
|
10
|
+
* space nothing can be starred, so the whole list is emptied without resolving any posts; otherwise
|
|
11
|
+
* each post (and its distinct source Subscription) is resolved and the starred ones are kept.
|
|
12
|
+
*/
|
|
13
|
+
var handler = ClearMagazine.pipe(Operation.withHandler(Effect.fn(function* ({ magazine: magazineRef }) {
|
|
14
|
+
const magazine = yield* Database.load(magazineRef);
|
|
15
|
+
const db = Obj.getDatabase(magazine);
|
|
16
|
+
if (!db || magazine.posts.length === 0) return;
|
|
17
|
+
const starredUri = yield* Effect.promise(() => findSystemTagUri(db, "starred"));
|
|
18
|
+
const next = yield* Option.fromNullable(starredUri).pipe(Option.match({
|
|
19
|
+
onNone: () => Effect.succeed([]),
|
|
20
|
+
onSome: (uri) => Effect.gen(function* () {
|
|
21
|
+
const loaded = yield* Effect.all(magazine.posts.map((ref) => Database.load(ref)), { concurrency: "unbounded" });
|
|
22
|
+
const sourceById = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const post of loaded) if (post.source) sourceById.set(EID.getEntityId(EID.parse(post.source.uri)) ?? post.source.uri, post.source);
|
|
24
|
+
const subscriptionEntries = yield* Effect.all([...sourceById].map(([entityId, source]) => Database.load(source).pipe(Effect.tap((subscription) => Database.load(subscription.tags)), Effect.map((subscription) => [entityId, subscription]))), { concurrency: "unbounded" });
|
|
25
|
+
const subscriptionById = new Map(subscriptionEntries);
|
|
26
|
+
return loaded.filter((post) => hasTag(post.source ? subscriptionById.get(EID.getEntityId(EID.parse(post.source.uri)) ?? post.source.uri) : void 0, post.id, uri)).map((post) => Ref.make(post));
|
|
27
|
+
})
|
|
28
|
+
}));
|
|
29
|
+
if (next.length === magazine.posts.length) return;
|
|
30
|
+
Obj.update(magazine, (magazine) => {
|
|
31
|
+
magazine.posts = next;
|
|
32
|
+
});
|
|
33
|
+
})));
|
|
34
|
+
//#endregion
|
|
35
|
+
export { handler as default };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=chunk-clear-magazine.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-clear-magazine.mjs","names":[],"sources":["../../src/operations/clear-magazine.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\n\nimport { Operation } from '@dxos/compute';\nimport { Database, Obj, Ref } from '@dxos/echo';\nimport { EID } from '@dxos/keys';\n\nimport { FeedOperation, Subscription } from '../types';\n\n/**\n * Clears a Magazine's curated posts, preserving any that are starred. With no `starred` tag in the\n * space nothing can be starred, so the whole list is emptied without resolving any posts; otherwise\n * each post (and its distinct source Subscription) is resolved and the starred ones are kept.\n */\nconst handler: Operation.WithHandler<typeof FeedOperation.ClearMagazine> = FeedOperation.ClearMagazine.pipe(\n Operation.withHandler(\n Effect.fn(function* ({ magazine: magazineRef }) {\n const magazine = yield* Database.load(magazineRef);\n const db = Obj.getDatabase(magazine);\n if (!db || magazine.posts.length === 0) {\n return;\n }\n const starredUri = yield* Effect.promise(() => Subscription.findSystemTagUri(db, 'starred'));\n\n const next = yield* Option.fromNullable(starredUri).pipe(\n Option.match({\n onNone: () => Effect.succeed<Ref.Ref<Subscription.Post>[]>([]),\n onSome: (uri) =>\n Effect.gen(function* () {\n // Resolve posts concurrently — `Effect.all` defaults to sequential.\n const loaded = yield* Effect.all(\n magazine.posts.map((ref) => Database.load(ref)),\n { concurrency: 'unbounded' },\n );\n // A post's starred flag lives on its Subscription's TagIndex; many posts share one\n // Subscription (a feed has many posts), so resolve each distinct source once.\n const sourceById = new Map<string, Ref.Ref<Subscription.Subscription>>();\n for (const post of loaded) {\n if (post.source) {\n sourceById.set(EID.getEntityId(EID.parse(post.source.uri)) ?? post.source.uri, post.source);\n }\n }\n const subscriptionEntries = yield* Effect.all(\n [...sourceById].map(([entityId, source]) =>\n Database.load(source).pipe(\n // Resolve the child tag index so `hasTag` can read it synchronously below.\n Effect.tap((subscription) => Database.load(subscription.tags)),\n Effect.map((subscription) => [entityId, subscription] as const),\n ),\n ),\n { concurrency: 'unbounded' },\n );\n const subscriptionById = new Map(subscriptionEntries);\n return loaded\n .filter((post) =>\n Subscription.hasTag(\n post.source\n ? subscriptionById.get(EID.getEntityId(EID.parse(post.source.uri)) ?? post.source.uri)\n : undefined,\n post.id,\n uri,\n ),\n )\n .map((post) => Ref.make(post));\n }),\n }),\n );\n\n if (next.length === magazine.posts.length) {\n return;\n }\n Obj.update(magazine, (magazine) => {\n magazine.posts = next;\n });\n }),\n ),\n);\n\nexport default handler;\n"],"mappings":";;;;;;;;;;;;AAkBA,IAAM,UAAA,cAAiG,KACrG,UAAU,YACR,OAAO,GAAG,WAAW,EAAE,UAAU,eAAe;CAC9C,MAAM,WAAW,OAAO,SAAS,KAAK,WAAW;CACjD,MAAM,KAAK,IAAI,YAAY,QAAQ;CACnC,IAAI,CAAC,MAAM,SAAS,MAAM,WAAW,GACnC;CAEF,MAAM,aAAa,OAAO,OAAO,cAAc,iBAA8B,IAAI,SAAS,CAAC;CAE3F,MAAM,OAAO,OAAO,OAAO,aAAa,UAAU,CAAC,CAAC,KAClD,OAAO,MAAM;EACX,cAAc,OAAO,QAAsC,CAAC,CAAC;EAC7D,SAAS,QACP,OAAO,IAAI,aAAa;GAEtB,MAAM,SAAS,OAAO,OAAO,IAC3B,SAAS,MAAM,KAAK,QAAQ,SAAS,KAAK,GAAG,CAAC,GAC9C,EAAE,aAAa,YAAY,CAC7B;GAGA,MAAM,6BAAa,IAAI,IAAgD;GACvE,KAAK,MAAM,QAAQ,QACjB,IAAI,KAAK,QACP,WAAW,IAAI,IAAI,YAAY,IAAI,MAAM,KAAK,OAAO,GAAG,CAAC,KAAK,KAAK,OAAO,KAAK,KAAK,MAAM;GAG9F,MAAM,sBAAsB,OAAO,OAAO,IACxC,CAAC,GAAG,UAAU,CAAC,CAAC,KAAK,CAAC,UAAU,YAC9B,SAAS,KAAK,MAAM,CAAC,CAAC,KAEpB,OAAO,KAAK,iBAAiB,SAAS,KAAK,aAAa,IAAI,CAAC,GAC7D,OAAO,KAAK,iBAAiB,CAAC,UAAU,YAAY,CAAU,CAChE,CACF,GACA,EAAE,aAAa,YAAY,CAC7B;GACA,MAAM,mBAAmB,IAAI,IAAI,mBAAmB;GACpD,OAAO,OACJ,QAAQ,SACP,OACE,KAAK,SACD,iBAAiB,IAAI,IAAI,YAAY,IAAI,MAAM,KAAK,OAAO,GAAG,CAAC,KAAK,KAAK,OAAO,GAAG,IACnF,KAAA,GACJ,KAAK,IACL,GACF,CACF,CAAA,CACC,KAAK,SAAS,IAAI,KAAK,IAAI,CAAC;EACjC,CAAC;CACL,CAAC,CACH;CAEA,IAAI,KAAK,WAAW,SAAS,MAAM,QACjC;CAEF,IAAI,OAAO,WAAW,aAAa;EACjC,SAAS,QAAQ;CACnB,CAAC;AACH,CAAC,CACH,CACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { t as getMagazinesPath } from "./chunk-paths.mjs";
|
|
2
|
+
import { i as fetchRss, n as listStandardSitePublications, o as browserCorsProxy, r as searchStandardSiteHandles } from "./chunk-sources.mjs";
|
|
3
|
+
import { Capability } from "@dxos/app-framework";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import { Obj, Ref, Type } from "@dxos/echo";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import { Operation } from "@dxos/compute";
|
|
8
|
+
import { CreateSubscription, FeedOperation, Magazine, Subscription } from "#types";
|
|
9
|
+
import { SpaceCapabilities, SpaceOperation } from "@dxos/plugin-space";
|
|
10
|
+
import { AutofillAnnotation, OptionsLookupAnnotation, autofill, optionsLookup } from "@dxos/react-ui-form";
|
|
11
|
+
//#region src/capabilities/create-object.ts
|
|
12
|
+
var StandardSiteCreate = Schema.Struct({
|
|
13
|
+
...CreateSubscription.StandardSiteCreateBase.fields,
|
|
14
|
+
handle: CreateSubscription.StandardSiteCreateBase.fields.handle.pipe(OptionsLookupAnnotation.set(optionsLookup()(["handle"], ({ handle }) => searchStandardSiteHandles(handle ?? "", { corsProxy: browserCorsProxy() }).pipe(Effect.map((suggestions) => suggestions.map((suggestion) => ({
|
|
15
|
+
value: suggestion.handle,
|
|
16
|
+
label: suggestion.handle,
|
|
17
|
+
secondaryLabel: suggestion.displayName
|
|
18
|
+
})))), { combobox: true }))),
|
|
19
|
+
publication: CreateSubscription.StandardSiteCreateBase.fields.publication.pipe(OptionsLookupAnnotation.set(optionsLookup()(["handle"], ({ handle }) => CreateSubscription.isHandle(handle) ? listStandardSitePublications(handle, { corsProxy: browserCorsProxy() }).pipe(Effect.map((publications) => publications.map((publication) => ({
|
|
20
|
+
value: publication.site,
|
|
21
|
+
label: publication.name ?? publication.url ?? publication.site
|
|
22
|
+
}))), Effect.orElseSucceed(() => [])) : Effect.succeed([]))))
|
|
23
|
+
});
|
|
24
|
+
var RssCreate = Schema.Struct({
|
|
25
|
+
...CreateSubscription.RssCreateBase.fields,
|
|
26
|
+
name: Schema.optional(Schema.String.pipe(AutofillAnnotation.set(autofill()(["url"], ({ url }) => CreateSubscription.isUrl(url) ? fetchRss(url, { corsProxy: browserCorsProxy() }).pipe(Effect.map((result) => result.feed.name), Effect.orElseSucceed(() => void 0)) : Effect.succeed(void 0)))).annotations({ title: "Name" }))
|
|
27
|
+
});
|
|
28
|
+
var CreateSubscriptionSchema = Schema.Union(StandardSiteCreate, RssCreate);
|
|
29
|
+
var create_object_default = Capability.makeModule(Effect.fnUntraced(function* () {
|
|
30
|
+
return [Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
|
|
31
|
+
id: Type.getTypename(Subscription.Subscription),
|
|
32
|
+
inputSchema: CreateSubscriptionSchema,
|
|
33
|
+
createObject: (props, options) => Effect.gen(function* () {
|
|
34
|
+
const object = CreateSubscription.makeSubscriptionFromCreate(props);
|
|
35
|
+
const result = yield* Operation.invoke(SpaceOperation.AddObject, {
|
|
36
|
+
object,
|
|
37
|
+
target: options.target,
|
|
38
|
+
targetNodeId: options.targetNodeId
|
|
39
|
+
});
|
|
40
|
+
if (object.url) yield* Operation.schedule(FeedOperation.SyncFeed, { feed: Ref.make(object) }, { spaceId: Obj.getDatabase(object)?.spaceId });
|
|
41
|
+
return result;
|
|
42
|
+
})
|
|
43
|
+
}), Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
|
|
44
|
+
id: Type.getTypename(Magazine.Magazine),
|
|
45
|
+
inputSchema: Magazine.CreateMagazineSchema,
|
|
46
|
+
createObject: (props, options) => Effect.gen(function* () {
|
|
47
|
+
const magazine = Magazine.make(props);
|
|
48
|
+
return yield* Operation.invoke(SpaceOperation.AddObject, {
|
|
49
|
+
object: magazine,
|
|
50
|
+
target: options.target,
|
|
51
|
+
targetNodeId: getMagazinesPath(options.db.spaceId)
|
|
52
|
+
});
|
|
53
|
+
})
|
|
54
|
+
})];
|
|
55
|
+
}));
|
|
56
|
+
//#endregion
|
|
57
|
+
export { create_object_default as default };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=chunk-create-object.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-create-object.mjs","names":[],"sources":["../../src/capabilities/create-object.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { Capability } from '@dxos/app-framework';\nimport { Operation } from '@dxos/compute';\nimport { Obj, Ref, Type } from '@dxos/echo';\nimport { SpaceOperation } from '@dxos/plugin-space';\nimport { SpaceCapabilities } from '@dxos/plugin-space';\nimport { AutofillAnnotation, OptionsLookupAnnotation, autofill, optionsLookup } from '@dxos/react-ui-form';\n\nimport { CreateSubscription, FeedOperation, Magazine, Subscription } from '#types';\n\nimport {\n browserCorsProxy,\n fetchRss,\n listStandardSitePublications,\n searchStandardSiteHandles,\n} from '../operations/sources';\nimport { getMagazinesPath } from '../paths';\n\nconst StandardSiteCreate = Schema.Struct({\n ...CreateSubscription.StandardSiteCreateBase.fields,\n // Handle is a combobox: typing queries known handles (typeahead); the typed text stays selectable.\n handle: CreateSubscription.StandardSiteCreateBase.fields.handle.pipe(\n OptionsLookupAnnotation.set(\n optionsLookup<CreateSubscription.StandardSiteValues>()(\n ['handle'],\n ({ handle }) =>\n searchStandardSiteHandles(handle ?? '', { corsProxy: browserCorsProxy() }).pipe(\n Effect.map((suggestions) =>\n suggestions.map((suggestion) => ({\n value: suggestion.handle,\n label: suggestion.handle,\n secondaryLabel: suggestion.displayName,\n })),\n ),\n ),\n { combobox: true },\n ),\n ),\n ),\n // Publication options are looked up from the entered `handle`.\n publication: CreateSubscription.StandardSiteCreateBase.fields.publication.pipe(\n OptionsLookupAnnotation.set(\n optionsLookup<CreateSubscription.StandardSiteValues>()(['handle'], ({ handle }) =>\n CreateSubscription.isHandle(handle)\n ? listStandardSitePublications(handle, { corsProxy: browserCorsProxy() }).pipe(\n Effect.map((publications) =>\n publications.map((publication) => ({\n value: publication.site,\n label: publication.name ?? publication.url ?? publication.site,\n })),\n ),\n Effect.orElseSucceed(() => []),\n )\n : Effect.succeed([]),\n ),\n ),\n ),\n});\n\nconst RssCreate = Schema.Struct({\n ...CreateSubscription.RssCreateBase.fields,\n // Name is pre-filled from the feed title once the `url` is a valid feed URL.\n name: Schema.optional(\n Schema.String.pipe(\n AutofillAnnotation.set(\n autofill<CreateSubscription.RssValues>()(['url'], ({ url }) =>\n CreateSubscription.isUrl(url)\n ? fetchRss(url, { corsProxy: browserCorsProxy() }).pipe(\n Effect.map((result) => result.feed.name),\n Effect.orElseSucceed(() => undefined),\n )\n : Effect.succeed(undefined),\n ),\n ),\n ).annotations({ title: 'Name' }),\n ),\n});\n\nconst CreateSubscriptionSchema = Schema.Union(StandardSiteCreate, RssCreate);\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n return [\n Capability.contributes(SpaceCapabilities.CreateObjectEntry, {\n id: Type.getTypename(Subscription.Subscription),\n inputSchema: CreateSubscriptionSchema,\n createObject: (props, options) =>\n Effect.gen(function* () {\n const object = CreateSubscription.makeSubscriptionFromCreate(props);\n const result = yield* Operation.invoke(SpaceOperation.AddObject, {\n object,\n target: options.target,\n targetNodeId: options.targetNodeId,\n });\n // Auto-sync after creation if URL is provided.\n if (object.url) {\n yield* Operation.schedule(\n FeedOperation.SyncFeed,\n { feed: Ref.make(object) },\n { spaceId: Obj.getDatabase(object)?.spaceId },\n );\n }\n return result;\n }),\n }),\n Capability.contributes(SpaceCapabilities.CreateObjectEntry, {\n id: Type.getTypename(Magazine.Magazine),\n inputSchema: Magazine.CreateMagazineSchema,\n createObject: (props, options) =>\n Effect.gen(function* () {\n const magazine = Magazine.make(props);\n return yield* Operation.invoke(SpaceOperation.AddObject, {\n object: magazine,\n target: options.target,\n targetNodeId: getMagazinesPath(options.db.spaceId),\n });\n }),\n }),\n ];\n }),\n);\n"],"mappings":";;;;;;;;;;;AAwBA,IAAM,qBAAqB,OAAO,OAAO;CACvC,GAAG,mBAAmB,uBAAuB;CAE7C,QAAQ,mBAAmB,uBAAuB,OAAO,OAAO,KAC9D,wBAAwB,IACtB,cAAqD,CAAC,CACpD,CAAC,QAAQ,IACR,EAAE,aACD,0BAA0B,UAAU,IAAI,EAAE,WAAW,iBAAiB,EAAE,CAAC,CAAC,CAAC,KACzE,OAAO,KAAK,gBACV,YAAY,KAAK,gBAAgB;EAC/B,OAAO,WAAW;EAClB,OAAO,WAAW;EAClB,gBAAgB,WAAW;CAC7B,EAAE,CACJ,CACF,GACF,EAAE,UAAU,KAAK,CACnB,CACF,CACF;CAEA,aAAa,mBAAmB,uBAAuB,OAAO,YAAY,KACxE,wBAAwB,IACtB,cAAqD,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,aACpE,mBAAmB,SAAS,MAAM,IAC9B,6BAA6B,QAAQ,EAAE,WAAW,iBAAiB,EAAE,CAAC,CAAC,CAAC,KACtE,OAAO,KAAK,iBACV,aAAa,KAAK,iBAAiB;EACjC,OAAO,YAAY;EACnB,OAAO,YAAY,QAAQ,YAAY,OAAO,YAAY;CAC5D,EAAE,CACJ,GACA,OAAO,oBAAoB,CAAC,CAAC,CAC/B,IACA,OAAO,QAAQ,CAAC,CAAC,CACvB,CACF,CACF;AACF,CAAC;AAED,IAAM,YAAY,OAAO,OAAO;CAC9B,GAAG,mBAAmB,cAAc;CAEpC,MAAM,OAAO,SACX,OAAO,OAAO,KACZ,mBAAmB,IACjB,SAAuC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,UACnD,mBAAmB,MAAM,GAAG,IACxB,SAAS,KAAK,EAAE,WAAW,iBAAiB,EAAE,CAAC,CAAC,CAAC,KAC/C,OAAO,KAAK,WAAW,OAAO,KAAK,IAAI,GACvC,OAAO,oBAAoB,KAAA,CAAS,CACtC,IACA,OAAO,QAAQ,KAAA,CAAS,CAC9B,CACF,CACF,CAAC,CAAC,YAAY,EAAE,OAAO,OAAO,CAAC,CACjC;AACF,CAAC;AAED,IAAM,2BAA2B,OAAO,MAAM,oBAAoB,SAAS;AAE3E,IAAA,wBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,OAAO,CACL,WAAW,YAAY,kBAAkB,mBAAmB;EAC1D,IAAI,KAAK,YAAY,aAAa,YAAY;EAC9C,aAAa;EACb,eAAe,OAAO,YACpB,OAAO,IAAI,aAAa;GACtB,MAAM,SAAS,mBAAmB,2BAA2B,KAAK;GAClE,MAAM,SAAS,OAAO,UAAU,OAAO,eAAe,WAAW;IAC/D;IACA,QAAQ,QAAQ;IAChB,cAAc,QAAQ;GACxB,CAAC;GAED,IAAI,OAAO,KACT,OAAO,UAAU,SACf,cAAc,UACd,EAAE,MAAM,IAAI,KAAK,MAAM,EAAE,GACzB,EAAE,SAAS,IAAI,YAAY,MAAM,CAAC,EAAE,QAAQ,CAC9C;GAEF,OAAO;EACT,CAAC;CACL,CAAC,GACD,WAAW,YAAY,kBAAkB,mBAAmB;EAC1D,IAAI,KAAK,YAAY,SAAS,QAAQ;EACtC,aAAa,SAAS;EACtB,eAAe,OAAO,YACpB,OAAO,IAAI,aAAa;GACtB,MAAM,WAAW,SAAS,KAAK,KAAK;GACpC,OAAO,OAAO,UAAU,OAAO,eAAe,WAAW;IACvD,QAAQ;IACR,QAAQ,QAAQ;IAChB,cAAc,iBAAiB,QAAQ,GAAG,OAAO;GACnD,CAAC;EACH,CAAC;CACL,CAAC,CACH;AACF,CAAC,CACH"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { _ as hasTag, d as Post, g as findSystemTagUri, l as patchPostState, n as CurateMagazine, o as SyncFeed, s as CurationOutput } from "./chunk-types.mjs";
|
|
2
|
+
import { n as publishedTimestamp } from "./chunk-date.mjs";
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
import { Database, Filter, Obj, Query, Ref } from "@dxos/echo";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import { Operation } from "@dxos/compute";
|
|
7
|
+
import * as Option from "effect/Option";
|
|
8
|
+
import { log } from "@dxos/log";
|
|
9
|
+
import { RunInstructions } from "@dxos/assistant-toolkit";
|
|
10
|
+
//#region src/operations/util.ts
|
|
11
|
+
/**
|
|
12
|
+
* Resolves the Magazine's referenced feeds and the Posts in each feed's backing
|
|
13
|
+
* ECHO queue, filtering out Posts already curated into `magazine.posts` (and cross-feed duplicates).
|
|
14
|
+
*/
|
|
15
|
+
var collectCandidates = (magazine) => Effect.gen(function* () {
|
|
16
|
+
const seen = new Set(magazine.posts.map((ref) => ref.target?.id ?? ref.uri));
|
|
17
|
+
const candidates = [];
|
|
18
|
+
for (const feedRef of magazine.feeds) {
|
|
19
|
+
const feed = yield* Database.load(feedRef);
|
|
20
|
+
const echoFeed = feed.feed?.target;
|
|
21
|
+
if (!echoFeed) continue;
|
|
22
|
+
const posts = yield* Database.query(Query.select(Filter.type(Post)).from(echoFeed)).run;
|
|
23
|
+
for (const post of posts) {
|
|
24
|
+
if (seen.has(post.id)) continue;
|
|
25
|
+
seen.add(post.id);
|
|
26
|
+
candidates.push({
|
|
27
|
+
post,
|
|
28
|
+
feed
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return candidates;
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Partitions posts into those to keep and those to drop, given a maximum
|
|
36
|
+
* non-starred retention bound. Starred posts are always kept; the remaining
|
|
37
|
+
* non-starred posts are sorted newest-first by `published` and the top
|
|
38
|
+
* `keep` retained.
|
|
39
|
+
*
|
|
40
|
+
* `isStarred` is injectable; callers with a starred-tag uri pass a predicate backed by it
|
|
41
|
+
* (defaults to treating nothing as starred).
|
|
42
|
+
*/
|
|
43
|
+
var partitionByKeepBound = (posts, keep, isStarred = () => false) => {
|
|
44
|
+
const kept = [];
|
|
45
|
+
const candidates = [];
|
|
46
|
+
for (const post of posts) if (isStarred(post)) kept.push(post);
|
|
47
|
+
else candidates.push(post);
|
|
48
|
+
candidates.sort((a, b) => publishedTimestamp(b.published) - publishedTimestamp(a.published));
|
|
49
|
+
const retained = candidates.slice(0, Math.max(0, keep));
|
|
50
|
+
const dropped = candidates.slice(Math.max(0, keep));
|
|
51
|
+
return {
|
|
52
|
+
kept: [...kept, ...retained],
|
|
53
|
+
dropped
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/operations/curate-magazine.ts
|
|
58
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-magazine/src/operations/curate-magazine.ts";
|
|
59
|
+
var curate_magazine_default = CurateMagazine.pipe(Operation.withHandler(Effect.fnUntraced(function* ({ magazine: magazineRef }) {
|
|
60
|
+
const magazine = yield* Effect.promise(() => magazineRef.load());
|
|
61
|
+
const synced = yield* syncFeeds(yield* loadValidFeeds(magazine));
|
|
62
|
+
const candidates = yield* collectCandidates(magazine);
|
|
63
|
+
const spaceId = Obj.getDatabase(magazine)?.spaceId;
|
|
64
|
+
const selected = resolveSelected(candidates, candidates.length > 0 && spaceId ? yield* selectPostIds(magazine, candidates, spaceId) : []);
|
|
65
|
+
const db = Obj.getDatabase(magazine);
|
|
66
|
+
const starredUri = db ? yield* Effect.promise(() => findSystemTagUri(db, "starred")) : void 0;
|
|
67
|
+
const nextPosts = applyKeep([...magazine.posts, ...selected.map(({ post }) => Ref.make(post))], magazine.keep ?? 10, starredUri);
|
|
68
|
+
const curated = selected.length;
|
|
69
|
+
if (nextPosts.length !== magazine.posts.length || nextPosts.some((ref, index) => ref.uri !== magazine.posts[index]?.uri)) Obj.update(magazine, (magazine) => {
|
|
70
|
+
magazine.posts = nextPosts;
|
|
71
|
+
});
|
|
72
|
+
for (const { post, snippet, imageUrl } of selected) if (snippet || imageUrl) patchPostState(magazine, post.id, {
|
|
73
|
+
snippet,
|
|
74
|
+
imageUrl
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
synced,
|
|
78
|
+
curated
|
|
79
|
+
};
|
|
80
|
+
})), Operation.opaqueHandler);
|
|
81
|
+
/** Bound on concurrent feed syncs. */
|
|
82
|
+
var SYNC_CONCURRENCY = 8;
|
|
83
|
+
/** Loads each referenced feed (and its backing ECHO feed), tolerating individual failures, keeping only syncable feeds. */
|
|
84
|
+
var loadValidFeeds = (magazine) => Effect.forEach(magazine.feeds, (ref) => Effect.gen(function* () {
|
|
85
|
+
const feed = yield* Database.load(ref);
|
|
86
|
+
if (feed.feed) yield* Database.load(feed.feed);
|
|
87
|
+
return feed;
|
|
88
|
+
}).pipe(Effect.tapError((error) => Effect.sync(() => log.catch(error, void 0, {
|
|
89
|
+
"~LogMeta": "~LogMeta",
|
|
90
|
+
F: __dxlog_file,
|
|
91
|
+
L: 79,
|
|
92
|
+
S: void 0
|
|
93
|
+
}))), Effect.option)).pipe(Effect.map((feeds) => feeds.flatMap(Option.toArray).filter((feed) => Boolean(feed.url))));
|
|
94
|
+
/** Syncs all feeds in parallel, tolerating per-feed failures; resolves to the count synced successfully. */
|
|
95
|
+
var syncFeeds = (validFeeds) => Effect.forEach(validFeeds, (feed) => Operation.invoke(SyncFeed, { feed: Ref.make(feed) }, { spaceId: Obj.getDatabase(feed)?.spaceId }).pipe(Effect.as(true), Effect.catchAll((error) => Effect.sync(() => (log.catch(error, { feedUrl: feed.url }, {
|
|
96
|
+
"~LogMeta": "~LogMeta",
|
|
97
|
+
F: __dxlog_file,
|
|
98
|
+
L: 95,
|
|
99
|
+
S: void 0
|
|
100
|
+
}), false)))), { concurrency: SYNC_CONCURRENCY }).pipe(Effect.map((results) => results.filter(Boolean).length));
|
|
101
|
+
/**
|
|
102
|
+
* Runs the curation agent over the candidate summaries and resolves to the selected Post entries.
|
|
103
|
+
* The magazine's persisted Instructions (created with the magazine) carries the editorial brief and
|
|
104
|
+
* references the Magazine skill, which RunInstructions resolves at run time. No instructions → no selection.
|
|
105
|
+
* Tolerates agent/parse failures (logs → no selection).
|
|
106
|
+
*/
|
|
107
|
+
var selectPostIds = (magazine, candidates, spaceId) => Effect.gen(function* () {
|
|
108
|
+
if (!magazine.instructions) return [];
|
|
109
|
+
const input = { candidates: candidates.map(({ post, feed }) => ({
|
|
110
|
+
id: post.id,
|
|
111
|
+
feedName: feed.name,
|
|
112
|
+
title: post.title,
|
|
113
|
+
description: post.description,
|
|
114
|
+
author: post.author,
|
|
115
|
+
published: post.published,
|
|
116
|
+
link: post.link
|
|
117
|
+
})) };
|
|
118
|
+
return yield* Operation.invoke(RunInstructions, {
|
|
119
|
+
instructions: magazine.instructions,
|
|
120
|
+
input
|
|
121
|
+
}, { spaceId }).pipe(Effect.flatMap(Schema.decodeUnknown(CurationOutput)), Effect.map((output) => output.posts), Effect.catchAll((error) => Effect.sync(() => {
|
|
122
|
+
log.warn("curation selection failed", { error }, {
|
|
123
|
+
"~LogMeta": "~LogMeta",
|
|
124
|
+
F: __dxlog_file,
|
|
125
|
+
L: 132,
|
|
126
|
+
S: this
|
|
127
|
+
});
|
|
128
|
+
return [];
|
|
129
|
+
})));
|
|
130
|
+
});
|
|
131
|
+
/** Resolves the agent's selected entries back to candidate Posts, preserving order and dropping unknown/duplicate ids. */
|
|
132
|
+
var resolveSelected = (candidates, entries) => {
|
|
133
|
+
const byId = new Map(candidates.map(({ post }) => [post.id, post]));
|
|
134
|
+
const seen = /* @__PURE__ */ new Set();
|
|
135
|
+
const selected = [];
|
|
136
|
+
for (const { id, snippet, imageUrl } of entries) {
|
|
137
|
+
const post = byId.get(id);
|
|
138
|
+
if (post && !seen.has(id)) {
|
|
139
|
+
seen.add(id);
|
|
140
|
+
selected.push({
|
|
141
|
+
post,
|
|
142
|
+
snippet,
|
|
143
|
+
imageUrl
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return selected;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Bounds a curated posts ref list to `keep` total (non-starred) posts: keeps all starred posts and
|
|
151
|
+
* unresolved refs, plus the `keep` newest non-starred by published date. Pure; returns the retained
|
|
152
|
+
* refs. Delegates the sort/slice/starred partition to {@link partitionByKeepBound}.
|
|
153
|
+
*/
|
|
154
|
+
var applyKeep = (posts, keep, starredUri) => {
|
|
155
|
+
const isStarred = (post) => hasTag(post.source?.target, post.id, starredUri);
|
|
156
|
+
const resolved = posts.map((ref) => ref.target).filter((post) => post !== void 0);
|
|
157
|
+
const unresolved = posts.filter((ref) => !ref.target);
|
|
158
|
+
const { kept } = partitionByKeepBound(resolved, keep, isStarred);
|
|
159
|
+
return [...kept.map((post) => Ref.make(post)), ...unresolved];
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { applyKeep, curate_magazine_default as default, resolveSelected };
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=chunk-curate-magazine.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-curate-magazine.mjs","names":[],"sources":["../../src/operations/util.ts","../../src/operations/curate-magazine.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Database, Filter, Query } from '@dxos/echo';\n\nimport { type Magazine, Subscription } from '../types';\nimport { publishedTimestamp } from '../util/date';\n\n/**\n * Resolves the Magazine's referenced feeds and the Posts in each feed's backing\n * ECHO queue, filtering out Posts already curated into `magazine.posts` (and cross-feed duplicates).\n */\nexport const collectCandidates = (magazine: Magazine.Magazine) =>\n Effect.gen(function* () {\n const seen = new Set(magazine.posts.map((ref) => ref.target?.id ?? ref.uri));\n const candidates: Array<{ post: Subscription.Post; feed: Subscription.Subscription }> = [];\n for (const feedRef of magazine.feeds) {\n const feed = yield* Database.load(feedRef);\n const echoFeed = feed.feed?.target;\n if (!echoFeed) {\n continue;\n }\n const posts = yield* Database.query(Query.select(Filter.type(Subscription.Post)).from(echoFeed)).run;\n for (const post of posts) {\n if (seen.has(post.id)) {\n continue;\n }\n seen.add(post.id);\n candidates.push({ post, feed });\n }\n }\n return candidates;\n });\n\n/**\n * Partitions posts into those to keep and those to drop, given a maximum\n * non-starred retention bound. Starred posts are always kept; the remaining\n * non-starred posts are sorted newest-first by `published` and the top\n * `keep` retained.\n *\n * `isStarred` is injectable; callers with a starred-tag uri pass a predicate backed by it\n * (defaults to treating nothing as starred).\n */\nexport const partitionByKeepBound = <T extends Subscription.Post>(\n posts: readonly T[],\n keep: number,\n isStarred: (post: T) => boolean = () => false,\n): { kept: T[]; dropped: T[] } => {\n const kept: T[] = [];\n const candidates: T[] = [];\n for (const post of posts) {\n if (isStarred(post)) {\n kept.push(post);\n } else {\n candidates.push(post);\n }\n }\n candidates.sort((a, b) => publishedTimestamp(b.published) - publishedTimestamp(a.published));\n const retained = candidates.slice(0, Math.max(0, keep));\n const dropped = candidates.slice(Math.max(0, keep));\n return { kept: [...kept, ...retained], dropped };\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\nimport * as Option from 'effect/Option';\nimport * as Schema from 'effect/Schema';\n\nimport { RunInstructions } from '@dxos/assistant-toolkit';\nimport { Operation } from '@dxos/compute';\nimport { Database, Obj, Ref } from '@dxos/echo';\nimport { type EntityId, type SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\n\nimport { FeedOperation, Magazine, Subscription } from '../types';\nimport { collectCandidates, partitionByKeepBound } from './util';\n\nexport default FeedOperation.CurateMagazine.pipe(\n Operation.withHandler(\n Effect.fnUntraced(function* ({ magazine: magazineRef }) {\n const magazine = yield* Effect.promise(() => magazineRef.load());\n\n const validFeeds = yield* loadValidFeeds(magazine);\n const synced = yield* syncFeeds(validFeeds);\n\n // Select matching Posts via the agent (single-shot structured output), then add them mechanically.\n const candidates = yield* collectCandidates(magazine);\n const spaceId = Obj.getDatabase(magazine)?.spaceId;\n const selectedEntries =\n candidates.length > 0 && spaceId ? yield* selectPostIds(magazine, candidates, spaceId) : [];\n const selected = resolveSelected(candidates, selectedEntries);\n\n // Build the next posts list as a pure function of (existing curated + newly selected), bounded\n // by the magazine's `keep`, then commit it in one update. collectCandidates already excludes\n // posts already curated, so the additions are simply appended (resolveSelected deduped them).\n const db = Obj.getDatabase(magazine);\n const starredUri = db ? yield* Effect.promise(() => Subscription.findSystemTagUri(db, 'starred')) : undefined;\n const merged = [...magazine.posts, ...selected.map(({ post }) => Ref.make(post))];\n const nextPosts = applyKeep(merged, magazine.keep ?? Subscription.DEFAULT_KEEP, starredUri);\n const curated = selected.length;\n\n const changed =\n nextPosts.length !== magazine.posts.length ||\n nextPosts.some((ref, index) => ref.uri !== magazine.posts[index]?.uri);\n if (changed) {\n Obj.update(magazine, (magazine) => {\n magazine.posts = nextPosts;\n });\n }\n\n // Write agent-generated snippet/imageUrl into per-post magazine state.\n for (const { post, snippet, imageUrl } of selected) {\n if (snippet || imageUrl) {\n Magazine.patchPostState(magazine, post.id as EntityId, { snippet, imageUrl });\n }\n }\n\n return { synced, curated };\n }),\n ),\n Operation.opaqueHandler,\n);\n\n// -- Helpers --\n\n/** Bound on concurrent feed syncs. */\nconst SYNC_CONCURRENCY = 8;\n\n/** Loads each referenced feed (and its backing ECHO feed), tolerating individual failures, keeping only syncable feeds. */\nconst loadValidFeeds = (magazine: Magazine.Magazine) =>\n Effect.forEach(magazine.feeds, (ref) =>\n Effect.gen(function* () {\n const feed = yield* Database.load(ref);\n if (feed.feed) {\n yield* Database.load(feed.feed); // Hydrate the backing ECHO feed.\n }\n return feed;\n }).pipe(\n Effect.tapError((error) => Effect.sync(() => log.catch(error))),\n Effect.option,\n ),\n ).pipe(Effect.map((feeds) => feeds.flatMap(Option.toArray).filter((feed) => Boolean(feed.url))));\n\n/** Syncs all feeds in parallel, tolerating per-feed failures; resolves to the count synced successfully. */\nconst syncFeeds = (validFeeds: readonly Subscription.Subscription[]) =>\n Effect.forEach(\n validFeeds,\n (feed) =>\n Operation.invoke(\n FeedOperation.SyncFeed,\n { feed: Ref.make(feed) },\n { spaceId: Obj.getDatabase(feed)?.spaceId },\n ).pipe(\n Effect.as(true),\n Effect.catchAll((error) => Effect.sync(() => (log.catch(error, { feedUrl: feed.url }), false))),\n ),\n { concurrency: SYNC_CONCURRENCY },\n ).pipe(Effect.map((results) => results.filter(Boolean).length));\n\n/**\n * Runs the curation agent over the candidate summaries and resolves to the selected Post entries.\n * The magazine's persisted Instructions (created with the magazine) carries the editorial brief and\n * references the Magazine skill, which RunInstructions resolves at run time. No instructions → no selection.\n * Tolerates agent/parse failures (logs → no selection).\n */\nconst selectPostIds = (\n magazine: Magazine.Magazine,\n candidates: ReadonlyArray<{ post: Subscription.Post; feed: Subscription.Subscription }>,\n spaceId: SpaceId,\n) =>\n Effect.gen(function* () {\n if (!magazine.instructions) {\n return [] as readonly (typeof Magazine.CurationOutput.Type.posts)[number][];\n }\n const input = {\n candidates: candidates.map(({ post, feed }) => ({\n id: post.id,\n feedName: feed.name,\n title: post.title,\n description: post.description,\n author: post.author,\n published: post.published,\n link: post.link,\n })),\n };\n\n return yield* Operation.invoke(RunInstructions, { instructions: magazine.instructions, input }, { spaceId }).pipe(\n Effect.flatMap(Schema.decodeUnknown(Magazine.CurationOutput)),\n Effect.map((output) => output.posts),\n Effect.catchAll((error) =>\n Effect.sync(() => {\n log.warn('curation selection failed', { error });\n return [] as readonly (typeof Magazine.CurationOutput.Type.posts)[number][];\n }),\n ),\n );\n });\n\ntype SelectedEntry = {\n post: Subscription.Post;\n snippet: string | undefined;\n imageUrl: string | undefined;\n};\n\n/** Resolves the agent's selected entries back to candidate Posts, preserving order and dropping unknown/duplicate ids. */\nexport const resolveSelected = (\n candidates: ReadonlyArray<{ post: Subscription.Post }>,\n entries: readonly { id: string; snippet?: string; imageUrl?: string }[],\n): SelectedEntry[] => {\n const byId = new Map(candidates.map(({ post }) => [post.id, post]));\n const seen = new Set<string>();\n const selected: SelectedEntry[] = [];\n for (const { id, snippet, imageUrl } of entries) {\n const post = byId.get(id);\n if (post && !seen.has(id)) {\n seen.add(id);\n selected.push({ post, snippet, imageUrl });\n }\n }\n return selected;\n};\n\n/**\n * Bounds a curated posts ref list to `keep` total (non-starred) posts: keeps all starred posts and\n * unresolved refs, plus the `keep` newest non-starred by published date. Pure; returns the retained\n * refs. Delegates the sort/slice/starred partition to {@link partitionByKeepBound}.\n */\nexport const applyKeep = (\n posts: readonly Ref.Ref<Subscription.Post>[],\n keep: number,\n starredUri: string | undefined,\n): Ref.Ref<Subscription.Post>[] => {\n const isStarred = (post: Subscription.Post) => Subscription.hasTag(post.source?.target, post.id, starredUri);\n const resolved = posts.map((ref) => ref.target).filter((post): post is Subscription.Post => post !== undefined);\n const unresolved = posts.filter((ref) => !ref.target);\n const { kept } = partitionByKeepBound(resolved, keep, isStarred);\n return [...kept.map((post) => Ref.make(post)), ...unresolved];\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/util/date.ts
|
|
2
|
+
/** Format an ISO 8601 date string for tile/reader display. Returns undefined for missing/invalid input. */
|
|
3
|
+
var formatDate = (iso) => {
|
|
4
|
+
if (!iso) return;
|
|
5
|
+
const date = new Date(iso);
|
|
6
|
+
if (Number.isNaN(date.getTime())) return;
|
|
7
|
+
return date.toLocaleDateString(void 0, {
|
|
8
|
+
year: "numeric",
|
|
9
|
+
month: "short",
|
|
10
|
+
day: "numeric"
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Sortable timestamp from an ISO date string (e.g. a Post's `published`); missing/unparseable
|
|
15
|
+
* values sort last (newest-first sorts treat them as `-Infinity`).
|
|
16
|
+
*/
|
|
17
|
+
var publishedTimestamp = (published) => {
|
|
18
|
+
if (!published) return Number.NEGATIVE_INFINITY;
|
|
19
|
+
const ms = Date.parse(published);
|
|
20
|
+
return Number.isNaN(ms) ? Number.NEGATIVE_INFINITY : ms;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { publishedTimestamp as n, formatDate as t };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=chunk-date.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-date.mjs","names":[],"sources":["../../src/util/date.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n/** Format an ISO 8601 date string for tile/reader display. Returns undefined for missing/invalid input. */\nexport const formatDate = (iso: string | undefined): string | undefined => {\n if (!iso) {\n return undefined;\n }\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return undefined;\n }\n return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n};\n\n/**\n * Sortable timestamp from an ISO date string (e.g. a Post's `published`); missing/unparseable\n * values sort last (newest-first sorts treat them as `-Infinity`).\n */\nexport const publishedTimestamp = (published: string | undefined): number => {\n if (!published) {\n return Number.NEGATIVE_INFINITY;\n }\n const ms = Date.parse(published);\n return Number.isNaN(ms) ? Number.NEGATIVE_INFINITY : ms;\n};\n"],"mappings":";;AAKA,IAAa,cAAc,QAAgD;CACzE,IAAI,CAAC,KACH;CAEF,MAAM,OAAO,IAAI,KAAK,GAAG;CACzB,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAC7B;CAEF,OAAO,KAAK,mBAAmB,KAAA,GAAW;EAAE,MAAM;EAAW,OAAO;EAAS,KAAK;CAAU,CAAC;AAC/F;;;;;AAMA,IAAa,sBAAsB,cAA0C;CAC3E,IAAI,CAAC,WACH,OAAO,OAAO;CAEhB,MAAM,KAAK,KAAK,MAAM,SAAS;CAC/B,OAAO,OAAO,MAAM,EAAE,IAAI,OAAO,oBAAoB;AACvD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { i as FetchArticleContent } from "./chunk-types.mjs";
|
|
2
|
+
import { a as fetchArticle, o as browserCorsProxy } from "./chunk-sources.mjs";
|
|
3
|
+
import { Database } from "@dxos/echo";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { Operation } from "@dxos/compute";
|
|
6
|
+
import { invariant } from "@dxos/invariant";
|
|
7
|
+
//#region src/operations/fetch-article-content.ts
|
|
8
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-magazine/src/operations/fetch-article-content.ts";
|
|
9
|
+
var handler = FetchArticleContent.pipe(Operation.withHandler(Effect.fn(function* ({ post: postRef }) {
|
|
10
|
+
const post = yield* Database.load(postRef);
|
|
11
|
+
invariant(post.link, "Post has no link.", {
|
|
12
|
+
"~LogMeta": "~LogMeta",
|
|
13
|
+
F: __dxlog_file,
|
|
14
|
+
L: 18,
|
|
15
|
+
S: this,
|
|
16
|
+
A: ["post.link", "'Post has no link.'"]
|
|
17
|
+
});
|
|
18
|
+
return yield* Effect.tryPromise({
|
|
19
|
+
try: () => fetchArticle(post.link, { corsProxy: browserCorsProxy() }),
|
|
20
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
21
|
+
});
|
|
22
|
+
})));
|
|
23
|
+
//#endregion
|
|
24
|
+
export { handler as default };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=chunk-fetch-article-content.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-fetch-article-content.mjs","names":[],"sources":["../../src/operations/fetch-article-content.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Operation } from '@dxos/compute';\nimport { Database } from '@dxos/echo';\nimport { invariant } from '@dxos/invariant';\n\nimport { FeedOperation } from '../types';\nimport { browserCorsProxy, fetchArticle } from './sources';\n\nconst handler: Operation.WithHandler<typeof FeedOperation.FetchArticleContent> = FeedOperation.FetchArticleContent.pipe(\n Operation.withHandler(\n Effect.fn(function* ({ post: postRef }) {\n const post = yield* Database.load(postRef);\n invariant(post.link, 'Post has no link.');\n return yield* Effect.tryPromise({\n try: () => fetchArticle(post.link!, { corsProxy: browserCorsProxy() }),\n catch: (error) => (error instanceof Error ? error : new Error(String(error))),\n });\n }),\n ),\n);\n\nexport default handler;\n"],"mappings":";;;;;;;;AAaA,IAAM,UAAA,oBAA6G,KACjH,UAAU,YACR,OAAO,GAAG,WAAW,EAAE,MAAM,WAAW;CACtC,MAAM,OAAO,OAAO,SAAS,KAAK,OAAO;CACzC,UAAU,KAAK,MAAM,qBAAkB;EAAA,YAAA;EAAA,GAAA;EAAA,GAAA;EAAA,GAAA;EAAA,GAAA,CAAA,aAAA,qBAAA;CAAA,CAAC;CACxC,OAAO,OAAO,OAAO,WAAW;EAC9B,WAAW,aAAa,KAAK,MAAO,EAAE,WAAW,iBAAiB,EAAE,CAAC;EACrE,QAAQ,UAAW,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;CAC7E,CAAC;AACH,CAAC,CACH,CACF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { a as LoadPostContent, h as findPostContent, m as appendPostContent } from "./chunk-types.mjs";
|
|
2
|
+
import { i as stripHtml, r as makeSnippet } from "./chunk-text.mjs";
|
|
3
|
+
import { a as fetchArticle, o as browserCorsProxy } from "./chunk-sources.mjs";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { Operation } from "@dxos/compute";
|
|
6
|
+
import { invariant } from "@dxos/invariant";
|
|
7
|
+
import { getSpace } from "@dxos/client/echo";
|
|
8
|
+
//#region src/operations/load-post-content.ts
|
|
9
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-magazine/src/operations/load-post-content.ts";
|
|
10
|
+
var load_post_content_default = LoadPostContent.pipe(Operation.withHandler(Effect.fn(function* ({ post: postRef, force }) {
|
|
11
|
+
const post = postRef.target;
|
|
12
|
+
invariant(post, "Post ref target not loaded.", {
|
|
13
|
+
"~LogMeta": "~LogMeta",
|
|
14
|
+
F: __dxlog_file,
|
|
15
|
+
L: 25,
|
|
16
|
+
S: this,
|
|
17
|
+
A: ["post", "'Post ref target not loaded.'"]
|
|
18
|
+
});
|
|
19
|
+
const subscription = post.source?.target;
|
|
20
|
+
if (!subscription || !post.link) return;
|
|
21
|
+
const space = getSpace(subscription);
|
|
22
|
+
invariant(space, "Subscription is not in a space.", {
|
|
23
|
+
"~LogMeta": "~LogMeta",
|
|
24
|
+
F: __dxlog_file,
|
|
25
|
+
L: 31,
|
|
26
|
+
S: this,
|
|
27
|
+
A: ["space", "'Subscription is not in a space.'"]
|
|
28
|
+
});
|
|
29
|
+
if (!force) {
|
|
30
|
+
if (yield* Effect.tryPromise(() => findPostContent(subscription, post))) return;
|
|
31
|
+
}
|
|
32
|
+
const inlineContent = post.content;
|
|
33
|
+
if (subscription.type === "standard-site" && inlineContent) {
|
|
34
|
+
yield* Effect.tryPromise(() => appendPostContent(space, subscription, {
|
|
35
|
+
post,
|
|
36
|
+
text: inlineContent,
|
|
37
|
+
snippet: makeSnippet(stripHtml(inlineContent))
|
|
38
|
+
}));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
yield* Effect.tryPromise({
|
|
42
|
+
try: async () => {
|
|
43
|
+
const { text, imageUrls } = await fetchArticle(post.link, { corsProxy: browserCorsProxy() });
|
|
44
|
+
if (text) await appendPostContent(space, subscription, {
|
|
45
|
+
post,
|
|
46
|
+
text,
|
|
47
|
+
snippet: makeSnippet(stripHtml(text)),
|
|
48
|
+
imageUrl: imageUrls[0]
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
52
|
+
});
|
|
53
|
+
})));
|
|
54
|
+
//#endregion
|
|
55
|
+
export { load_post_content_default as default };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=chunk-load-post-content.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-load-post-content.mjs","names":[],"sources":["../../src/operations/load-post-content.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { getSpace } from '@dxos/client/echo';\nimport { Operation } from '@dxos/compute';\nimport { invariant } from '@dxos/invariant';\n\nimport { FeedOperation, Subscription } from '../types';\nimport { makeSnippet, stripHtml } from '../util/text';\nimport { browserCorsProxy, fetchArticle } from './sources';\n\nexport default FeedOperation.LoadPostContent.pipe(\n Operation.withHandler(\n Effect.fn(function* ({ post: postRef, force }) {\n // The Post is a queue item (its source Subscription lives in space.db).\n // Resolve the source Subscription first; everything that needs a\n // Database / Space binding comes from there. The Post's own\n // `Obj.getDatabase` link isn't reliably set on every code path that\n // produces a queue-decoded proxy, but the Subscription is always in\n // space.db.\n const post = postRef.target;\n invariant(post, 'Post ref target not loaded.');\n const subscription = post.source?.target;\n if (!subscription || !post.link) {\n return;\n }\n const space = getSpace(subscription);\n invariant(space, 'Subscription is not in a space.');\n // Idempotent by default (first-open auto-load); `force` re-fetches for the reader's refresh.\n if (!force) {\n const existing = yield* Effect.tryPromise(() => Subscription.findPostContent(subscription, post));\n if (existing) {\n return;\n }\n }\n\n // Standard.site documents carry the full article body inline (`Post.content`), so the reader\n // renders offline without a second network round-trip. Gated on the feed type: RSS keeps the\n // HTTP fetch path because its `content:encoded` is frequently a truncated summary, not the full\n // body.\n const inlineContent = post.content;\n if (subscription.type === 'standard-site' && inlineContent) {\n yield* Effect.tryPromise(() =>\n Subscription.appendPostContent(space, subscription, {\n post,\n text: inlineContent,\n snippet: makeSnippet(stripHtml(inlineContent)),\n }),\n );\n return;\n }\n\n yield* Effect.tryPromise({\n try: async () => {\n // In the browser, route through the dev-server CORS proxy. Server-side callers\n // (e.g. agent operations) pass no proxy and fetch directly.\n const { text, imageUrls } = await fetchArticle(post.link!, { corsProxy: browserCorsProxy() });\n if (text) {\n // Store the body plus refined snippet/imageUrl derived from the full article — preferred\n // over the description-derived defaults wherever the Post is rendered.\n await Subscription.appendPostContent(space, subscription, {\n post,\n text,\n snippet: makeSnippet(stripHtml(text)),\n imageUrl: imageUrls[0],\n });\n }\n },\n catch: (error) => (error instanceof Error ? error : new Error(String(error))),\n });\n }),\n ),\n);\n"],"mappings":";;;;;;;;;AAcA,IAAA,4BAAA,gBAA6C,KAC3C,UAAU,YACR,OAAO,GAAG,WAAW,EAAE,MAAM,SAAS,SAAS;CAO7C,MAAM,OAAO,QAAQ;CACrB,UAAU,MAAM,+BAA4B;EAAA,YAAA;EAAA,GAAA;EAAA,GAAA;EAAA,GAAA;EAAA,GAAA,CAAA,QAAA,+BAAA;CAAA,CAAC;CAC7C,MAAM,eAAe,KAAK,QAAQ;CAClC,IAAI,CAAC,gBAAgB,CAAC,KAAK,MACzB;CAEF,MAAM,QAAQ,SAAS,YAAY;CACnC,UAAU,OAAO,mCAAgC;EAAA,YAAA;EAAA,GAAA;EAAA,GAAA;EAAA,GAAA;EAAA,GAAA,CAAA,SAAA,mCAAA;CAAA,CAAC;CAElD,IAAI,CAAC;MAEC,OADoB,OAAO,iBAAiB,gBAA6B,cAAc,IAAI,CAAC,GAE9F;CAAA;CAQJ,MAAM,gBAAgB,KAAK;CAC3B,IAAI,aAAa,SAAS,mBAAmB,eAAe;EAC1D,OAAO,OAAO,iBACZ,kBAA+B,OAAO,cAAc;GAClD;GACA,MAAM;GACN,SAAS,YAAY,UAAU,aAAa,CAAC;EAC/C,CAAC,CACH;EACA;CACF;CAEA,OAAO,OAAO,WAAW;EACvB,KAAK,YAAY;GAGf,MAAM,EAAE,MAAM,cAAc,MAAM,aAAa,KAAK,MAAO,EAAE,WAAW,iBAAiB,EAAE,CAAC;GAC5F,IAAI,MAGF,MAAM,kBAA+B,OAAO,cAAc;IACxD;IACA;IACA,SAAS,YAAY,UAAU,IAAI,CAAC;IACpC,UAAU,UAAU;GACtB,CAAC;EAEL;EACA,QAAQ,UAAW,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;CAC7E,CAAC;AACH,CAAC,CACH,CACF"}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
// src/meta.ts
|
|
2
1
|
import { Plugin } from "@dxos/app-framework";
|
|
3
|
-
|
|
4
|
-
// dx.config.ts
|
|
5
2
|
import { Config2 } from "@dxos/app-framework/config";
|
|
6
3
|
import { trim } from "@dxos/util";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
//#region dx.config.ts
|
|
5
|
+
var dx_config_default = Config2.make({ plugin: {
|
|
6
|
+
key: "org.dxos.plugin.magazine",
|
|
7
|
+
name: "Magazine",
|
|
8
|
+
author: "DXOS",
|
|
9
|
+
description: trim`
|
|
13
10
|
MagazinePlugin brings syndicated content into DXOS Composer by syncing RSS feeds
|
|
14
11
|
and ATproto (Bluesky) feeds as ECHO objects stored in the user's space.
|
|
15
12
|
Subscribed feeds are polled incrementally using a cursor so only new posts
|
|
@@ -39,22 +36,18 @@ var dx_config_default = Config2.make({
|
|
|
39
36
|
the tile renders a tag row when tags are present — enabling navigation and
|
|
40
37
|
feed-discovery flows in a later revision.
|
|
41
38
|
`,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// src/meta.ts
|
|
39
|
+
source: "https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-magazine",
|
|
40
|
+
icon: {
|
|
41
|
+
key: "ph--book-open-text--regular",
|
|
42
|
+
hue: "orange"
|
|
43
|
+
},
|
|
44
|
+
spec: "PLUGIN.mdl",
|
|
45
|
+
tags: ["alpha"]
|
|
46
|
+
} });
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/meta.ts
|
|
55
49
|
var meta = Plugin.getMetaFromConfig(dx_config_default);
|
|
50
|
+
//#endregion
|
|
51
|
+
export { meta as t };
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
meta
|
|
59
|
-
};
|
|
60
|
-
//# sourceMappingURL=chunk-JFPR6OV4.mjs.map
|
|
53
|
+
//# sourceMappingURL=chunk-meta.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-meta.mjs","names":[],"sources":["../../dx.config.ts","../../src/meta.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { Config2 } from '@dxos/app-framework/config';\nimport { trim } from '@dxos/util';\n\nexport default Config2.make({\n plugin: {\n key: 'org.dxos.plugin.magazine',\n name: 'Magazine',\n author: 'DXOS',\n description: trim`\n MagazinePlugin brings syndicated content into DXOS Composer by syncing RSS feeds\n and ATproto (Bluesky) feeds as ECHO objects stored in the user's space.\n Subscribed feeds are polled incrementally using a cursor so only new posts\n are fetched on each sync, and feed metadata (name, icon, description) is\n persisted alongside the posts for offline access.\n\n Magazines are agent-curated collections drawn from one or more feeds. Each\n Magazine carries the user's topic instructions describing what it should\n cover; pressing the Curate button (or a scheduled trigger) syncs all\n referenced feeds in parallel, then runs a single-shot agent pass that selects\n matching posts and returns their ids, which the operation adds mechanically.\n The agent's base methodology — how to select, how to dedup, and the structured\n output contract — lives in the MagazineSkill. Each magazine stores a\n persisted curation Routine (created at magazine creation time) whose instructions\n hold the editorial brief; pressing Curate runs that Routine against the synced\n candidates via RunInstructions. The skill also exposes a FetchArticleContent\n tool the agent may call to read a candidate's full text when title and\n description are insufficient to judge.\n A short snippet and hero image are derived mechanically from each post for\n display. A Masonry grid renders the curated tiles with images, titles, author\n metadata, and read-state dimming.\n\n Clicking a tile opens the PostArticle detail surface via the deck's layout\n dispatch, routing to the appropriate location (complementary drawer, sibling\n plank, or deck companion) based on the current layout mode. The schema also\n scaffolds a future tagging feature — posts carry an optional tags array and\n the tile renders a tag row when tags are present — enabling navigation and\n feed-discovery flows in a later revision.\n `,\n source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-magazine',\n icon: { key: 'ph--book-open-text--regular', hue: 'orange' },\n spec: 'PLUGIN.mdl',\n tags: ['alpha'],\n },\n});\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\n\nimport config from '../dx.config';\n\nexport const meta = Plugin.getMetaFromConfig(config);\n"],"mappings":";;;;AAOA,IAAA,oBAAe,QAAQ,KAAK,EAC1B,QAAQ;CACN,KAAK;CACL,MAAM;CACN,QAAQ;CACR,aAAa,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BjB,QAAQ;CACR,MAAM;EAAE,KAAK;EAA+B,KAAK;CAAS;CAC1D,MAAM;CACN,MAAM,CAAC,OAAO;AAChB,EACF,CAAC;;;ACvCD,IAAa,OAAO,OAAO,kBAAkB,iBAAM"}
|