@aglyn/plugins-marketplace 1.18.0 → 1.20.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/package.json +15 -15
- package/src/lib/components/editor-connections-provider.component.js +15 -0
- package/src/lib/components/editor-connections-provider.component.js.map +1 -1
- package/src/lib/components/listing-content.component.d.ts +5 -5
- package/src/lib/components/listing-content.component.js +87 -70
- package/src/lib/components/listing-content.component.js.map +1 -1
- package/src/lib/components/listing-detail-page.component.d.ts +2 -4
- package/src/lib/components/listing-detail-page.component.js.map +1 -1
- package/src/lib/components/marketplace-hub.component.js +10 -3
- package/src/lib/components/marketplace-hub.component.js.map +1 -1
- package/src/lib/components/plugin-site-set-panel.component.d.ts +7 -4
- package/src/lib/components/plugin-site-set-panel.component.js +3 -2
- package/src/lib/components/plugin-site-set-panel.component.js.map +1 -1
- package/src/lib/components/plugin-site-set.component.js +11 -0
- package/src/lib/components/plugin-site-set.component.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +5 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/declarations.server.js +15 -6
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-marketplace-actions.js +14 -1
- package/src/lib/hooks/use-marketplace-actions.js.map +1 -1
- package/src/lib/model/editor-connections.d.ts +4 -3
- package/src/lib/model/editor-connections.js +24 -7
- package/src/lib/model/editor-connections.js.map +1 -1
- package/src/lib/model/marketplace-catalog.d.ts +25 -4
- package/src/lib/model/marketplace-catalog.js +31 -6
- package/src/lib/model/marketplace-catalog.js.map +1 -1
- package/src/lib/model/marketplace.d.ts +28 -2
- package/src/lib/model/marketplace.js +36 -0
- package/src/lib/model/marketplace.js.map +1 -1
- package/src/lib/plugin.js +8 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/repeat/connection-repeat-source.d.ts +55 -0
- package/src/lib/repeat/connection-repeat-source.js +157 -0
- package/src/lib/repeat/connection-repeat-source.js.map +1 -0
- package/src/lib/server/connections/site-records-platform.d.ts +1 -0
- package/src/lib/server/connections/site-records-platform.js +8 -1
- package/src/lib/server/connections/site-records-platform.js.map +1 -1
- package/src/lib/server/connections/site-records.d.ts +28 -0
- package/src/lib/server/connections/site-records.js +59 -1
- package/src/lib/server/connections/site-records.js.map +1 -1
- package/src/lib/server/install-plugin.js +52 -8
- package/src/lib/server/install-plugin.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-marketplace",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.
|
|
29
|
-
"@aglyn/aglyn-markdown-editor": "1.
|
|
30
|
-
"@aglyn/shared-data-enums": "1.
|
|
31
|
-
"@aglyn/shared-data-mdi": "1.
|
|
32
|
-
"@aglyn/shared-ui-jsx": "1.
|
|
33
|
-
"@aglyn/shared-ui-next": "1.
|
|
34
|
-
"@aglyn/shared-ui-snackstack": "1.
|
|
35
|
-
"@aglyn/shared-util-email": "1.
|
|
36
|
-
"@aglyn/shared-util-http": "1.
|
|
37
|
-
"@aglyn/shared-util-timestamp": "1.
|
|
38
|
-
"@aglyn/shared-util-tools": "1.
|
|
39
|
-
"@aglyn/tenant-data-admin": "1.
|
|
40
|
-
"@aglyn/tenant-feature-instance": "1.
|
|
41
|
-
"@aglyn/tenant-runtime": "1.
|
|
28
|
+
"@aglyn/aglyn": "1.20.0",
|
|
29
|
+
"@aglyn/aglyn-markdown-editor": "1.20.0",
|
|
30
|
+
"@aglyn/shared-data-enums": "1.20.0",
|
|
31
|
+
"@aglyn/shared-data-mdi": "1.20.0",
|
|
32
|
+
"@aglyn/shared-ui-jsx": "1.20.0",
|
|
33
|
+
"@aglyn/shared-ui-next": "1.20.0",
|
|
34
|
+
"@aglyn/shared-ui-snackstack": "1.20.0",
|
|
35
|
+
"@aglyn/shared-util-email": "1.20.0",
|
|
36
|
+
"@aglyn/shared-util-http": "1.20.0",
|
|
37
|
+
"@aglyn/shared-util-timestamp": "1.20.0",
|
|
38
|
+
"@aglyn/shared-util-tools": "1.20.0",
|
|
39
|
+
"@aglyn/tenant-data-admin": "1.20.0",
|
|
40
|
+
"@aglyn/tenant-feature-instance": "1.20.0",
|
|
41
|
+
"@aglyn/tenant-runtime": "1.20.0",
|
|
42
42
|
"@swc/helpers": "0.5.23"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
18
|
import { EditorPluginConnectionsContext } from "@aglyn/aglyn/app-utils/plugin-connection-editor";
|
|
19
|
+
import { registerRepeatSource } from "@aglyn/aglyn/app-utils/repeat-sources";
|
|
19
20
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
20
21
|
import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
|
|
21
22
|
import { doc, getDoc } from "firebase/firestore";
|
|
@@ -23,6 +24,7 @@ import { usePathname } from "next/navigation";
|
|
|
23
24
|
import { useEffect, useState } from "react";
|
|
24
25
|
import { editorPluginConnections, syncingConnectionsOf } from "../model/editor-connections.js";
|
|
25
26
|
import { PLUGIN_CONNECTIONS_API_ROUTES } from "../model/plugin-connections.js";
|
|
27
|
+
import { connectionRepeatSource } from "../repeat/connection-repeat-source.js";
|
|
26
28
|
/** The console's editor surfaces: the Besigner and Preview of any document. */ const EDITOR_PATH = /\/(besigner|preview)(\/|$)/;
|
|
27
29
|
/** A pin of `listingId` on the site, else the workspace's. */ async function readPin(firestore, hostId, orgId, listingId) {
|
|
28
30
|
for (const path of [
|
|
@@ -150,6 +152,19 @@ import { PLUGIN_CONNECTIONS_API_ROUTES } from "../model/plugin-connections.js";
|
|
|
150
152
|
firestore,
|
|
151
153
|
load
|
|
152
154
|
]);
|
|
155
|
+
// The repeat source follows the site (AGL-4212): its picker lists this
|
|
156
|
+
// site's connections, and its token menu their fields. Put back to the
|
|
157
|
+
// site-less source when the editor closes, as the registrar left it.
|
|
158
|
+
useEffect(()=>{
|
|
159
|
+
var _value_repeatables;
|
|
160
|
+
if (!(value == null ? void 0 : (_value_repeatables = value.repeatables) == null ? void 0 : _value_repeatables.length)) return undefined;
|
|
161
|
+
registerRepeatSource(connectionRepeatSource(value));
|
|
162
|
+
return ()=>{
|
|
163
|
+
registerRepeatSource(connectionRepeatSource());
|
|
164
|
+
};
|
|
165
|
+
}, [
|
|
166
|
+
value
|
|
167
|
+
]);
|
|
153
168
|
return /*#__PURE__*/ _jsx(EditorPluginConnectionsContext.Provider, {
|
|
154
169
|
value: value,
|
|
155
170
|
children: children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/editor-connections-provider.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n EditorPluginConnectionsContext,\n type EditorPluginConnections,\n} from '@aglyn/aglyn/app-utils/plugin-connection-editor'\nimport type { PluginConnectionSiteRecord } from '@aglyn/aglyn/app-utils/plugin-connection-records'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { doc, getDoc, type Firestore } from 'firebase/firestore'\nimport { usePathname } from 'next/navigation'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport {\n editorPluginConnections,\n syncingConnectionsOf,\n type EditorListingConnections,\n type EditorRealmInstall,\n} from '../model/editor-connections'\nimport { PLUGIN_CONNECTIONS_API_ROUTES } from '../model/plugin-connections'\n\n/** The console's editor surfaces: the Besigner and Preview of any document. */\nconst EDITOR_PATH = /\\/(besigner|preview)(\\/|$)/\n\nexport interface EditorConnectionsProviderProps {\n orgId?: string | null\n hostId?: string | null\n children?: ReactNode\n /** Test seam: the console path. */\n pathname?: string\n /** Test seam: reads the site's realm installs, pins and records. */\n load?: (input: { orgId: string; hostId: string }) => Promise<EditorPluginConnections | undefined>\n}\n\ntype Fetcher = (path: string) => Promise<Response>\n\n/** A pin of `listingId` on the site, else the workspace's. */\nasync function readPin(firestore: Firestore, hostId: string, orgId: string, listingId: string): Promise<unknown> {\n for (const path of [['hosts', hostId, 'installs', listingId], ['orgs', orgId, 'installs', listingId]] as const) {\n try {\n const snapshot = await getDoc(doc(firestore, path[0], path[1], path[2], path[3]))\n if (snapshot.exists()) return snapshot.data()\n } catch {\n // A pin this member cannot read is one the editor does without.\n }\n }\n return null\n}\n\n/** The record of one connection, or `null` quietly: no key, no plan, no sync yet. */\nasync function readRecord(\n fetcher: Fetcher,\n input: { hostId: string; listingId: string; connectionId: string },\n): Promise<PluginConnectionSiteRecord | null> {\n try {\n const response = await fetcher(`/api/${PLUGIN_CONNECTIONS_API_ROUTES.record}?${new URLSearchParams(input).toString()}`)\n if (!response.ok) return null\n const answer = (await response.json().catch(() => null)) as { record?: Record<string, unknown> | null } | null\n const record = answer?.record\n if (!record || typeof record !== 'object') return null\n return {\n fields: (record['fields'] ?? {}) as PluginConnectionSiteRecord['fields'],\n items: (Array.isArray(record['items']) ? record['items'] : []) as PluginConnectionSiteRecord['items'],\n syncedAtMs: Number(record['syncedAtMs']) || 0,\n }\n } catch {\n return null\n }\n}\n\n/** Everything the editor is handed for one site, read once. */\nexport async function loadEditorPluginConnections(\n deps: { firestore: Firestore; fetcher: Fetcher },\n input: { orgId: string; hostId: string },\n): Promise<EditorPluginConnections | undefined> {\n let installs: EditorRealmInstall[]\n try {\n const response = await deps.fetcher(`/api/orgs/realm-plugins?${new URLSearchParams(input).toString()}`)\n if (!response.ok) return undefined\n installs = (((await response.json()) as { installs?: EditorRealmInstall[] }).installs ?? []).filter(\n (install) => typeof install?.listingId === 'string' && install.listingId,\n )\n } catch {\n return undefined\n }\n const listings: EditorListingConnections[] = []\n for (const install of installs) {\n const pin = (await readPin(deps.firestore, input.hostId, input.orgId, install.listingId)) as { manifest?: unknown } | null\n const connections = syncingConnectionsOf(pin?.manifest)\n if (connections.length) listings.push({ listingId: install.listingId, connections })\n }\n if (!listings.length) return undefined\n const records: Record<string, Record<string, PluginConnectionSiteRecord>> = {}\n await Promise.all(\n listings.flatMap((listing) =>\n listing.connections.map(async (connection) => {\n const record = await readRecord(deps.fetcher, {\n hostId: input.hostId,\n listingId: listing.listingId,\n connectionId: connection.id,\n })\n if (record) (records[listing.listingId] ??= {})[connection.id] = record\n }),\n ),\n )\n return editorPluginConnections({ installs, listings, records })\n}\n\n/**\n * The site's declared connections, for the editor (AGL-4196): registered as a\n * console provider, active only on the Besigner and Preview, where the\n * canvas hands each signed element its record and the binding picker lists\n * each record's fields by name. A refusal (no site key, no plan) or a failed\n * read leaves the context empty: elements read `null`, as on a site that has\n * not connected.\n */\nexport function EditorConnectionsProvider(props: EditorConnectionsProviderProps) {\n const { orgId, hostId, children } = props\n const routerPath = usePathname()\n const pathname = props.pathname ?? routerPath ?? ''\n const firestore = useFirestore()\n const { data: user } = useUser()\n const [value, setValue] = useState<EditorPluginConnections | undefined>(undefined)\n const active = Boolean(orgId && hostId && EDITOR_PATH.test(pathname))\n const load = props.load\n const uid = (user as { uid?: string } | null | undefined)?.uid ?? ''\n\n useEffect(() => {\n if (!active || !orgId || !hostId || (!load && !uid)) {\n setValue(undefined)\n return undefined\n }\n let live = true\n const read =\n load ?? ((input) => loadEditorPluginConnections({ firestore, fetcher: (path) => authorizedFetch(user as never, path) }, input))\n void read({ orgId, hostId })\n .then((next) => {\n if (live) setValue(next)\n })\n .catch(() => {\n if (live) setValue(undefined)\n })\n return () => {\n live = false\n }\n // `user` churns with token refreshes; the member, the site and the surface name the read.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [active, orgId, hostId, uid, firestore, load])\n\n return <EditorPluginConnectionsContext.Provider value={value}>{children}</EditorPluginConnectionsContext.Provider>\n}\nEditorConnectionsProvider.displayName = 'EditorConnectionsProvider'\n\nexport default EditorConnectionsProvider\n"],"names":["EditorPluginConnectionsContext","authorizedFetch","useFirestore","useUser","doc","getDoc","usePathname","useEffect","useState","editorPluginConnections","syncingConnectionsOf","PLUGIN_CONNECTIONS_API_ROUTES","EDITOR_PATH","readPin","firestore","hostId","orgId","listingId","path","snapshot","exists","data","readRecord","fetcher","input","record","response","URLSearchParams","toString","ok","answer","json","catch","fields","items","Array","isArray","syncedAtMs","Number","loadEditorPluginConnections","deps","installs","undefined","filter","install","listings","pin","connections","manifest","length","push","records","Promise","all","flatMap","listing","map","connection","connectionId","id","EditorConnectionsProvider","props","children","routerPath","pathname","user","value","setValue","active","Boolean","test","load","uid","live","read","then","next","Provider","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/editor-connections-provider.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n EditorPluginConnectionsContext,\n type EditorPluginConnections,\n} from '@aglyn/aglyn/app-utils/plugin-connection-editor'\nimport type { PluginConnectionSiteRecord } from '@aglyn/aglyn/app-utils/plugin-connection-records'\nimport { registerRepeatSource } from '@aglyn/aglyn/app-utils/repeat-sources'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { doc, getDoc, type Firestore } from 'firebase/firestore'\nimport { usePathname } from 'next/navigation'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport {\n editorPluginConnections,\n syncingConnectionsOf,\n type EditorListingConnections,\n type EditorRealmInstall,\n} from '../model/editor-connections'\nimport { PLUGIN_CONNECTIONS_API_ROUTES } from '../model/plugin-connections'\nimport { connectionRepeatSource } from '../repeat/connection-repeat-source'\n\n/** The console's editor surfaces: the Besigner and Preview of any document. */\nconst EDITOR_PATH = /\\/(besigner|preview)(\\/|$)/\n\nexport interface EditorConnectionsProviderProps {\n orgId?: string | null\n hostId?: string | null\n children?: ReactNode\n /** Test seam: the console path. */\n pathname?: string\n /** Test seam: reads the site's realm installs, pins and records. */\n load?: (input: { orgId: string; hostId: string }) => Promise<EditorPluginConnections | undefined>\n}\n\ntype Fetcher = (path: string) => Promise<Response>\n\n/** A pin of `listingId` on the site, else the workspace's. */\nasync function readPin(firestore: Firestore, hostId: string, orgId: string, listingId: string): Promise<unknown> {\n for (const path of [['hosts', hostId, 'installs', listingId], ['orgs', orgId, 'installs', listingId]] as const) {\n try {\n const snapshot = await getDoc(doc(firestore, path[0], path[1], path[2], path[3]))\n if (snapshot.exists()) return snapshot.data()\n } catch {\n // A pin this member cannot read is one the editor does without.\n }\n }\n return null\n}\n\n/** The record of one connection, or `null` quietly: no key, no plan, no sync yet. */\nasync function readRecord(\n fetcher: Fetcher,\n input: { hostId: string; listingId: string; connectionId: string },\n): Promise<PluginConnectionSiteRecord | null> {\n try {\n const response = await fetcher(`/api/${PLUGIN_CONNECTIONS_API_ROUTES.record}?${new URLSearchParams(input).toString()}`)\n if (!response.ok) return null\n const answer = (await response.json().catch(() => null)) as { record?: Record<string, unknown> | null } | null\n const record = answer?.record\n if (!record || typeof record !== 'object') return null\n return {\n fields: (record['fields'] ?? {}) as PluginConnectionSiteRecord['fields'],\n items: (Array.isArray(record['items']) ? record['items'] : []) as PluginConnectionSiteRecord['items'],\n syncedAtMs: Number(record['syncedAtMs']) || 0,\n }\n } catch {\n return null\n }\n}\n\n/** Everything the editor is handed for one site, read once. */\nexport async function loadEditorPluginConnections(\n deps: { firestore: Firestore; fetcher: Fetcher },\n input: { orgId: string; hostId: string },\n): Promise<EditorPluginConnections | undefined> {\n let installs: EditorRealmInstall[]\n try {\n const response = await deps.fetcher(`/api/orgs/realm-plugins?${new URLSearchParams(input).toString()}`)\n if (!response.ok) return undefined\n installs = (((await response.json()) as { installs?: EditorRealmInstall[] }).installs ?? []).filter(\n (install) => typeof install?.listingId === 'string' && install.listingId,\n )\n } catch {\n return undefined\n }\n const listings: EditorListingConnections[] = []\n for (const install of installs) {\n const pin = (await readPin(deps.firestore, input.hostId, input.orgId, install.listingId)) as { manifest?: unknown } | null\n const connections = syncingConnectionsOf(pin?.manifest)\n if (connections.length) listings.push({ listingId: install.listingId, connections })\n }\n if (!listings.length) return undefined\n const records: Record<string, Record<string, PluginConnectionSiteRecord>> = {}\n await Promise.all(\n listings.flatMap((listing) =>\n listing.connections.map(async (connection) => {\n const record = await readRecord(deps.fetcher, {\n hostId: input.hostId,\n listingId: listing.listingId,\n connectionId: connection.id,\n })\n if (record) (records[listing.listingId] ??= {})[connection.id] = record\n }),\n ),\n )\n return editorPluginConnections({ installs, listings, records })\n}\n\n/**\n * The site's declared connections, for the editor (AGL-4196): registered as a\n * console provider, active only on the Besigner and Preview, where the\n * canvas hands each signed element its record and the binding picker lists\n * each record's fields by name. A refusal (no site key, no plan) or a failed\n * read leaves the context empty: elements read `null`, as on a site that has\n * not connected.\n */\nexport function EditorConnectionsProvider(props: EditorConnectionsProviderProps) {\n const { orgId, hostId, children } = props\n const routerPath = usePathname()\n const pathname = props.pathname ?? routerPath ?? ''\n const firestore = useFirestore()\n const { data: user } = useUser()\n const [value, setValue] = useState<EditorPluginConnections | undefined>(undefined)\n const active = Boolean(orgId && hostId && EDITOR_PATH.test(pathname))\n const load = props.load\n const uid = (user as { uid?: string } | null | undefined)?.uid ?? ''\n\n useEffect(() => {\n if (!active || !orgId || !hostId || (!load && !uid)) {\n setValue(undefined)\n return undefined\n }\n let live = true\n const read =\n load ?? ((input) => loadEditorPluginConnections({ firestore, fetcher: (path) => authorizedFetch(user as never, path) }, input))\n void read({ orgId, hostId })\n .then((next) => {\n if (live) setValue(next)\n })\n .catch(() => {\n if (live) setValue(undefined)\n })\n return () => {\n live = false\n }\n // `user` churns with token refreshes; the member, the site and the surface name the read.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [active, orgId, hostId, uid, firestore, load])\n\n // The repeat source follows the site (AGL-4212): its picker lists this\n // site's connections, and its token menu their fields. Put back to the\n // site-less source when the editor closes, as the registrar left it.\n useEffect(() => {\n if (!value?.repeatables?.length) return undefined\n registerRepeatSource(connectionRepeatSource(value))\n return () => {\n registerRepeatSource(connectionRepeatSource())\n }\n }, [value])\n\n return <EditorPluginConnectionsContext.Provider value={value}>{children}</EditorPluginConnectionsContext.Provider>\n}\nEditorConnectionsProvider.displayName = 'EditorConnectionsProvider'\n\nexport default EditorConnectionsProvider\n"],"names":["EditorPluginConnectionsContext","registerRepeatSource","authorizedFetch","useFirestore","useUser","doc","getDoc","usePathname","useEffect","useState","editorPluginConnections","syncingConnectionsOf","PLUGIN_CONNECTIONS_API_ROUTES","connectionRepeatSource","EDITOR_PATH","readPin","firestore","hostId","orgId","listingId","path","snapshot","exists","data","readRecord","fetcher","input","record","response","URLSearchParams","toString","ok","answer","json","catch","fields","items","Array","isArray","syncedAtMs","Number","loadEditorPluginConnections","deps","installs","undefined","filter","install","listings","pin","connections","manifest","length","push","records","Promise","all","flatMap","listing","map","connection","connectionId","id","EditorConnectionsProvider","props","children","routerPath","pathname","user","value","setValue","active","Boolean","test","load","uid","live","read","then","next","repeatables","Provider","displayName"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OrgInstallHost } from '../model/marketplace';
|
|
1
2
|
export declare function ListingReadme({ readme }: {
|
|
2
3
|
readme: string;
|
|
3
4
|
}): import("react").JSX.Element;
|
|
@@ -41,12 +42,11 @@ export interface MarketplaceListingContentProps {
|
|
|
41
42
|
/**
|
|
42
43
|
* The org's sites, for the install-targeting picker (AGL-773). Present only
|
|
43
44
|
* at org scope; when given (and non-empty) the CTA offers All sites vs
|
|
44
|
-
* Selected sites instead of the single org/host choice.
|
|
45
|
+
* Selected sites instead of the single org/host choice. Each carries the
|
|
46
|
+
* site's own `disabledPlugins` when the shell has it (AGL-4047), which the
|
|
47
|
+
* installed-sites panel reads.
|
|
45
48
|
*/
|
|
46
|
-
hosts?: ReadonlyArray<
|
|
47
|
-
id: string;
|
|
48
|
-
label: string;
|
|
49
|
-
}>;
|
|
49
|
+
hosts?: ReadonlyArray<OrgInstallHost>;
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* Marketplace listing detail (AGL-95/419), relocated from the app route —
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
-
import { LISTING_README_MAX_CHARS, listingArtifactType, installsUnreviewedFallback, listingArtifactLabel, listingInclusions, hasLivePurchaseOf, hasOrgLicenceOf, licensedOrgIdsFor, installTargetsFor, isListingDeleted, isPrivateListing, resolveInstallPlan, resolveOrgInstallSummary, resolvePluginInstallState, resolveUninstallTargets, safePublisherHref, safePublisherImageSrc } from "../model/marketplace.js";
|
|
19
|
+
import { LISTING_README_MAX_CHARS, listingArtifactType, installsUnreviewedFallback, listingArtifactLabel, listingInclusions, hasLivePurchaseOf, hasOrgLicenceOf, licensedOrgIdsFor, installTargetsFor, isListingDeleted, isPrivateListing, resolveInstallPlan, resolveOrgInstallSummary, resolvePluginInstallState, resolveUninstallTargets, safePublisherHref, safePublisherImageSrc, allExceptInstallKind } from "../model/marketplace.js";
|
|
20
20
|
import { isPublisherMemberElsewhere, landsInPublisherWorkspace, publisherMemberBuyReason } from "../model/own-listing.js";
|
|
21
21
|
import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
|
|
22
22
|
import { PrerequisiteNotice } from "@aglyn/shared-ui-jsx/components/prerequisite-notice.component";
|
|
23
|
+
import { SiteSharingField } from "@aglyn/shared-ui-jsx/components/site-sharing-field.component";
|
|
24
|
+
import { orgPermissionLabel } from "@aglyn/aglyn/app-utils/org-permissions";
|
|
23
25
|
import UninstallImpactDialog from "./uninstall-impact-dialog.component.js";
|
|
24
26
|
import { ListingImage } from "./listing-image.component.js";
|
|
25
27
|
import { buildRoute, parseMarkdownLite, PLATFORM_BRAND_NAME, offeredPluginVersion, PLUGIN_HOST_ABI_VERSION, pluginDocsHelp, resolveMarkdownLink, resolveMediaSrc, Route } from "@aglyn/aglyn";
|
|
@@ -27,7 +29,7 @@ import { mdiCheckCircle, mdiCheckCircleOutline, mdiEmailOutline, mdiGithub, mdiI
|
|
|
27
29
|
import { AppLink, CardDisplay, GridItems, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
28
30
|
import { NextPageTitle } from "@aglyn/shared-ui-next/contexts/next-page-title-provider";
|
|
29
31
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
30
|
-
import { Alert, Avatar, Box, Button,
|
|
32
|
+
import { Alert, Avatar, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, IconButton, Link as MuiLink, Rating, Stack, Tooltip, Typography } from "@mui/material";
|
|
31
33
|
import { collection, doc, documentId, getDoc, limit, orderBy, query, where } from "firebase/firestore";
|
|
32
34
|
import { useEffect, useMemo, useState } from "react";
|
|
33
35
|
import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useFirestoreDoc, useHostOrgId, useUser, useScopeTokens } from "@aglyn/tenant-feature-instance";
|
|
@@ -427,15 +429,23 @@ export function ListingReadme({ readme }) {
|
|
|
427
429
|
const [updateOpen, setUpdateOpen] = useState(false);
|
|
428
430
|
// Screenshot lightbox (AGL-869): the clicked screenshot's URL, or null.
|
|
429
431
|
const [lightboxUrl, setLightboxUrl] = useState(null);
|
|
430
|
-
// Org-scope install targeting (AGL-773): All sites
|
|
431
|
-
// engaged at org scope with a known site
|
|
432
|
-
// simpler org/host choice (AGL-656) still
|
|
432
|
+
// Org-scope install targeting (AGL-773): All sites, a chosen subset, or all
|
|
433
|
+
// sites except some (AGL-4047). Only engaged at org scope with a known site
|
|
434
|
+
// list; otherwise the per-site tab's simpler org/host choice (AGL-656) still
|
|
435
|
+
// applies.
|
|
433
436
|
const orgTargeting = Boolean(orgScoped && hosts && hosts.length > 0);
|
|
434
|
-
const [
|
|
435
|
-
|
|
437
|
+
const [installTo, setInstallTo] = useState({
|
|
438
|
+
mode: 'all'
|
|
439
|
+
});
|
|
436
440
|
const allHostIds = useMemo(()=>(hosts != null ? hosts : []).map((h)=>h.id), [
|
|
437
441
|
hosts
|
|
438
442
|
]);
|
|
443
|
+
const installSites = useMemo(()=>(hosts != null ? hosts : []).map((host)=>({
|
|
444
|
+
id: host.id,
|
|
445
|
+
name: host.label
|
|
446
|
+
})), [
|
|
447
|
+
hosts
|
|
448
|
+
]);
|
|
439
449
|
// Listings are org-owned (AGL-652) — "did I publish this" is an org
|
|
440
450
|
// comparison, resolved from the routing mirror like the browse grid.
|
|
441
451
|
const [viewerOrgId, setViewerOrgId] = useState(null);
|
|
@@ -922,15 +932,49 @@ export function ListingReadme({ readme }) {
|
|
|
922
932
|
// artifact can host-pin (plugins, components, templates, layouts) — an
|
|
923
933
|
// org-only artifact can't target a subset.
|
|
924
934
|
const canSelectSites = installTargets.includes('host');
|
|
935
|
+
// "All sites except…" (AGL-4047): a plugin pins once for the workspace and
|
|
936
|
+
// leaves the chosen sites out, so a site added later gets it; a per-site copy
|
|
937
|
+
// only picks today's sites; an org-only artifact can't leave one out.
|
|
938
|
+
const allExceptKind = listing ? allExceptInstallKind(listing) : null;
|
|
939
|
+
const installModes = useMemo(()=>[
|
|
940
|
+
'all',
|
|
941
|
+
...canSelectSites ? [
|
|
942
|
+
'selected'
|
|
943
|
+
] : [],
|
|
944
|
+
...allExceptKind ? [
|
|
945
|
+
'allExcept'
|
|
946
|
+
] : []
|
|
947
|
+
], [
|
|
948
|
+
canSelectSites,
|
|
949
|
+
allExceptKind
|
|
950
|
+
]);
|
|
951
|
+
// A choice the artifact doesn't offer reads as All sites, never as a plan
|
|
952
|
+
// that silently means something else.
|
|
953
|
+
const installChoice = useMemo(()=>installModes.includes(installTo.mode) ? installTo : {
|
|
954
|
+
mode: 'all'
|
|
955
|
+
}, [
|
|
956
|
+
installModes,
|
|
957
|
+
installTo
|
|
958
|
+
]);
|
|
959
|
+
const targeting = installChoice.mode === 'selected' ? 'selected-sites' : installChoice.mode === 'allExcept' ? 'all-sites-except' : 'all-sites';
|
|
925
960
|
const installPlanSteps = useMemo(()=>listing ? resolveInstallPlan(listing, targeting, {
|
|
926
|
-
selectedHostIds,
|
|
927
|
-
allHostIds
|
|
961
|
+
selectedHostIds: installChoice.mode === 'selected' ? installChoice.hostIds : [],
|
|
962
|
+
allHostIds,
|
|
963
|
+
exceptHostIds: installChoice.mode === 'allExcept' ? installChoice.exceptHostIds : []
|
|
928
964
|
}) : [], [
|
|
929
965
|
listing,
|
|
930
966
|
targeting,
|
|
931
|
-
|
|
967
|
+
installChoice,
|
|
932
968
|
allHostIds
|
|
933
969
|
]);
|
|
970
|
+
const installModeDetails = installTargets.includes('org') ? {
|
|
971
|
+
all: 'Every site, including sites you add later.',
|
|
972
|
+
allExcept: 'Every site but the ones you leave out, including sites you add later.'
|
|
973
|
+
} : {
|
|
974
|
+
all: `Installs to all ${allHostIds.length} site` + (allHostIds.length === 1 ? '' : 's') + ". New sites won't get it automatically.",
|
|
975
|
+
allExcept: 'Installs to the sites you have today, but the ones you leave ' + "out. New sites won't get it automatically."
|
|
976
|
+
};
|
|
977
|
+
const installToProblem = installChoice.mode === 'selected' && !installChoice.hostIds.length ? 'Pick at least one site.' : installChoice.mode === 'allExcept' && !installChoice.exceptHostIds.length ? 'Choose at least one site to leave out, or choose All sites.' : null;
|
|
934
978
|
const versionHistory = Array.isArray(listing == null ? void 0 : listing.versionHistory) ? [
|
|
935
979
|
...listing.versionHistory
|
|
936
980
|
].sort((a, b)=>b.version - a.version) : [];
|
|
@@ -940,8 +984,18 @@ export function ListingReadme({ readme }) {
|
|
|
940
984
|
// something else.
|
|
941
985
|
const installTargetSummary = ()=>{
|
|
942
986
|
if (orgTargeting && !installed) {
|
|
943
|
-
var _installPlanSteps_;
|
|
944
|
-
|
|
987
|
+
var _installPlanSteps_, _orgStep_exceptHostIds;
|
|
988
|
+
const orgStep = installPlanSteps.length === 1 && ((_installPlanSteps_ = installPlanSteps[0]) == null ? void 0 : _installPlanSteps_.scope) === 'org' ? installPlanSteps[0] : null;
|
|
989
|
+
if (orgStep == null ? void 0 : (_orgStep_exceptHostIds = orgStep.exceptHostIds) == null ? void 0 : _orgStep_exceptHostIds.length) {
|
|
990
|
+
// "All sites except…" (AGL-4047): the sites left out, by name.
|
|
991
|
+
const leftOut = orgStep.exceptHostIds.map((hostId)=>{
|
|
992
|
+
var _ref;
|
|
993
|
+
var _find;
|
|
994
|
+
return (_ref = (_find = (hosts != null ? hosts : []).find((host)=>host.id === hostId)) == null ? void 0 : _find.label) != null ? _ref : hostId;
|
|
995
|
+
});
|
|
996
|
+
return `every site except ${leftOut.join(', ')}, ` + 'including sites added later';
|
|
997
|
+
}
|
|
998
|
+
if (orgStep) {
|
|
945
999
|
return 'the whole organization — every site, including sites added later';
|
|
946
1000
|
}
|
|
947
1001
|
const names = installPlanSteps.filter((step)=>step.scope === 'host').map((step)=>{
|
|
@@ -965,10 +1019,13 @@ export function ListingReadme({ readme }) {
|
|
|
965
1019
|
// and which are still free to add. Only meaningful at org scope for a
|
|
966
1020
|
// plugin — everything else installs INTO a site and has no pin to survey.
|
|
967
1021
|
const orgPluginScope = Boolean(orgTargeting && isPlugin);
|
|
968
|
-
const orgInstall = useMemo(
|
|
1022
|
+
const orgInstall = useMemo(// With each site's own switch (AGL-4047), so a site that turned the plugin
|
|
1023
|
+
// off, or was left out at install, is not listed as running it.
|
|
1024
|
+
()=>resolveOrgInstallSummary(hosts != null ? hosts : [], sitePins, orgPin != null ? orgPin : null, listingId), [
|
|
969
1025
|
hosts,
|
|
970
1026
|
sitePins,
|
|
971
|
-
orgPin
|
|
1027
|
+
orgPin,
|
|
1028
|
+
listingId
|
|
972
1029
|
]);
|
|
973
1030
|
/** Run a pin write, then re-read every site's pin. */ const runSiteEdit = async (work)=>{
|
|
974
1031
|
try {
|
|
@@ -1428,68 +1485,28 @@ export function ListingReadme({ readme }) {
|
|
|
1428
1485
|
})
|
|
1429
1486
|
}) : null,
|
|
1430
1487
|
orgTargeting && !installed && !(orgPluginScope && orgInstall.installedAnywhere) ? /*#__PURE__*/ _jsxs(Stack, {
|
|
1431
|
-
spacing: 1
|
|
1488
|
+
spacing: 1,
|
|
1432
1489
|
sx: {
|
|
1433
1490
|
maxWidth: 420
|
|
1434
1491
|
},
|
|
1435
1492
|
children: [
|
|
1436
|
-
/*#__PURE__*/
|
|
1437
|
-
select: true,
|
|
1438
|
-
size: "small",
|
|
1493
|
+
/*#__PURE__*/ _jsx(SiteSharingField, {
|
|
1439
1494
|
label: "Install to",
|
|
1440
|
-
value:
|
|
1441
|
-
onChange:
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
children: 'Selected sites'
|
|
1451
|
-
}) : null
|
|
1452
|
-
]
|
|
1495
|
+
value: installChoice,
|
|
1496
|
+
onChange: setInstallTo,
|
|
1497
|
+
sites: installSites,
|
|
1498
|
+
sitesReady: true,
|
|
1499
|
+
modes: installModes,
|
|
1500
|
+
modeDetails: installModeDetails,
|
|
1501
|
+
listLabels: {
|
|
1502
|
+
selected: 'Sites to install to'
|
|
1503
|
+
},
|
|
1504
|
+
problem: installToProblem
|
|
1453
1505
|
}),
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
children:
|
|
1458
|
-
/*#__PURE__*/ _jsx(FormLabel, {
|
|
1459
|
-
component: "legend",
|
|
1460
|
-
sx: {
|
|
1461
|
-
typography: 'caption'
|
|
1462
|
-
},
|
|
1463
|
-
children: 'Sites'
|
|
1464
|
-
}),
|
|
1465
|
-
/*#__PURE__*/ _jsx(FormGroup, {
|
|
1466
|
-
// Scrolls rather than pushing Install off
|
|
1467
|
-
// the page on an org with many sites.
|
|
1468
|
-
sx: {
|
|
1469
|
-
maxHeight: 240,
|
|
1470
|
-
overflowY: 'auto'
|
|
1471
|
-
},
|
|
1472
|
-
children: (hosts != null ? hosts : []).map((host)=>/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
1473
|
-
control: /*#__PURE__*/ _jsx(Checkbox, {
|
|
1474
|
-
size: "small",
|
|
1475
|
-
checked: selectedHostIds.includes(host.id),
|
|
1476
|
-
onChange: (event)=>setSelectedHostIds((current)=>event.target.checked ? [
|
|
1477
|
-
...current,
|
|
1478
|
-
host.id
|
|
1479
|
-
] : current.filter((id)=>id !== host.id))
|
|
1480
|
-
}),
|
|
1481
|
-
label: host.label,
|
|
1482
|
-
slotProps: {
|
|
1483
|
-
typography: {
|
|
1484
|
-
variant: 'body2'
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
}, host.id))
|
|
1488
|
-
}),
|
|
1489
|
-
/*#__PURE__*/ _jsx(FormHelperText, {
|
|
1490
|
-
children: selectedHostIds.length ? `${selectedHostIds.length} of ${(hosts != null ? hosts : []).length} selected.` : 'Pick at least one site.'
|
|
1491
|
-
})
|
|
1492
|
-
]
|
|
1506
|
+
installChoice.mode === 'allExcept' && allExceptKind === 'lasting' ? /*#__PURE__*/ _jsx(Typography, {
|
|
1507
|
+
variant: "caption",
|
|
1508
|
+
color: "text.secondary",
|
|
1509
|
+
children: 'Each site you leave out has the plugin ' + 'switched off, with the same switch as its ' + 'own Admin › Plugins page. Leaving a site ' + 'out takes ' + `“${orgPermissionLabel('site.manage')}” ` + 'there, and anyone holding it, which the ' + 'site’s admins do by default, can turn the ' + 'plugin back on for that site.'
|
|
1493
1510
|
}) : null
|
|
1494
1511
|
]
|
|
1495
1512
|
}) : installTargets.length > 1 && !installed ? /*#__PURE__*/ _jsxs(TextField, {
|