@xpufx/paseo-forges 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/client/active-forge.ts +47 -0
- package/client/board-alert.tsx +236 -0
- package/client/foreign-link.tsx +33 -0
- package/client/hook-queue-panel.tsx +412 -0
- package/client/issues-pill.tsx +1976 -0
- package/client/label-chip.tsx +116 -0
- package/client/linkifier.tsx +280 -0
- package/client/pill-label.ts +183 -0
- package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
- package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
- package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
- package/client/vendor/paseo-plugin-helper/host.ts +277 -0
- package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/index.ts +28 -0
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
- package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
- package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
- package/client/vendor/paseo-plugin-helper/query.ts +66 -0
- package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
- package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
- package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
- package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
- package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
- package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
- package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
- package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
- package/client/webhook-card.tsx +198 -0
- package/docs/specs/forge-workflow-gui.md +820 -0
- package/docs/workflow.md +339 -0
- package/examples/README.md +108 -0
- package/examples/hook-service/README.md +66 -0
- package/examples/hook-service/forge-hook.service +28 -0
- package/examples/hook-service/hook-server.mjs +265 -0
- package/examples/hook-service/hook.env.example +21 -0
- package/examples/labels/README.md +64 -0
- package/examples/labels/label-base.yaml +117 -0
- package/examples/skills/coding-agent/SKILL.md +262 -0
- package/examples/skills/coding-agent-fgjx/SKILL.md +271 -0
- package/examples/skills/orchestrator/SKILL.md +133 -0
- package/examples/skills/orchestrator-fgjx/SKILL.md +139 -0
- package/examples/tools/README.md +68 -0
- package/examples/tools/fgjx +464 -0
- package/package.json +38 -0
- package/paseo-plugin.json +4 -0
- package/server/forge-client.ts +484 -0
- package/server/forge-guard.ts +70 -0
- package/server/git-origin.ts +70 -0
- package/server/hook-queue.ts +127 -0
- package/server/issues.ts +542 -0
- package/server/settings.ts +52 -0
- package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
- package/server/vendor/paseo-plugin-helper/index.ts +18 -0
- package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
- package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
- package/server/vendor/paseo-plugin-helper/network.ts +91 -0
- package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
- package/server/vendor/paseo-plugin-helper/process.ts +186 -0
- package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
- package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
- package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
- package/server/vendor/paseo-plugin-helper/system.ts +128 -0
- package/server/vendor/paseo-plugin-helper/task.ts +116 -0
- package/server/vendor/paseo-plugin-helper/version.ts +153 -0
- package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
- package/shared/hook-queue.ts +140 -0
- package/shared/issues.ts +1750 -0
- package/shared/vendor/paseo-plugin-helper/README.md +11 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
- package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
- package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
- package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
- package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
- package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
- package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
- package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
- package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
- package/shared/version.ts +2 -0
- package/shared/webhook.ts +223 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { HostToast } from "../host";
|
|
2
|
+
import { getOptionalClientHost } from "../host";
|
|
3
|
+
|
|
4
|
+
export interface CopyToClipboardOptions {
|
|
5
|
+
toast?: HostToast;
|
|
6
|
+
toastMessage?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ClipboardTier = "navigator" | "host" | "rnAsync" | "rnSync" | "execCommand";
|
|
10
|
+
|
|
11
|
+
export interface ClipboardEnvironment {
|
|
12
|
+
hasNavigatorClipboard: boolean;
|
|
13
|
+
hasHostCopyText: boolean;
|
|
14
|
+
hasRnClipboard: boolean;
|
|
15
|
+
hasRnSetStringAsync: boolean;
|
|
16
|
+
isDom: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Deterministic tier order for an explicit copy.
|
|
21
|
+
*
|
|
22
|
+
* Web's `navigator.clipboard.writeText` is the only API that rejects when the
|
|
23
|
+
* clipboard did not change, so it leads. The synchronous
|
|
24
|
+
* `react-native-web` `Clipboard.setString` reports success even when its
|
|
25
|
+
* `document.execCommand("copy")` fails, which leaves the previous clipboard
|
|
26
|
+
* item in place while the UI claims success (xpufx-org/paseo#278); it is only
|
|
27
|
+
* usable off-DOM (native), where it is the real platform clipboard. In a DOM
|
|
28
|
+
* the checked `execCommand` fallback is preferred to that unverifiable path.
|
|
29
|
+
*/
|
|
30
|
+
export function clipboardTierOrder(env: ClipboardEnvironment): ClipboardTier[] {
|
|
31
|
+
const tiers: ClipboardTier[] = [];
|
|
32
|
+
if (env.hasNavigatorClipboard) tiers.push("navigator");
|
|
33
|
+
if (env.hasHostCopyText) tiers.push("host");
|
|
34
|
+
if (env.hasRnSetStringAsync) {
|
|
35
|
+
tiers.push("rnAsync");
|
|
36
|
+
} else if (env.hasRnClipboard && !env.isDom) {
|
|
37
|
+
tiers.push("rnSync");
|
|
38
|
+
}
|
|
39
|
+
tiers.push("execCommand");
|
|
40
|
+
return tiers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Robust cross-platform clipboard copy helper for Paseo plugins.
|
|
45
|
+
* Works seamlessly across React Native (mobile), web, and desktop.
|
|
46
|
+
*
|
|
47
|
+
* Tier order comes from `clipboardTierOrder`; every tier reports failure
|
|
48
|
+
* honestly so a denied or blocked write never leaves the previous clipboard
|
|
49
|
+
* item behind under a fake success.
|
|
50
|
+
*/
|
|
51
|
+
export async function copyToClipboard(
|
|
52
|
+
text: string,
|
|
53
|
+
options?: CopyToClipboardOptions,
|
|
54
|
+
): Promise<boolean> {
|
|
55
|
+
if (text === null || text === undefined) return false;
|
|
56
|
+
const str = String(text);
|
|
57
|
+
|
|
58
|
+
const globalObj = typeof globalThis !== "undefined" ? (globalThis as any) : {};
|
|
59
|
+
|
|
60
|
+
let rn: any;
|
|
61
|
+
try {
|
|
62
|
+
rn = require("react-native");
|
|
63
|
+
} catch {
|
|
64
|
+
// Ignore require error if not in RN context
|
|
65
|
+
}
|
|
66
|
+
const rnClipboard = rn?.Clipboard;
|
|
67
|
+
const rnSetStringAsync =
|
|
68
|
+
typeof rnClipboard?.setStringAsync === "function" ? rnClipboard.setStringAsync : undefined;
|
|
69
|
+
const rnSetString =
|
|
70
|
+
typeof rnClipboard?.setString === "function" ? rnClipboard.setString : undefined;
|
|
71
|
+
const copyText = getOptionalClientHost()?.copyText;
|
|
72
|
+
|
|
73
|
+
const isDom =
|
|
74
|
+
typeof globalObj.document !== "undefined" &&
|
|
75
|
+
typeof globalObj.document?.createElement === "function";
|
|
76
|
+
|
|
77
|
+
const tiers = clipboardTierOrder({
|
|
78
|
+
hasNavigatorClipboard: Boolean(globalObj.navigator?.clipboard?.writeText),
|
|
79
|
+
hasHostCopyText: Boolean(copyText),
|
|
80
|
+
hasRnClipboard: Boolean(rnSetStringAsync ?? rnSetString),
|
|
81
|
+
hasRnSetStringAsync: Boolean(rnSetStringAsync),
|
|
82
|
+
isDom,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
let success = false;
|
|
86
|
+
for (const tier of tiers) {
|
|
87
|
+
if (success) break;
|
|
88
|
+
try {
|
|
89
|
+
switch (tier) {
|
|
90
|
+
case "navigator":
|
|
91
|
+
await globalObj.navigator.clipboard.writeText(str);
|
|
92
|
+
success = true;
|
|
93
|
+
break;
|
|
94
|
+
case "host":
|
|
95
|
+
if (copyText) {
|
|
96
|
+
await copyText(str);
|
|
97
|
+
success = true;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
case "rnAsync":
|
|
101
|
+
// Expo's async API returns a boolean; a rejected promise falls through.
|
|
102
|
+
success = (await rnSetStringAsync(str)) !== false;
|
|
103
|
+
break;
|
|
104
|
+
case "rnSync":
|
|
105
|
+
// Only reached off-DOM, where this is the native platform clipboard.
|
|
106
|
+
rnSetString(str);
|
|
107
|
+
success = true;
|
|
108
|
+
break;
|
|
109
|
+
case "execCommand": {
|
|
110
|
+
const doc = globalObj.document;
|
|
111
|
+
if (doc?.createElement && doc?.body) {
|
|
112
|
+
const textarea = doc.createElement("textarea");
|
|
113
|
+
textarea.value = str;
|
|
114
|
+
textarea.style.position = "fixed";
|
|
115
|
+
textarea.style.opacity = "0";
|
|
116
|
+
textarea.style.left = "-9999px";
|
|
117
|
+
doc.body.appendChild(textarea);
|
|
118
|
+
textarea.focus();
|
|
119
|
+
textarea.select();
|
|
120
|
+
const res = doc.execCommand("copy");
|
|
121
|
+
doc.body.removeChild(textarea);
|
|
122
|
+
success = res === true;
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Tier failed; try the next one.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (success && options?.toast) {
|
|
133
|
+
try {
|
|
134
|
+
const toastAny = options.toast as any;
|
|
135
|
+
if (typeof toastAny.copied === "function") {
|
|
136
|
+
toastAny.copied(options.toastMessage);
|
|
137
|
+
} else if (typeof toastAny.show === "function") {
|
|
138
|
+
const msg = options.toastMessage
|
|
139
|
+
? `Copied ${options.toastMessage} to clipboard`
|
|
140
|
+
: "Copied to clipboard";
|
|
141
|
+
toastAny.show(msg, { variant: "success" });
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Ignore toast failure
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return success;
|
|
149
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type HapticFeedbackType =
|
|
2
|
+
| "light"
|
|
3
|
+
| "medium"
|
|
4
|
+
| "heavy"
|
|
5
|
+
| "success"
|
|
6
|
+
| "warning"
|
|
7
|
+
| "error";
|
|
8
|
+
|
|
9
|
+
const PATTERNS: Record<HapticFeedbackType, number | number[]> = {
|
|
10
|
+
light: 10,
|
|
11
|
+
medium: 25,
|
|
12
|
+
heavy: 45,
|
|
13
|
+
success: [15, 40, 20],
|
|
14
|
+
warning: [30, 50, 30],
|
|
15
|
+
error: [40, 60, 40, 60, 40],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Cross-platform haptic feedback helper for Paseo plugins.
|
|
20
|
+
* Supports web vibration API and graceful fallback when vibration is unavailable.
|
|
21
|
+
*/
|
|
22
|
+
export function triggerHaptic(type: HapticFeedbackType = "light"): boolean {
|
|
23
|
+
try {
|
|
24
|
+
const globalObj = typeof globalThis !== "undefined" ? (globalThis as any) : {};
|
|
25
|
+
const nav = globalObj.navigator;
|
|
26
|
+
if (typeof nav?.vibrate === "function") {
|
|
27
|
+
const pattern = PATTERNS[type] ?? 15;
|
|
28
|
+
return Boolean(nav.vibrate(pattern));
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Ignore environments where vibration permissions are denied or unavailable
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Linking, Pressable, StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import type {
|
|
4
|
+
PluginTimelineItemProps,
|
|
5
|
+
PluginTimelineRendererContribution,
|
|
6
|
+
PluginTimelineTransformerContribution,
|
|
7
|
+
} from "@getpaseo/plugin/client";
|
|
8
|
+
import type { PluginTheme } from "@getpaseo/plugin";
|
|
9
|
+
import { Badge, ForgeIcon, Icon } from "paseo-plugin-helper/client";
|
|
10
|
+
import {
|
|
11
|
+
forgejoWebhookCardSchema,
|
|
12
|
+
forgejoWebhookItem,
|
|
13
|
+
subjectLinkUrl,
|
|
14
|
+
type ForgejoSubject,
|
|
15
|
+
type ForgejoWebhookCardData,
|
|
16
|
+
} from "../shared/webhook.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Forgejo webhook timeline transformer: incoming hook deliveries become typed
|
|
20
|
+
* `forgejo-webhook` items so the composer renders the event card below instead
|
|
21
|
+
* of a raw chat line. Registered before the linkifier because hook messages
|
|
22
|
+
* carry a bare issue URL the linkifier would otherwise claim first.
|
|
23
|
+
*
|
|
24
|
+
* Presentation-only: `PluginTimelineTransformerContribution.transform` returns
|
|
25
|
+
* `PluginTimelineItem[]`, a client-side render target. It has no reference to
|
|
26
|
+
* mutate the source `AgentTimelineItem`, so the raw envelope + human line stays
|
|
27
|
+
* verbatim in the agent's context. Nothing below may echo that text.
|
|
28
|
+
*/
|
|
29
|
+
export const forgejoWebhookUserTransformer: PluginTimelineTransformerContribution<"user_message"> = {
|
|
30
|
+
id: "forgejo-webhook",
|
|
31
|
+
query: { itemType: "user_message" },
|
|
32
|
+
transform({ item }) {
|
|
33
|
+
return forgejoWebhookItem(item.text) as any;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function openUrl(url: string) {
|
|
38
|
+
Linking.openURL(url).catch(() => {});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function SubjectRow({ theme, subject }: { theme: PluginTheme; subject: ForgejoSubject }) {
|
|
42
|
+
const link = subjectLinkUrl(subject);
|
|
43
|
+
const onPress = link ? () => openUrl(link) : undefined;
|
|
44
|
+
const isPush = subject.kind === "push";
|
|
45
|
+
return (
|
|
46
|
+
<View style={styles.subjectRow}>
|
|
47
|
+
<Icon
|
|
48
|
+
name={isPush ? "GitCommit" : "GitPullRequest"}
|
|
49
|
+
size={13}
|
|
50
|
+
color={theme.colors.foregroundMuted}
|
|
51
|
+
/>
|
|
52
|
+
{subject.number != null ? <Badge variant="info" label={`#${subject.number}`} /> : null}
|
|
53
|
+
<View style={styles.subjectBody}>
|
|
54
|
+
{subject.title ? (
|
|
55
|
+
onPress ? (
|
|
56
|
+
<Pressable onPress={onPress} hitSlop={8} accessibilityRole="link">
|
|
57
|
+
<Text style={[styles.subjectTitle, styles.link, { color: theme.colors.accent }]}>
|
|
58
|
+
{subject.title}
|
|
59
|
+
</Text>
|
|
60
|
+
</Pressable>
|
|
61
|
+
) : (
|
|
62
|
+
<Text style={[styles.subjectTitle, { color: theme.colors.foreground }]}>
|
|
63
|
+
{subject.title}
|
|
64
|
+
</Text>
|
|
65
|
+
)
|
|
66
|
+
) : null}
|
|
67
|
+
{subject.ref ? (
|
|
68
|
+
<Text style={[styles.subjectMeta, { color: theme.colors.foregroundMuted }]}>
|
|
69
|
+
{subject.ref}
|
|
70
|
+
</Text>
|
|
71
|
+
) : null}
|
|
72
|
+
{subject.commits != null ? (
|
|
73
|
+
<Text style={[styles.subjectMeta, { color: theme.colors.foregroundMuted }]}>
|
|
74
|
+
{subject.commits} {subject.commits === 1 ? "commit" : "commits"}
|
|
75
|
+
</Text>
|
|
76
|
+
) : null}
|
|
77
|
+
{subject.commentId != null ? (
|
|
78
|
+
<Text style={[styles.subjectMeta, { color: theme.colors.foregroundMuted }]}>
|
|
79
|
+
comment #{subject.commentId}
|
|
80
|
+
</Text>
|
|
81
|
+
) : null}
|
|
82
|
+
{link ? (
|
|
83
|
+
<Pressable onPress={onPress} hitSlop={8} accessibilityRole="link">
|
|
84
|
+
<Text style={[styles.subjectUrl, styles.link, { color: theme.colors.accent }]}>
|
|
85
|
+
{link}
|
|
86
|
+
</Text>
|
|
87
|
+
</Pressable>
|
|
88
|
+
) : null}
|
|
89
|
+
</View>
|
|
90
|
+
</View>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function ForgejoWebhookCard({ item, theme }: PluginTimelineItemProps<ForgejoWebhookCardData>) {
|
|
95
|
+
const data = item.data;
|
|
96
|
+
const repoLink = data.repoUrl ? () => openUrl(data.repoUrl as string) : undefined;
|
|
97
|
+
return (
|
|
98
|
+
<View
|
|
99
|
+
style={[
|
|
100
|
+
styles.card,
|
|
101
|
+
{ backgroundColor: theme.colors.surface1, borderColor: theme.colors.border },
|
|
102
|
+
]}
|
|
103
|
+
>
|
|
104
|
+
<View style={styles.header}>
|
|
105
|
+
<ForgeIcon host={data.repoUrl} size={14} color={theme.colors.accent} />
|
|
106
|
+
<Text style={[styles.title, { color: theme.colors.foreground }]}>Forgejo webhook</Text>
|
|
107
|
+
<Badge
|
|
108
|
+
variant="info"
|
|
109
|
+
label={data.action ? `${data.event}:${data.action}` : data.event}
|
|
110
|
+
/>
|
|
111
|
+
</View>
|
|
112
|
+
<View style={styles.metaRow}>
|
|
113
|
+
{repoLink ? (
|
|
114
|
+
<Pressable onPress={repoLink} hitSlop={8} accessibilityRole="link">
|
|
115
|
+
<Text style={[styles.meta, styles.link, { color: theme.colors.accent }]}>
|
|
116
|
+
{data.repo}
|
|
117
|
+
</Text>
|
|
118
|
+
</Pressable>
|
|
119
|
+
) : (
|
|
120
|
+
<Text style={[styles.meta, { color: theme.colors.foregroundMuted }]}>{data.repo}</Text>
|
|
121
|
+
)}
|
|
122
|
+
{data.sender ? (
|
|
123
|
+
<Text style={[styles.meta, { color: theme.colors.foregroundMuted }]}>
|
|
124
|
+
· by {data.sender}
|
|
125
|
+
</Text>
|
|
126
|
+
) : null}
|
|
127
|
+
</View>
|
|
128
|
+
{data.subject ? <SubjectRow theme={theme} subject={data.subject} /> : null}
|
|
129
|
+
<Text style={[styles.footer, { color: theme.colors.foregroundMuted }]}>
|
|
130
|
+
via forges
|
|
131
|
+
</Text>
|
|
132
|
+
</View>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const forgejoWebhookRenderer: PluginTimelineRendererContribution<
|
|
137
|
+
typeof forgejoWebhookCardSchema
|
|
138
|
+
> = {
|
|
139
|
+
kind: "forgejo-webhook",
|
|
140
|
+
version: 1,
|
|
141
|
+
schema: forgejoWebhookCardSchema,
|
|
142
|
+
Component: ForgejoWebhookCard,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const styles = StyleSheet.create({
|
|
146
|
+
card: {
|
|
147
|
+
borderRadius: 8,
|
|
148
|
+
borderWidth: 1,
|
|
149
|
+
padding: 8,
|
|
150
|
+
marginVertical: 2,
|
|
151
|
+
gap: 6,
|
|
152
|
+
},
|
|
153
|
+
header: {
|
|
154
|
+
flexDirection: "row",
|
|
155
|
+
alignItems: "center",
|
|
156
|
+
gap: 6,
|
|
157
|
+
},
|
|
158
|
+
title: {
|
|
159
|
+
fontSize: 12,
|
|
160
|
+
fontWeight: "600",
|
|
161
|
+
flex: 1,
|
|
162
|
+
},
|
|
163
|
+
metaRow: {
|
|
164
|
+
flexDirection: "row",
|
|
165
|
+
alignItems: "center",
|
|
166
|
+
flexWrap: "wrap",
|
|
167
|
+
gap: 4,
|
|
168
|
+
},
|
|
169
|
+
meta: {
|
|
170
|
+
fontSize: 11,
|
|
171
|
+
},
|
|
172
|
+
subjectRow: {
|
|
173
|
+
flexDirection: "row",
|
|
174
|
+
alignItems: "flex-start",
|
|
175
|
+
gap: 8,
|
|
176
|
+
},
|
|
177
|
+
subjectBody: {
|
|
178
|
+
flex: 1,
|
|
179
|
+
gap: 2,
|
|
180
|
+
},
|
|
181
|
+
subjectTitle: {
|
|
182
|
+
fontSize: 13,
|
|
183
|
+
fontWeight: "600",
|
|
184
|
+
},
|
|
185
|
+
subjectMeta: {
|
|
186
|
+
fontSize: 11,
|
|
187
|
+
},
|
|
188
|
+
subjectUrl: {
|
|
189
|
+
fontSize: 11,
|
|
190
|
+
},
|
|
191
|
+
link: {
|
|
192
|
+
textDecorationLine: "underline",
|
|
193
|
+
},
|
|
194
|
+
footer: {
|
|
195
|
+
fontSize: 10,
|
|
196
|
+
fontStyle: "italic",
|
|
197
|
+
},
|
|
198
|
+
});
|