@openforge-app/plugin-sdk 0.2.9 → 0.2.11
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/backend.d.ts +2 -2
- package/dist/collapsibleSectionState.js +30 -12
- package/dist/domain.d.ts +1 -0
- package/dist/domain.js +2 -1
- package/dist/frontend.d.ts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +1 -0
- package/dist/manifest.js +10 -4
- package/dist/markdown.d.ts +9 -0
- package/dist/markdown.js +57 -1
- package/dist/mermaid.d.ts +13 -0
- package/dist/mermaid.js +221 -0
- package/dist/mermaidZoom.d.ts +23 -0
- package/dist/mermaidZoom.js +52 -0
- package/dist/openforgePackageMetadataSchema.json +2 -11
- package/dist/publicEntrypoints.d.mts +27 -0
- package/dist/publicEntrypoints.mjs +104 -0
- package/dist/publicUiExports.mjs +12 -22
- package/dist/registryValidation.d.mts +11 -0
- package/dist/registryValidation.mjs +30 -0
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.js +131 -0
- package/dist/taskBrowserDevToolsShortcuts.d.ts +12 -0
- package/dist/taskBrowserDevToolsShortcuts.js +20 -0
- package/dist/testing/commonApiFake.js +240 -7
- package/dist/testing/contracts.d.ts +15 -3
- package/dist/testing/frontendContributionFake.d.ts +2 -3
- package/dist/testing/frontendContributionFake.js +0 -32
- package/dist/testing/support.d.ts +5 -2
- package/dist/testing/support.js +12 -1
- package/dist/types.d.ts +91 -20
- package/dist/types.js +1 -1
- package/dist/ui/MarkdownContent.svelte +46 -0
- package/dist/ui/MermaidDiagramPreview.svelte +216 -0
- package/dist/ui/Modal.svelte +39 -4
- package/dist/ui/PluginPageHeader.svelte +11 -4
- package/dist/ui/PluginPageShell.svelte +20 -0
- package/dist/ui/ProjectFileTree.svelte +192 -0
- package/dist/vite.js +7 -17
- package/package.json +14 -6
package/dist/backend.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot } from './types';
|
|
1
|
+
import type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalFileMetadata, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileAppendResult, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot } from './types';
|
|
2
2
|
export declare function defineBackendPlugin<const TPlugin extends BackendPlugin>(plugin: TPlugin): TPlugin;
|
|
3
|
-
export type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, };
|
|
3
|
+
export type { BackendFileSystemAPI, BackendMethodRegistration, BackendMethodRegistry, BackendOpenForgeAPI, BackendPlugin, BackendPluginContext, BackgroundServiceRegistration, BackgroundServiceRegistry, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, Disposable, ExternalFileMetadata, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, UserDataDirectoryRequest, UserDataFileAppendResult, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, };
|
|
@@ -2,6 +2,7 @@ import { writable } from 'svelte/store';
|
|
|
2
2
|
// The store started life in the host app as src/lib/infoPanelSectionState.ts. The key is
|
|
3
3
|
// deliberately unchanged: renaming it would reset every user's collapsed sections.
|
|
4
4
|
export const COLLAPSED_SECTIONS_STORAGE_KEY = 'openforge.infoPanelSectionCollapse.v1';
|
|
5
|
+
const SHARED_STATE_SYMBOL = Symbol.for('com.openforge.plugin-sdk.collapsible-section-state.v1');
|
|
5
6
|
export function pluginSectionKey(pluginId, sectionKey) {
|
|
6
7
|
return `plugin:${pluginId}:${sectionKey}`;
|
|
7
8
|
}
|
|
@@ -13,10 +14,10 @@ function getLocalStorage() {
|
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
function readPersisted() {
|
|
17
|
+
function readPersisted(fallback = {}) {
|
|
17
18
|
const storage = getLocalStorage();
|
|
18
19
|
if (storage === null)
|
|
19
|
-
return
|
|
20
|
+
return fallback;
|
|
20
21
|
try {
|
|
21
22
|
const rawValue = storage.getItem(COLLAPSED_SECTIONS_STORAGE_KEY);
|
|
22
23
|
if (rawValue === null)
|
|
@@ -32,7 +33,7 @@ function readPersisted() {
|
|
|
32
33
|
return state;
|
|
33
34
|
}
|
|
34
35
|
catch {
|
|
35
|
-
return
|
|
36
|
+
return fallback;
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
function writePersisted(state) {
|
|
@@ -56,23 +57,40 @@ function writePersisted(state) {
|
|
|
56
57
|
// Persistence is best effort and must not block the section from rendering.
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
+
function updatePersistedSection(current, key, collapsed) {
|
|
61
|
+
// localStorage is the cross-realm source of truth. Re-read it for every mutation so
|
|
62
|
+
// a bundle that cannot share this page's singleton still cannot overwrite newer keys.
|
|
63
|
+
const next = { ...readPersisted(current) };
|
|
64
|
+
if (collapsed)
|
|
65
|
+
next[key] = true;
|
|
66
|
+
else
|
|
67
|
+
delete next[key];
|
|
68
|
+
writePersisted(next);
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
function getSharedState() {
|
|
72
|
+
const registry = globalThis;
|
|
73
|
+
const existing = registry[SHARED_STATE_SYMBOL];
|
|
74
|
+
if (existing !== undefined)
|
|
75
|
+
return existing;
|
|
76
|
+
// Trusted Plugin bundles contain their own copy of this module. Symbol.for gives those
|
|
77
|
+
// copies and the host one live access to the same Svelte store in this renderer realm.
|
|
78
|
+
const shared = { store: writable(readPersisted()) };
|
|
79
|
+
registry[SHARED_STATE_SYMBOL] = shared;
|
|
80
|
+
return shared;
|
|
81
|
+
}
|
|
82
|
+
const { subscribe, set, update } = getSharedState().store;
|
|
60
83
|
export const collapsedSections = { subscribe };
|
|
61
84
|
export function isSectionCollapsed(state, key) {
|
|
62
85
|
return state[key] === true;
|
|
63
86
|
}
|
|
64
87
|
export function setSectionCollapsed(key, collapsed) {
|
|
65
|
-
update((current) =>
|
|
66
|
-
const next = { ...current, [key]: collapsed };
|
|
67
|
-
writePersisted(next);
|
|
68
|
-
return next;
|
|
69
|
-
});
|
|
88
|
+
update((current) => updatePersistedSection(current, key, collapsed));
|
|
70
89
|
}
|
|
71
90
|
export function toggleSection(key) {
|
|
72
91
|
update((current) => {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
return next;
|
|
92
|
+
const latest = readPersisted(current);
|
|
93
|
+
return updatePersistedSection(latest, key, !isSectionCollapsed(latest, key));
|
|
76
94
|
});
|
|
77
95
|
}
|
|
78
96
|
export function clearCollapsedSections() {
|
package/dist/domain.d.ts
CHANGED
|
@@ -179,6 +179,7 @@ export interface MergeReadinessInfo extends MergeStatusInfo {
|
|
|
179
179
|
review_status?: string | null;
|
|
180
180
|
draft?: boolean;
|
|
181
181
|
is_queued?: boolean;
|
|
182
|
+
merge_queue_required?: boolean | null;
|
|
182
183
|
unaddressed_comment_count?: number;
|
|
183
184
|
head_sha?: string | null;
|
|
184
185
|
updated_at?: number | null;
|
package/dist/domain.js
CHANGED
|
@@ -199,7 +199,8 @@ export function getMergeReadiness(pr, options = {}) {
|
|
|
199
199
|
warnings.push(mergeReadinessDetail('unprotected_fallback', 'Using simple mergeability because no protected-branch checks or review state are available.'));
|
|
200
200
|
}
|
|
201
201
|
if (hasDirectMergeability || isUnprotectedFallback) {
|
|
202
|
-
|
|
202
|
+
const mergeQueueRequired = options.requireMergeQueue === true || pr.merge_queue_required === true;
|
|
203
|
+
return mergeReadinessResult(pr, mergeQueueRequired ? 'ready_to_enqueue' : 'ready_to_merge', mergeQueueRequired ? 'enqueue' : 'merge', blockers, warnings);
|
|
203
204
|
}
|
|
204
205
|
if (mergeableState === 'unknown' || pr.mergeable === null || (mergeableState === null && pr.mergeable !== false)) {
|
|
205
206
|
warnings.push(mergeReadinessDetail('mergeability_unknown', 'GitHub has not reported definitive mergeability yet.'));
|
package/dist/frontend.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BrowserSurfaceCapture, BrowserDevToolsPanel, BrowserSurfaceFeedbackSelection, BrowserSurfaceVisualFeedback, BrowserSurfaceRegion, BrowserSurfaceErrorCode, BrowserSurfaceNavigationError, BrowserSurfacesAPI, GetOrCreateBrowserSurfaceRequest, TaskBrowserSurfaceController, TaskBrowserSurfaceState } from './browserSurfaces';
|
|
2
|
-
import type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendReviewUIRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot,
|
|
2
|
+
import type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendReviewUIRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginInjectionPointRegistration, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsSectionScope, PluginStorageScope, PluginSvgIcon, PluginTaskPaneProps, PluginTaskPaneTabRegistration, PluginReviewRowActionProps, PluginReviewRowActionRegistration, PluginTaskUISectionProps, PluginTaskUISectionRegistration, PluginViewProps, PluginViewRegistration, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, PtyBufferState, TerminalImageProtocol, TerminalViewSnapshot } from './types';
|
|
3
3
|
export declare const OPENFORGE_FRONTEND_PLUGIN_MARKER = "__openforgeFrontendPlugin";
|
|
4
4
|
export type MarkedFrontendPlugin<TPlugin extends FrontendPlugin = FrontendPlugin> = TPlugin & {
|
|
5
5
|
readonly [OPENFORGE_FRONTEND_PLUGIN_MARKER]: true;
|
|
@@ -7,4 +7,4 @@ export type MarkedFrontendPlugin<TPlugin extends FrontendPlugin = FrontendPlugin
|
|
|
7
7
|
export declare function defineFrontendPlugin<const TPlugin extends FrontendPlugin>(plugin: TPlugin): MarkedFrontendPlugin<TPlugin>;
|
|
8
8
|
export { BrowserSurfaceError, isAllowedBrowserSurfaceUrl } from './browserSurfaces';
|
|
9
9
|
export type { BrowserSurfaceCapture, BrowserDevToolsPanel, BrowserSurfaceFeedbackSelection, BrowserSurfaceVisualFeedback, BrowserSurfaceRegion, BrowserSurfaceErrorCode, BrowserSurfaceNavigationError, BrowserSurfacesAPI, GetOrCreateBrowserSurfaceRequest, TaskBrowserSurfaceController, TaskBrowserSurfaceState, };
|
|
10
|
-
export type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendReviewUIRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot,
|
|
10
|
+
export type { CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, Disposable, FrontendBackendBridge, FrontendOpenForgeAPI, FrontendPlugin, FrontendPluginContext, FrontendSettingsRegistry, FrontendTaskPaneRegistry, FrontendInjectionPointRegistry, FrontendTaskStartRegistry, FrontendReviewUIRegistry, FrontendTaskUIRegistry, FrontendViewRegistry, InjectionPointLocation, NavigationAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginInjectionPointRegistration, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsSectionScope, PluginStorageScope, PluginSvgIcon, PluginTaskPaneProps, PluginTaskPaneTabRegistration, PluginReviewRowActionProps, PluginReviewRowActionRegistration, PluginTaskUISectionProps, PluginTaskUISectionRegistration, PluginViewProps, PluginViewRegistration, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, PtyBufferState, TerminalImageProtocol, TerminalViewSnapshot, };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export { BrowserSurfaceError, isAllowedBrowserSurfaceUrl } from './browserSurfac
|
|
|
2
2
|
export type { BrowserSurfaceCapture, BrowserSurfaceFeedbackSelection, BrowserSurfaceVisualFeedback, BrowserSurfaceRegion, BrowserSurfaceErrorCode, BrowserSurfaceNavigationError, BrowserSurfacesAPI, GetOrCreateBrowserSurfaceRequest, TaskBrowserSurfaceController, TaskBrowserSurfaceState, } from './browserSurfaces';
|
|
3
3
|
export { OPENFORGE_PACKAGE_METADATA_SCHEMA, OPENFORGE_PLUGIN_CAPABILITIES, isOpenForgePackageMetadata, isPluginPackageMetadata, isSupportedOpenForgeApiVersion, validateOpenForgePackageMetadata, validatePluginPackageMetadata, } from './manifest';
|
|
4
4
|
export { createMemoryPluginStorage, createMockBackendOpenForgeApi, createMockFrontendOpenForgeApi, createMockOpenForgeApi, createMockPluginContext, createOpenForgeRegistryFake, createTestingCalls, TestingOpenForgeRegistryFake, TestingSubscriptionSink, } from './testing';
|
|
5
|
-
export { DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
|
|
6
|
-
export type { AgentCommandDescriptor, AgentCommandMetadata, AgentCommandRuntime, AttentionAPI, BackendReadyState, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, ConfigureStartPromptContributionRequest, CreateTaskRequest, Disposable, BackendFileSystemAPI, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, FileSystemAPI, UserDataDirectoryRequest, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, ImplementationRun, InjectionPointLocation, JsonObject, JsonPrimitive, JsonSchema, JsonValue, MaybePromise, KeyValueConfigAPI, NotificationRequest, NavigationAPI, NotificationsAPI, OpenForgeCommonAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, OpenForgePackageMetadata, OpenForgePluginCapability, OpenForgePluginContext, OpenForgePluginPackageJson, PluginCommandInvocationContext, PluginCommandInvocationSource, PluginComponentLoader, PluginComponentModule, PluginEntry, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginSettingsSectionProps, PluginState, PluginSvgIcon, PluginTaskPaneProps, PluginTaskUISectionProps, PluginViewProps, PluginStorage, PluginStorageScope, PluginViewKey, ProjectsAPI, PtyBufferState, ShellAPI, ShellResizeRequest, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, TerminalImageProtocol, TerminalViewSnapshot, ShellSessionRequest, ShellSpawnRequest,
|
|
5
|
+
export { MAX_AGENT_SESSION_PAGE_SIZE, DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
|
|
6
|
+
export type { AgentSessionCursor, AgentSessionOverlap, AgentSessionsAPI, AgentSessionSummary, AgentSessionSummaryPage, AgentSessionTaskSummary, AgentSessionWorkspace, AgentCommandDescriptor, AgentCommandMetadata, AgentCommandRuntime, AttentionAPI, BackendReadyState, CommandDescriptor, CommandRegistry, CommandRegistration, CommandShortcutMetadata, ComposeTaskRequest, ComposeTaskResult, ConfigureStartPromptContributionRequest, CreateTaskRequest, Disposable, BackendFileSystemAPI, ExternalFileMetadata, ExternalReadDirectoryRequest, ExternalReadFileRequest, ExternalReadTextFileChunksRequest, ExternalReadFileSystemAPI, FileSystemAPI, UserDataDirectoryRequest, UserDataFileAppendResult, UserDataFileRequest, UserDataFileSystemAPI, UserDataFileWriteRequest, ImplementationRun, ListAgentSessionsRequest, ListTaskSessionsRequest, InjectionPointLocation, JsonObject, JsonPrimitive, JsonSchema, JsonValue, MaybePromise, KeyValueConfigAPI, NotificationRequest, NavigationAPI, NotificationsAPI, OpenForgeCommonAPI, OpenForgeContextChangeHandler, OpenForgeContextSnapshot, OpenForgeNavigationRequest, OpenForgeNavigationSnapshot, OpenForgePackageMetadata, OpenForgePluginCapability, OpenForgePluginContext, OpenForgePluginPackageJson, PluginCommandInvocationContext, PluginCommandInvocationSource, PluginComponentLoader, PluginComponentModule, PluginEntry, PluginIcon, PluginSidebarNavigationProps, PluginSidebarViewIdentity, PluginInjectionPointProps, PluginSettingsSectionProps, PluginState, PluginSvgIcon, PluginTaskPaneProps, PluginTaskUISectionProps, PluginViewProps, PluginStorage, PluginStorageScope, PluginViewKey, ProjectsAPI, PtyBufferState, ShellAPI, ShellResizeRequest, TaskStartPrefixContext, TaskStartPrefixProviderRegistration, TerminalImageProtocol, TerminalViewSnapshot, ShellSessionRequest, ShellSpawnRequest, ShellWriteRequest, StartPromptContribution, SendTaskFollowUpRequest, StartTaskImplementationRequest, TaskFollowUpDisposition, TaskFollowUpErrorCode, TaskFollowUpReceipt, SubscriptionSink, SupportedOpenForgeApiVersion, SystemAPI, TasksAPI, ValidationError, } from './types';
|
|
7
7
|
export type { MockBackendOpenForgeAPI, MockFrontendOpenForgeAPI, TestingBackgroundServiceContribution, TestingBackendMethodContribution, TestingCommandContribution, TestingContributionBase, TestingExternalTextFile, TestingExternalTextFileChunksCall, TestingEventListenerContribution, TestingOpenForgeApiCalls, TestingOpenForgeApiOptions, TestingOpenForgeRegistrySnapshot, TestingRuntimeKind, TestingRuntimeScope, TestingSettingsSectionContribution, TestingTaskPaneTabContribution, TestingTaskUISectionContribution, TestingViewContribution, } from './testing';
|
|
8
8
|
export { parseStrictFiniteNumber } from './numberParsing';
|
|
9
9
|
export { buildProjectFileTree, flattenVisibleProjectFileTree, formatProjectFileTreeSize, getProjectFileTreeDepth, getProjectFileTreeItemAccessibility, getProjectFileTreeKeyboardAction, getProjectFileTreeParentPath, hasProjectFileTreeShortcutModifier, projectFileTreePathToId, } from './projectFileTree';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { BrowserSurfaceError, isAllowedBrowserSurfaceUrl } from './browserSurfaces';
|
|
2
2
|
export { OPENFORGE_PACKAGE_METADATA_SCHEMA, OPENFORGE_PLUGIN_CAPABILITIES, isOpenForgePackageMetadata, isPluginPackageMetadata, isSupportedOpenForgeApiVersion, validateOpenForgePackageMetadata, validatePluginPackageMetadata, } from './manifest';
|
|
3
3
|
export { createMemoryPluginStorage, createMockBackendOpenForgeApi, createMockFrontendOpenForgeApi, createMockOpenForgeApi, createMockPluginContext, createOpenForgeRegistryFake, createTestingCalls, TestingOpenForgeRegistryFake, TestingSubscriptionSink, } from './testing';
|
|
4
|
-
export { DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
|
|
4
|
+
export { MAX_AGENT_SESSION_PAGE_SIZE, DEFAULT_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MAX_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, MIN_EXTERNAL_TEXT_FILE_CHUNK_SIZE_BYTES, TaskFollowUpError, MAX_SUPPORTED_API_VERSION, MIN_SUPPORTED_API_VERSION, OPENFORGE_PLUGIN_API_VERSION, SUPPORTED_OPENFORGE_API_VERSIONS, isPluginViewKey, resolveExternalTextFileChunkSize, makePluginViewKey, parsePluginViewKey, } from './types';
|
|
5
5
|
export { parseStrictFiniteNumber } from './numberParsing';
|
|
6
6
|
export { buildProjectFileTree, flattenVisibleProjectFileTree, formatProjectFileTreeSize, getProjectFileTreeDepth, getProjectFileTreeItemAccessibility, getProjectFileTreeKeyboardAction, getProjectFileTreeParentPath, hasProjectFileTreeShortcutModifier, projectFileTreePathToId, } from './projectFileTree';
|
|
7
7
|
export { canMergePullRequest, getMergeReadiness, hasMergeConflicts, isClosedUnmergedPullRequest, isMergedPullRequest, isQueuedForMerge, isReadyToMerge, parseCheckRuns, preservePullRequestState, splitCheckRuns, } from './domain';
|
package/dist/manifest.d.ts
CHANGED
package/dist/manifest.js
CHANGED
|
@@ -28,6 +28,15 @@ function validateOptionalString(value, path) {
|
|
|
28
28
|
}
|
|
29
29
|
return [];
|
|
30
30
|
}
|
|
31
|
+
function validateBackendEntry(value) {
|
|
32
|
+
const errors = validateOptionalString(value, 'backend');
|
|
33
|
+
if (!isNonEmptyString(value))
|
|
34
|
+
return errors;
|
|
35
|
+
if (!['.mjs', '.js', '.cjs'].some(extension => value.endsWith(extension))) {
|
|
36
|
+
errors.push({ path: 'backend', message: 'Must point to a built .mjs, .js, or .cjs artifact' });
|
|
37
|
+
}
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
31
40
|
function validateEnablement(value) {
|
|
32
41
|
if (value === undefined || value === 'app' || value === 'project') {
|
|
33
42
|
return [];
|
|
@@ -117,7 +126,7 @@ export function validateOpenForgePackageMetadata(data) {
|
|
|
117
126
|
if (data.frontendStyles !== undefined && !isNonEmptyString(data.frontend)) {
|
|
118
127
|
errors.push({ path: 'frontendStyles', message: 'Requires a frontend entry' });
|
|
119
128
|
}
|
|
120
|
-
errors.push(...
|
|
129
|
+
errors.push(...validateBackendEntry(data.backend));
|
|
121
130
|
errors.push(...validateRequires(data.requires));
|
|
122
131
|
if (data.enablement === 'app' && (!Array.isArray(data.requires) || !data.requires.includes('appEnablement'))) {
|
|
123
132
|
errors.push({ path: 'requires', message: 'App enablement requires the appEnablement capability' });
|
|
@@ -125,9 +134,6 @@ export function validateOpenForgePackageMetadata(data) {
|
|
|
125
134
|
if (Array.isArray(data.requires) && data.requires.includes('browserSurfaces') && !isNonEmptyString(data.frontend)) {
|
|
126
135
|
errors.push({ path: 'requires', message: 'browserSurfaces capability requires a frontend entry' });
|
|
127
136
|
}
|
|
128
|
-
if (Array.isArray(data.requires) && data.requires.includes('taskLinks') && !isNonEmptyString(data.frontend)) {
|
|
129
|
-
errors.push({ path: 'requires', message: 'taskLinks capability requires a frontend entry' });
|
|
130
|
-
}
|
|
131
137
|
if (data.contributes !== undefined) {
|
|
132
138
|
errors.push({ path: 'contributes', message: 'Manifest contribution arrays are not supported; register contributions at runtime' });
|
|
133
139
|
}
|
package/dist/markdown.d.ts
CHANGED
|
@@ -11,6 +11,15 @@ export interface ResolvedMarkdownMedia {
|
|
|
11
11
|
}
|
|
12
12
|
/** Attribute holding the original URL of a `<img>`/`<a>` awaiting resolution. */
|
|
13
13
|
export declare const MARKDOWN_REMOTE_MEDIA_ATTRIBUTE = "data-markdown-remote-src";
|
|
14
|
+
export interface RenderedMarkdownCacheStats {
|
|
15
|
+
capacity: number;
|
|
16
|
+
size: number;
|
|
17
|
+
hits: number;
|
|
18
|
+
misses: number;
|
|
19
|
+
evictions: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function getRenderedMarkdownCacheStats(): Readonly<RenderedMarkdownCacheStats>;
|
|
22
|
+
export declare function clearRenderedMarkdownCache(): void;
|
|
14
23
|
export declare function resolveMarkdownRepositoryPath(value: string | null, markdownFilePath: string): string | null;
|
|
15
24
|
export declare function getMarkdownRepositoryLinkSuffix(value: string): string;
|
|
16
25
|
export declare function resolveMarkdownImageSrc(src: string | null, imageBaseUrl: string | null | undefined, markdownFilePath?: string): string | null;
|
package/dist/markdown.js
CHANGED
|
@@ -6,6 +6,56 @@ const markedOptions = {
|
|
|
6
6
|
gfm: true,
|
|
7
7
|
breaks: true,
|
|
8
8
|
};
|
|
9
|
+
const RENDERED_MARKDOWN_CACHE_CAPACITY = 100;
|
|
10
|
+
const renderedMarkdownCache = new Map();
|
|
11
|
+
let renderedMarkdownCacheHits = 0;
|
|
12
|
+
let renderedMarkdownCacheMisses = 0;
|
|
13
|
+
let renderedMarkdownCacheEvictions = 0;
|
|
14
|
+
function renderedMarkdownCacheKey(content, options) {
|
|
15
|
+
return JSON.stringify([
|
|
16
|
+
content,
|
|
17
|
+
options.imageBaseUrl ?? null,
|
|
18
|
+
options.markdownFilePath ?? null,
|
|
19
|
+
Boolean(options.deferRepositoryImages),
|
|
20
|
+
Boolean(options.deferRemoteMedia),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
function readRenderedMarkdownCache(key) {
|
|
24
|
+
const cached = renderedMarkdownCache.get(key);
|
|
25
|
+
if (cached === undefined) {
|
|
26
|
+
renderedMarkdownCacheMisses++;
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
renderedMarkdownCacheHits++;
|
|
30
|
+
renderedMarkdownCache.delete(key);
|
|
31
|
+
renderedMarkdownCache.set(key, cached);
|
|
32
|
+
return cached;
|
|
33
|
+
}
|
|
34
|
+
function writeRenderedMarkdownCache(key, html) {
|
|
35
|
+
if (renderedMarkdownCache.size >= RENDERED_MARKDOWN_CACHE_CAPACITY) {
|
|
36
|
+
const oldestKey = renderedMarkdownCache.keys().next().value;
|
|
37
|
+
if (oldestKey !== undefined) {
|
|
38
|
+
renderedMarkdownCache.delete(oldestKey);
|
|
39
|
+
renderedMarkdownCacheEvictions++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
renderedMarkdownCache.set(key, html);
|
|
43
|
+
}
|
|
44
|
+
export function getRenderedMarkdownCacheStats() {
|
|
45
|
+
return {
|
|
46
|
+
capacity: RENDERED_MARKDOWN_CACHE_CAPACITY,
|
|
47
|
+
size: renderedMarkdownCache.size,
|
|
48
|
+
hits: renderedMarkdownCacheHits,
|
|
49
|
+
misses: renderedMarkdownCacheMisses,
|
|
50
|
+
evictions: renderedMarkdownCacheEvictions,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function clearRenderedMarkdownCache() {
|
|
54
|
+
renderedMarkdownCache.clear();
|
|
55
|
+
renderedMarkdownCacheHits = 0;
|
|
56
|
+
renderedMarkdownCacheMisses = 0;
|
|
57
|
+
renderedMarkdownCacheEvictions = 0;
|
|
58
|
+
}
|
|
9
59
|
function hasAbsoluteOrSpecialUrl(value) {
|
|
10
60
|
return /^[a-z][a-z\d+.-]*:/i.test(value) || value.startsWith('//') || value.startsWith('#');
|
|
11
61
|
}
|
|
@@ -144,6 +194,12 @@ function prepareMarkdownMediaSources(html, options) {
|
|
|
144
194
|
return template.innerHTML;
|
|
145
195
|
}
|
|
146
196
|
export function renderMarkdownHtml(content, options = {}) {
|
|
197
|
+
const cacheKey = renderedMarkdownCacheKey(content, options);
|
|
198
|
+
const cached = readRenderedMarkdownCache(cacheKey);
|
|
199
|
+
if (cached !== undefined)
|
|
200
|
+
return cached;
|
|
147
201
|
const rawHtml = marked.parse(content, markedOptions);
|
|
148
|
-
|
|
202
|
+
const html = sanitizeHtml(prepareMarkdownMediaSources(rawHtml, options));
|
|
203
|
+
writeRenderedMarkdownCache(cacheKey, html);
|
|
204
|
+
return html;
|
|
149
205
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type MermaidTheme = 'default' | 'dark';
|
|
2
|
+
export declare const MERMAID_DIAGRAM_CLASS = "mermaid-diagram";
|
|
3
|
+
export declare const MERMAID_EXPAND_ACTION_CLASS = "mermaid-diagram-expand";
|
|
4
|
+
export declare function resolveMermaidTheme(doc?: Document): MermaidTheme;
|
|
5
|
+
export interface RenderedMermaidDiagram {
|
|
6
|
+
trigger: HTMLButtonElement;
|
|
7
|
+
wrapper: HTMLDivElement;
|
|
8
|
+
svg: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function getRenderedMermaidSvg(wrapper: HTMLDivElement): string | null;
|
|
11
|
+
export declare function getRenderedMermaidDiagram(target: Element): RenderedMermaidDiagram | null;
|
|
12
|
+
export declare function renderMermaidDiagrams(root: HTMLElement, isCurrent?: () => boolean): Promise<void>;
|
|
13
|
+
export declare function observeMermaidTheme(doc: Document, onChange: () => void): () => void;
|
package/dist/mermaid.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { sanitizeMermaidSvg } from './sanitize';
|
|
2
|
+
const MERMAID_CODE_SELECTOR = 'pre > code.language-mermaid';
|
|
3
|
+
export const MERMAID_DIAGRAM_CLASS = 'mermaid-diagram';
|
|
4
|
+
export const MERMAID_EXPAND_ACTION_CLASS = 'mermaid-diagram-expand';
|
|
5
|
+
const MERMAID_FAILURE_CLASS = 'mermaid-diagram-fallback';
|
|
6
|
+
const MERMAID_EXTERNAL_RESOURCE_PATTERN = /(?:\b(?:https?|ftp|file|data|javascript|vbscript):|(?:^|[\s("'=])\/\/|@import|\burl\s*\()/im;
|
|
7
|
+
const MERMAID_ESCAPED_STYLE_PATTERN = /\b(?:classDef|style|linkStyle)\b[^\r\n]*\\/i;
|
|
8
|
+
let diagramId = 0;
|
|
9
|
+
let renderQueue = Promise.resolve();
|
|
10
|
+
const mermaidThemeObservations = new WeakMap();
|
|
11
|
+
function loadMermaid() {
|
|
12
|
+
return import('mermaid').then(module => module.default);
|
|
13
|
+
}
|
|
14
|
+
export function resolveMermaidTheme(doc = document) {
|
|
15
|
+
const themeName = doc.documentElement.getAttribute('data-theme');
|
|
16
|
+
if (themeName)
|
|
17
|
+
return themeName.includes('dark') ? 'dark' : 'default';
|
|
18
|
+
if (doc.defaultView?.matchMedia?.('(prefers-color-scheme: dark)').matches)
|
|
19
|
+
return 'dark';
|
|
20
|
+
return 'default';
|
|
21
|
+
}
|
|
22
|
+
function queueMermaidRender(operation) {
|
|
23
|
+
const result = renderQueue.then(operation, operation);
|
|
24
|
+
renderQueue = result.then(() => undefined, () => undefined);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
function assertSafeMermaidSource(source) {
|
|
28
|
+
if (MERMAID_EXTERNAL_RESOURCE_PATTERN.test(source) || MERMAID_ESCAPED_STYLE_PATTERN.test(source)) {
|
|
29
|
+
throw new Error('Mermaid source contains an external resource');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function renderMermaidSvg(source, theme) {
|
|
33
|
+
assertSafeMermaidSource(source);
|
|
34
|
+
return queueMermaidRender(async () => {
|
|
35
|
+
const mermaid = await loadMermaid();
|
|
36
|
+
mermaid.initialize({
|
|
37
|
+
startOnLoad: false,
|
|
38
|
+
securityLevel: 'strict',
|
|
39
|
+
suppressErrorRendering: true,
|
|
40
|
+
maxTextSize: 50_000,
|
|
41
|
+
maxEdges: 500,
|
|
42
|
+
secure: [
|
|
43
|
+
'secure',
|
|
44
|
+
'securityLevel',
|
|
45
|
+
'startOnLoad',
|
|
46
|
+
'suppressErrorRendering',
|
|
47
|
+
'maxTextSize',
|
|
48
|
+
'maxEdges',
|
|
49
|
+
'theme',
|
|
50
|
+
'themeVariables',
|
|
51
|
+
'themeCSS',
|
|
52
|
+
'fontFamily',
|
|
53
|
+
'altFontFamily',
|
|
54
|
+
'dompurifyConfig',
|
|
55
|
+
'htmlLabels',
|
|
56
|
+
'flowchart',
|
|
57
|
+
],
|
|
58
|
+
theme,
|
|
59
|
+
htmlLabels: false,
|
|
60
|
+
flowchart: { htmlLabels: false },
|
|
61
|
+
});
|
|
62
|
+
const { svg } = await mermaid.render(`openforge-mermaid-${++diagramId}`, source);
|
|
63
|
+
return sanitizeMermaidSvg(svg);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function prepareDiagram(code) {
|
|
67
|
+
const fallback = code.parentElement;
|
|
68
|
+
if (!(fallback instanceof HTMLPreElement))
|
|
69
|
+
return null;
|
|
70
|
+
const existingWrapper = fallback.parentElement;
|
|
71
|
+
if (existingWrapper instanceof HTMLDivElement && existingWrapper.classList.contains(MERMAID_DIAGRAM_CLASS)) {
|
|
72
|
+
return { wrapper: existingWrapper, source: code.textContent?.trim() ?? '', fallback };
|
|
73
|
+
}
|
|
74
|
+
const wrapper = document.createElement('div');
|
|
75
|
+
wrapper.className = MERMAID_DIAGRAM_CLASS;
|
|
76
|
+
wrapper.setAttribute('role', 'group');
|
|
77
|
+
wrapper.setAttribute('aria-label', 'Mermaid diagram');
|
|
78
|
+
fallback.before(wrapper);
|
|
79
|
+
wrapper.append(fallback);
|
|
80
|
+
return { wrapper, source: code.textContent?.trim() ?? '', fallback };
|
|
81
|
+
}
|
|
82
|
+
function clearRenderedDiagram(wrapper, fallback) {
|
|
83
|
+
const expandAction = wrapper.querySelector(`:scope > button.${MERMAID_EXPAND_ACTION_CLASS}`);
|
|
84
|
+
for (const child of Array.from(wrapper.children)) {
|
|
85
|
+
if (child !== fallback && child !== expandAction)
|
|
86
|
+
child.remove();
|
|
87
|
+
}
|
|
88
|
+
if (expandAction)
|
|
89
|
+
expandAction.hidden = true;
|
|
90
|
+
fallback.hidden = false;
|
|
91
|
+
wrapper.classList.remove('mermaid-diagram-rendered', MERMAID_FAILURE_CLASS);
|
|
92
|
+
}
|
|
93
|
+
function showRenderFailure(wrapper, fallback) {
|
|
94
|
+
clearRenderedDiagram(wrapper, fallback);
|
|
95
|
+
const message = document.createElement('p');
|
|
96
|
+
message.className = 'mermaid-diagram-error';
|
|
97
|
+
message.setAttribute('role', 'status');
|
|
98
|
+
message.textContent = 'Unable to render Mermaid diagram. Showing source instead.';
|
|
99
|
+
wrapper.prepend(message);
|
|
100
|
+
wrapper.classList.add(MERMAID_FAILURE_CLASS);
|
|
101
|
+
}
|
|
102
|
+
function fitSvgToRenderedContent(svg) {
|
|
103
|
+
if (typeof svg.getBBox !== 'function')
|
|
104
|
+
return;
|
|
105
|
+
try {
|
|
106
|
+
const bounds = svg.getBBox();
|
|
107
|
+
if (![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite))
|
|
108
|
+
return;
|
|
109
|
+
if (bounds.width <= 0 || bounds.height <= 0)
|
|
110
|
+
return;
|
|
111
|
+
const padding = 8;
|
|
112
|
+
const width = bounds.width + padding * 2;
|
|
113
|
+
const height = bounds.height + padding * 2;
|
|
114
|
+
svg.setAttribute('viewBox', [
|
|
115
|
+
bounds.x - padding,
|
|
116
|
+
bounds.y - padding,
|
|
117
|
+
width,
|
|
118
|
+
height,
|
|
119
|
+
].join(' '));
|
|
120
|
+
svg.style.maxWidth = `${Math.ceil(width)}px`;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Keep Mermaid's original viewport when the browser cannot measure SVG geometry.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function createDiagramExpandAction(doc) {
|
|
127
|
+
const button = doc.createElement('button');
|
|
128
|
+
button.type = 'button';
|
|
129
|
+
button.className = MERMAID_EXPAND_ACTION_CLASS;
|
|
130
|
+
button.setAttribute('aria-label', 'Expand Mermaid diagram');
|
|
131
|
+
button.title = 'Expand Mermaid diagram';
|
|
132
|
+
button.textContent = 'Expand';
|
|
133
|
+
return button;
|
|
134
|
+
}
|
|
135
|
+
export function getRenderedMermaidSvg(wrapper) {
|
|
136
|
+
if (!wrapper.isConnected || !wrapper.classList.contains('mermaid-diagram-rendered'))
|
|
137
|
+
return null;
|
|
138
|
+
return wrapper.querySelector(':scope > svg')?.outerHTML ?? null;
|
|
139
|
+
}
|
|
140
|
+
export function getRenderedMermaidDiagram(target) {
|
|
141
|
+
const trigger = target.closest(`button.${MERMAID_EXPAND_ACTION_CLASS}`);
|
|
142
|
+
if (!(trigger instanceof HTMLButtonElement))
|
|
143
|
+
return null;
|
|
144
|
+
const wrapper = trigger.closest(`.${MERMAID_DIAGRAM_CLASS}`);
|
|
145
|
+
if (!(wrapper instanceof HTMLDivElement))
|
|
146
|
+
return null;
|
|
147
|
+
const svg = getRenderedMermaidSvg(wrapper);
|
|
148
|
+
return svg ? { trigger, wrapper, svg } : null;
|
|
149
|
+
}
|
|
150
|
+
function showRenderedDiagram(wrapper, fallback, sanitizedSvg) {
|
|
151
|
+
const template = document.createElement('template');
|
|
152
|
+
template.innerHTML = sanitizedSvg;
|
|
153
|
+
const svg = template.content.querySelector('svg');
|
|
154
|
+
if (!svg)
|
|
155
|
+
return false;
|
|
156
|
+
if (!svg.hasAttribute('role'))
|
|
157
|
+
svg.setAttribute('role', 'img');
|
|
158
|
+
if (!svg.hasAttribute('aria-label') && !svg.hasAttribute('aria-labelledby')) {
|
|
159
|
+
svg.setAttribute('aria-label', 'Mermaid diagram');
|
|
160
|
+
}
|
|
161
|
+
clearRenderedDiagram(wrapper, fallback);
|
|
162
|
+
wrapper.insertBefore(template.content, fallback);
|
|
163
|
+
fitSvgToRenderedContent(svg);
|
|
164
|
+
const expandAction = wrapper.querySelector(`:scope > button.${MERMAID_EXPAND_ACTION_CLASS}`)
|
|
165
|
+
?? createDiagramExpandAction(wrapper.ownerDocument);
|
|
166
|
+
expandAction.hidden = false;
|
|
167
|
+
if (!expandAction.parentElement)
|
|
168
|
+
wrapper.insertBefore(expandAction, fallback);
|
|
169
|
+
fallback.hidden = true;
|
|
170
|
+
wrapper.classList.add('mermaid-diagram-rendered');
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
export async function renderMermaidDiagrams(root, isCurrent = () => true) {
|
|
174
|
+
const diagrams = Array.from(root.querySelectorAll(MERMAID_CODE_SELECTOR))
|
|
175
|
+
.map(prepareDiagram)
|
|
176
|
+
.filter((diagram) => diagram !== null);
|
|
177
|
+
const theme = resolveMermaidTheme(root.ownerDocument);
|
|
178
|
+
await Promise.all(diagrams.map(async ({ wrapper, source, fallback }) => {
|
|
179
|
+
clearRenderedDiagram(wrapper, fallback);
|
|
180
|
+
if (!source) {
|
|
181
|
+
showRenderFailure(wrapper, fallback);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const svg = await renderMermaidSvg(source, theme);
|
|
186
|
+
if (!isCurrent() || !wrapper.isConnected)
|
|
187
|
+
return;
|
|
188
|
+
if (!showRenderedDiagram(wrapper, fallback, svg))
|
|
189
|
+
showRenderFailure(wrapper, fallback);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
if (isCurrent() && wrapper.isConnected)
|
|
193
|
+
showRenderFailure(wrapper, fallback);
|
|
194
|
+
}
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
export function observeMermaidTheme(doc, onChange) {
|
|
198
|
+
let observation = mermaidThemeObservations.get(doc);
|
|
199
|
+
if (!observation) {
|
|
200
|
+
const listeners = new Set();
|
|
201
|
+
const notify = () => listeners.forEach(listener => listener());
|
|
202
|
+
const observer = new MutationObserver((mutations) => {
|
|
203
|
+
if (mutations.some(mutation => mutation.attributeName === 'data-theme'))
|
|
204
|
+
notify();
|
|
205
|
+
});
|
|
206
|
+
observer.observe(doc.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
207
|
+
const media = doc.defaultView?.matchMedia?.('(prefers-color-scheme: dark)');
|
|
208
|
+
media?.addEventListener?.('change', notify);
|
|
209
|
+
observation = { listeners, observer, media, notify };
|
|
210
|
+
mermaidThemeObservations.set(doc, observation);
|
|
211
|
+
}
|
|
212
|
+
observation.listeners.add(onChange);
|
|
213
|
+
return () => {
|
|
214
|
+
observation.listeners.delete(onChange);
|
|
215
|
+
if (observation.listeners.size > 0)
|
|
216
|
+
return;
|
|
217
|
+
observation.observer.disconnect();
|
|
218
|
+
observation.media?.removeEventListener?.('change', observation.notify);
|
|
219
|
+
mermaidThemeObservations.delete(doc);
|
|
220
|
+
};
|
|
221
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const MIN_MERMAID_ZOOM = 0.25;
|
|
2
|
+
export declare const MAX_MERMAID_ZOOM = 4;
|
|
3
|
+
export declare const MERMAID_ZOOM_STEP = 0.25;
|
|
4
|
+
export interface MermaidSize {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export type MermaidZoomState = {
|
|
9
|
+
mode: 'fit';
|
|
10
|
+
} | {
|
|
11
|
+
mode: 'manual';
|
|
12
|
+
scale: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const FIT_MERMAID_ZOOM: MermaidZoomState;
|
|
15
|
+
export declare function calculateMermaidFitScale(content: MermaidSize, viewport: MermaidSize): number | null;
|
|
16
|
+
export declare function createManualMermaidZoom(scale: number): MermaidZoomState;
|
|
17
|
+
export declare function resolveMermaidZoomScale(state: MermaidZoomState, fitScale: number | null): number;
|
|
18
|
+
export declare function zoomMermaidIn(state: MermaidZoomState, fitScale: number | null): MermaidZoomState;
|
|
19
|
+
export declare function zoomMermaidOut(state: MermaidZoomState, fitScale: number | null): MermaidZoomState;
|
|
20
|
+
export declare function resetMermaidZoom(): MermaidZoomState;
|
|
21
|
+
export declare function canZoomMermaidIn(state: MermaidZoomState, fitScale: number | null): boolean;
|
|
22
|
+
export declare function canZoomMermaidOut(state: MermaidZoomState, fitScale: number | null): boolean;
|
|
23
|
+
export declare function formatMermaidZoomLabel(state: MermaidZoomState, fitScale: number | null): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const MIN_MERMAID_ZOOM = 0.25;
|
|
2
|
+
export const MAX_MERMAID_ZOOM = 4;
|
|
3
|
+
export const MERMAID_ZOOM_STEP = 0.25;
|
|
4
|
+
export const FIT_MERMAID_ZOOM = { mode: 'fit' };
|
|
5
|
+
function isPositiveSize(size) {
|
|
6
|
+
return Number.isFinite(size.width)
|
|
7
|
+
&& Number.isFinite(size.height)
|
|
8
|
+
&& size.width > 0
|
|
9
|
+
&& size.height > 0;
|
|
10
|
+
}
|
|
11
|
+
function clampManualScale(scale) {
|
|
12
|
+
if (!Number.isFinite(scale))
|
|
13
|
+
return 1;
|
|
14
|
+
return Math.min(MAX_MERMAID_ZOOM, Math.max(MIN_MERMAID_ZOOM, scale));
|
|
15
|
+
}
|
|
16
|
+
function normalizeScale(scale) {
|
|
17
|
+
return Math.round(scale * 10_000) / 10_000;
|
|
18
|
+
}
|
|
19
|
+
export function calculateMermaidFitScale(content, viewport) {
|
|
20
|
+
if (!isPositiveSize(content) || !isPositiveSize(viewport))
|
|
21
|
+
return null;
|
|
22
|
+
return Math.min(viewport.width / content.width, viewport.height / content.height);
|
|
23
|
+
}
|
|
24
|
+
export function createManualMermaidZoom(scale) {
|
|
25
|
+
return { mode: 'manual', scale: clampManualScale(scale) };
|
|
26
|
+
}
|
|
27
|
+
export function resolveMermaidZoomScale(state, fitScale) {
|
|
28
|
+
if (state.mode === 'manual')
|
|
29
|
+
return state.scale;
|
|
30
|
+
return fitScale && fitScale > 0 ? fitScale : 1;
|
|
31
|
+
}
|
|
32
|
+
export function zoomMermaidIn(state, fitScale) {
|
|
33
|
+
const currentScale = resolveMermaidZoomScale(state, fitScale);
|
|
34
|
+
return createManualMermaidZoom(normalizeScale(currentScale + MERMAID_ZOOM_STEP));
|
|
35
|
+
}
|
|
36
|
+
export function zoomMermaidOut(state, fitScale) {
|
|
37
|
+
const currentScale = resolveMermaidZoomScale(state, fitScale);
|
|
38
|
+
return createManualMermaidZoom(normalizeScale(currentScale - MERMAID_ZOOM_STEP));
|
|
39
|
+
}
|
|
40
|
+
export function resetMermaidZoom() {
|
|
41
|
+
return createManualMermaidZoom(1);
|
|
42
|
+
}
|
|
43
|
+
export function canZoomMermaidIn(state, fitScale) {
|
|
44
|
+
return resolveMermaidZoomScale(state, fitScale) < MAX_MERMAID_ZOOM;
|
|
45
|
+
}
|
|
46
|
+
export function canZoomMermaidOut(state, fitScale) {
|
|
47
|
+
return resolveMermaidZoomScale(state, fitScale) > MIN_MERMAID_ZOOM;
|
|
48
|
+
}
|
|
49
|
+
export function formatMermaidZoomLabel(state, fitScale) {
|
|
50
|
+
const percentage = Math.round(resolveMermaidZoomScale(state, fitScale) * 100);
|
|
51
|
+
return state.mode === 'fit' ? `Fit (${percentage}%)` : `${percentage}%`;
|
|
52
|
+
}
|
|
@@ -19,15 +19,6 @@
|
|
|
19
19
|
},
|
|
20
20
|
"then": { "required": ["frontend"] }
|
|
21
21
|
},
|
|
22
|
-
{
|
|
23
|
-
"if": {
|
|
24
|
-
"properties": {
|
|
25
|
-
"requires": { "contains": { "const": "taskLinks" } }
|
|
26
|
-
},
|
|
27
|
-
"required": ["requires"]
|
|
28
|
-
},
|
|
29
|
-
"then": { "required": ["frontend"] }
|
|
30
|
-
},
|
|
31
22
|
{
|
|
32
23
|
"if": {
|
|
33
24
|
"properties": { "enablement": { "const": "app" } },
|
|
@@ -106,7 +97,8 @@
|
|
|
106
97
|
"backend": {
|
|
107
98
|
"type": "string",
|
|
108
99
|
"minLength": 1,
|
|
109
|
-
"
|
|
100
|
+
"pattern": "\\.(?:mjs|js|cjs)$",
|
|
101
|
+
"description": "Path to the built backend JavaScript artifact. Use .mjs for new plugins; .js and .cjs remain supported for compatibility. The host runs each backend in a replaceable worker."
|
|
110
102
|
},
|
|
111
103
|
"requires": {
|
|
112
104
|
"type": "array",
|
|
@@ -136,7 +128,6 @@
|
|
|
136
128
|
"config",
|
|
137
129
|
"projectConfig",
|
|
138
130
|
"browserSurfaces",
|
|
139
|
-
"taskLinks",
|
|
140
131
|
"appEnablement",
|
|
141
132
|
"customSidebarNavigation",
|
|
142
133
|
"reviewUI"
|