@hienlh/ppm 0.17.29 → 0.17.30
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/CHANGELOG.md +5 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{ai-resource-editor-BCgyv1KF.js → ai-resource-editor-D1efMh5f.js} +1 -1
- package/dist/web/assets/{audio-preview-BPuiX8ma.js → audio-preview-BfaIfDsY.js} +1 -1
- package/dist/web/assets/chat-tab-CVfcHLiQ.js +13 -0
- package/dist/web/assets/code-editor-hKFw2M2d.js +10 -0
- package/dist/web/assets/{conflict-editor-BuLnwxFE.js → conflict-editor-BRDEJEq6.js} +1 -1
- package/dist/web/assets/{csv-preview-DdA_IpTo.js → csv-preview-D32vaKWU.js} +1 -1
- package/dist/web/assets/{database-viewer-BcF3uuk5.js → database-viewer-B-ukNIhW.js} +1 -1
- package/dist/web/assets/{diff-viewer-tyMHX20V.js → diff-viewer-DfFzvZND.js} +1 -1
- package/dist/web/assets/{docx-preview-BTclTd2v.js → docx-preview-B1kTBbEc.js} +1 -1
- package/dist/web/assets/{extension-webview-CBLWXhjt.js → extension-webview-mQPWwlxb.js} +1 -1
- package/dist/web/assets/{git-log-panel-tmGgY2kK.js → git-log-panel-CIB6xuoc.js} +1 -1
- package/dist/web/assets/glide-data-grid-DM0v58AP.js +138 -0
- package/dist/web/assets/group-chat-tab-pfDZpGO_.js +1 -0
- package/dist/web/assets/{image-preview-CQ57RN6l.js → image-preview-Dxvk6fiw.js} +1 -1
- package/dist/web/assets/index-Cp6hb7uj.js +50 -0
- package/dist/web/assets/keybindings-store-BDQFSrPz.js +1 -0
- package/dist/web/assets/{markdown-renderer-devgS0zw.js → markdown-renderer-TCeFZDxj.js} +2 -2
- package/dist/web/assets/{markdown-renderer-C7clwuDe.js → markdown-renderer-zRn368SN.js} +1 -1
- package/dist/web/assets/notification-store-BcEM83wQ.js +1 -0
- package/dist/web/assets/{pdf-preview-Cw_GRd7e.js → pdf-preview-Bx_RsWdB.js} +1 -1
- package/dist/web/assets/{postgres-viewer-DN40Bdc-.js → postgres-viewer-DJXDXZbE.js} +2 -2
- package/dist/web/assets/{settings-tab-DkpOd_kU.js → settings-tab-CAJ4skP0.js} +1 -1
- package/dist/web/assets/{sql-query-editor-Df1pzXAj.js → sql-query-editor-UDQo7Bck.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-Dbh3LluT.js → sqlite-viewer-BaQq64OX.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-bbct9aEk.js → system-monitor-tab-P1uy84Os.js} +1 -1
- package/dist/web/assets/terminal-tab-mI-vAmOV.js +3 -0
- package/dist/web/assets/{tool-cards-BonPvpUI.js → tool-cards-B6jz0GZE.js} +2 -2
- package/dist/web/assets/{use-monaco-theme-Cqo17yKA.js → use-monaco-theme-CZKIw2ZG.js} +1 -1
- package/dist/web/assets/{video-preview-kZgggFAn.js → video-preview-TGDSUs0J.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/web/components/chat/account-dialogs.tsx +3 -3
- package/src/web/components/chat/chat-history-bar.tsx +4 -2
- package/src/web/components/chat/message-action-bar.tsx +2 -1
- package/src/web/components/database/connection-import-export.tsx +5 -2
- package/src/web/components/database/export-button.tsx +4 -3
- package/src/web/components/explorer/file-tree.tsx +3 -2
- package/src/web/components/layout/cloud-share-popover.tsx +2 -1
- package/src/web/components/layout/mobile-nav.tsx +3 -2
- package/src/web/components/layout/project-bottom-sheet.tsx +2 -1
- package/src/web/components/layout/project-switcher.tsx +2 -1
- package/src/web/components/layout/tab-bar.tsx +3 -2
- package/src/web/components/settings/accounts-settings-section.tsx +3 -3
- package/src/web/components/settings/proxy-settings-section.tsx +8 -7
- package/src/web/components/shared/bug-report-popup.tsx +2 -1
- package/src/web/components/shared/markdown-code-block.tsx +3 -2
- package/src/web/components/terminal/terminal-tab.tsx +2 -1
- package/src/web/components/tunnels/tunnel-manager-tab.tsx +4 -2
- package/src/web/lib/clipboard.ts +52 -0
- package/src/web/lib/report-bug.ts +0 -10
- package/dist/web/assets/chat-tab-WnnM6GKA.js +0 -13
- package/dist/web/assets/code-editor-CnGUyOuU.js +0 -10
- package/dist/web/assets/glide-data-grid-CkCW7sFw.js +0 -138
- package/dist/web/assets/group-chat-tab-D_x5CRae.js +0 -1
- package/dist/web/assets/index-Cp3e9olf.js +0 -50
- package/dist/web/assets/keybindings-store-CpgEwnFA.js +0 -1
- package/dist/web/assets/notification-store-BohufOcS.js +0 -1
- package/dist/web/assets/terminal-tab-Cqs7xZJt.js +0 -3
- package/skills-lock.json +0 -11
- package/spike-memory-region-dump.ps1 +0 -69
|
@@ -43,6 +43,7 @@ import { DraggableTab } from "./draggable-tab";
|
|
|
43
43
|
import { cn } from "@/lib/utils";
|
|
44
44
|
import type { Tab } from "@/stores/tab-store";
|
|
45
45
|
import { downloadFile } from "@/lib/file-download";
|
|
46
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
46
47
|
import { FileActions } from "@/components/explorer/file-actions";
|
|
47
48
|
import {
|
|
48
49
|
ContextMenu as BarContextMenu,
|
|
@@ -278,7 +279,7 @@ export const TabBar = memo(function TabBar({ panelId }: TabBarProps) {
|
|
|
278
279
|
}
|
|
279
280
|
case "copy-path": {
|
|
280
281
|
const filePath = tab.metadata?.filePath as string | undefined;
|
|
281
|
-
if (filePath)
|
|
282
|
+
if (filePath) void copyToClipboard(filePath);
|
|
282
283
|
break;
|
|
283
284
|
}
|
|
284
285
|
case "copy-full-path": {
|
|
@@ -286,7 +287,7 @@ export const TabBar = memo(function TabBar({ panelId }: TabBarProps) {
|
|
|
286
287
|
const projectName = tab.metadata?.projectName as string | undefined;
|
|
287
288
|
if (filePath) {
|
|
288
289
|
const project = projectName ? useProjectStore.getState().projects.find((p) => p.name === projectName) : null;
|
|
289
|
-
|
|
290
|
+
void copyToClipboard(project ? `${project.path}/${filePath}` : filePath);
|
|
290
291
|
}
|
|
291
292
|
break;
|
|
292
293
|
}
|
|
@@ -8,6 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|
|
8
8
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
|
|
9
9
|
import { Eye, Loader2, Copy, X, Download, Upload, Lock, FlaskConical } from "lucide-react";
|
|
10
10
|
import { getAuthToken } from "../../lib/api-client";
|
|
11
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
11
12
|
import {
|
|
12
13
|
getAccounts,
|
|
13
14
|
getActiveAccount,
|
|
@@ -347,10 +348,9 @@ export function AccountsSettingsSection() {
|
|
|
347
348
|
if (!res.ok) { const j = await res.json() as any; throw new Error(j.error ?? `Export failed: ${res.status}`); }
|
|
348
349
|
const text = await res.text();
|
|
349
350
|
if (toClipboard) {
|
|
350
|
-
|
|
351
|
-
await navigator.clipboard.writeText(text);
|
|
351
|
+
if (await copyToClipboard(text)) {
|
|
352
352
|
showMessage({ type: "success", text: "Backup copied to clipboard!" });
|
|
353
|
-
}
|
|
353
|
+
} else {
|
|
354
354
|
const blob = new Blob([text], { type: "application/json" });
|
|
355
355
|
const a = document.createElement("a");
|
|
356
356
|
a.href = URL.createObjectURL(blob);
|
|
@@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input";
|
|
|
5
5
|
import { Label } from "@/components/ui/label";
|
|
6
6
|
import { Switch } from "@/components/ui/switch";
|
|
7
7
|
import { getProxySettings, updateProxySettings, type ProxySettings } from "@/lib/api-settings";
|
|
8
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
8
9
|
import { ProxyTestButton } from "./proxy-test-section";
|
|
9
10
|
|
|
10
11
|
export function ProxySettingsSection() {
|
|
@@ -37,8 +38,8 @@ export function ProxySettingsSection() {
|
|
|
37
38
|
}
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
-
|
|
41
|
+
const handleCopy = (text: string, label: string) => {
|
|
42
|
+
void copyToClipboard(text);
|
|
42
43
|
setCopied(label);
|
|
43
44
|
setTimeout(() => setCopied(null), 2000);
|
|
44
45
|
};
|
|
@@ -96,7 +97,7 @@ export function ProxySettingsSection() {
|
|
|
96
97
|
variant="outline"
|
|
97
98
|
size="sm"
|
|
98
99
|
className="h-7 px-2 cursor-pointer shrink-0"
|
|
99
|
-
onClick={() =>
|
|
100
|
+
onClick={() => handleCopy(settings.authKey!, "key")}
|
|
100
101
|
>
|
|
101
102
|
{copied === "key" ? "Copied!" : <Copy className="size-3" />}
|
|
102
103
|
</Button>
|
|
@@ -145,7 +146,7 @@ export function ProxySettingsSection() {
|
|
|
145
146
|
variant="ghost"
|
|
146
147
|
size="sm"
|
|
147
148
|
className="h-6 px-1.5 cursor-pointer shrink-0"
|
|
148
|
-
onClick={() =>
|
|
149
|
+
onClick={() => handleCopy(hasTunnel ? settings.proxyEndpoint! : localEndpoint, "anthropic")}
|
|
149
150
|
>
|
|
150
151
|
{copied === "anthropic" ? "Copied!" : <Copy className="size-3" />}
|
|
151
152
|
</Button>
|
|
@@ -163,7 +164,7 @@ export function ProxySettingsSection() {
|
|
|
163
164
|
variant="ghost"
|
|
164
165
|
size="sm"
|
|
165
166
|
className="h-6 px-1.5 cursor-pointer shrink-0"
|
|
166
|
-
onClick={() =>
|
|
167
|
+
onClick={() => handleCopy(
|
|
167
168
|
hasTunnel ? settings.openAiEndpoint! : settings.localOpenAiEndpoint, "openai",
|
|
168
169
|
)}
|
|
169
170
|
>
|
|
@@ -190,7 +191,7 @@ ANTHROPIC_API_KEY=${settings.authKey}`}
|
|
|
190
191
|
variant="ghost"
|
|
191
192
|
size="sm"
|
|
192
193
|
className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
|
|
193
|
-
onClick={() =>
|
|
194
|
+
onClick={() => handleCopy(
|
|
194
195
|
`ANTHROPIC_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy" : localBaseUrl + "/proxy"}\nANTHROPIC_API_KEY=${settings.authKey}`,
|
|
195
196
|
"anthropic-env",
|
|
196
197
|
)}
|
|
@@ -211,7 +212,7 @@ OPENAI_API_KEY=${settings.authKey}`}
|
|
|
211
212
|
variant="ghost"
|
|
212
213
|
size="sm"
|
|
213
214
|
className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
|
|
214
|
-
onClick={() =>
|
|
215
|
+
onClick={() => handleCopy(
|
|
215
216
|
`OPENAI_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy/v1" : localBaseUrl + "/proxy/v1"}\nOPENAI_API_KEY=${settings.authKey}`,
|
|
216
217
|
"openai-env",
|
|
217
218
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { Copy, ExternalLink, X, Check } from "lucide-react";
|
|
3
|
-
import { openGithubIssue
|
|
3
|
+
import { openGithubIssue } from "@/lib/report-bug";
|
|
4
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
4
5
|
|
|
5
6
|
export function BugReportPopup() {
|
|
6
7
|
const [text, setText] = useState<string | null>(null);
|
|
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef, type ReactNode } from "react";
|
|
|
2
2
|
import { useMdContext, FILE_EXT_RE, GLOB_CHARS_RE } from "./markdown-context";
|
|
3
3
|
import { useTabStore } from "@/stores/tab-store";
|
|
4
4
|
import { highlightToHtml, highlightSync, getActiveShikiTheme } from "@/theme/adapters/shiki-adapter";
|
|
5
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Shiki-highlighted HTML for a code block. Returns null while streaming or
|
|
@@ -88,13 +89,13 @@ export function MdPre({ children, node, ...rest }: any) {
|
|
|
88
89
|
: <pre {...rest}>{children}</pre>}
|
|
89
90
|
{codeActions && (
|
|
90
91
|
<div className="code-actions absolute top-1 right-1 flex gap-1">
|
|
91
|
-
<ActionBtn title="Copy" icon={<CopyIcon />} activeIcon={<CheckIcon />} onClick={() =>
|
|
92
|
+
<ActionBtn title="Copy" icon={<CopyIcon />} activeIcon={<CheckIcon />} onClick={() => void copyToClipboard(text)} />
|
|
92
93
|
{isBash && projectName && (
|
|
93
94
|
<ActionBtn
|
|
94
95
|
title="Run in terminal"
|
|
95
96
|
icon={<PlayIcon />}
|
|
96
97
|
onClick={() => {
|
|
97
|
-
|
|
98
|
+
void copyToClipboard(text.replace(/^\$\s*/gm, ""));
|
|
98
99
|
openTab({ type: "terminal", title: "Terminal", metadata: { projectName }, projectId: projectName, closable: true });
|
|
99
100
|
}}
|
|
100
101
|
/>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useRef, useEffect, useState, useCallback, memo } from "react";
|
|
2
2
|
import { useTerminal } from "@/hooks/use-terminal";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
4
5
|
import { Copy, ClipboardPaste, RotateCcw, MessageSquare } from "lucide-react";
|
|
5
6
|
import "@xterm/xterm/css/xterm.css";
|
|
6
7
|
import { toast } from "sonner";
|
|
@@ -88,7 +89,7 @@ export const TerminalTab = memo(function TerminalTab({ metadata, tabId }: Termin
|
|
|
88
89
|
const handleCopy = useCallback(async () => {
|
|
89
90
|
const selection = getSelection();
|
|
90
91
|
if (selection) {
|
|
91
|
-
await
|
|
92
|
+
await copyToClipboard(selection);
|
|
92
93
|
}
|
|
93
94
|
}, [getSelection]);
|
|
94
95
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { Check, Copy, ExternalLink, Globe, Loader2, Lock, RefreshCw, Square } from "lucide-react";
|
|
3
3
|
import { tunnelsApi, type TunnelEntry } from "@/lib/api-tunnels";
|
|
4
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
4
5
|
import { toast } from "sonner";
|
|
5
6
|
|
|
6
7
|
/** Badge label + style per tunnel source. */
|
|
@@ -73,11 +74,12 @@ export function TunnelManagerTab() {
|
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
const copyUrl = (pid: number, url: string) => {
|
|
76
|
-
|
|
77
|
+
copyToClipboard(url).then((ok) => {
|
|
78
|
+
if (!ok) { toast.error("Failed to copy URL"); return; }
|
|
77
79
|
setCopiedPid(pid);
|
|
78
80
|
toast.success("URL copied");
|
|
79
81
|
setTimeout(() => setCopiedPid(null), 2000);
|
|
80
|
-
})
|
|
82
|
+
});
|
|
81
83
|
};
|
|
82
84
|
|
|
83
85
|
const handleSubmit = (e: React.FormEvent) => {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clipboard write with an insecure-context fallback.
|
|
3
|
+
*
|
|
4
|
+
* `navigator.clipboard` only exists in a secure context (HTTPS or localhost).
|
|
5
|
+
* PPM is regularly reached over plain HTTP — LAN IP, Tailscale MagicDNS name —
|
|
6
|
+
* where the API is absent and every copy button silently no-ops. The legacy
|
|
7
|
+
* `execCommand` path keeps copy working on those origins.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Copy text to the clipboard. Returns false only if both paths fail. */
|
|
11
|
+
export async function copyToClipboard(text: string): Promise<boolean> {
|
|
12
|
+
if (navigator.clipboard?.writeText) {
|
|
13
|
+
try {
|
|
14
|
+
await navigator.clipboard.writeText(text);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
// Denied permission or lost user activation — fall through to execCommand.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return legacyCopy(text);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function legacyCopy(text: string): boolean {
|
|
24
|
+
const textarea = document.createElement("textarea");
|
|
25
|
+
textarea.value = text;
|
|
26
|
+
textarea.setAttribute("readonly", "");
|
|
27
|
+
// Must stay rendered and non-zero sized: iOS Safari refuses to select a
|
|
28
|
+
// hidden or display:none element, which breaks the copy.
|
|
29
|
+
textarea.style.cssText =
|
|
30
|
+
"position:fixed;top:0;left:0;width:1px;height:1px;padding:0;border:none;opacity:0;";
|
|
31
|
+
document.body.appendChild(textarea);
|
|
32
|
+
|
|
33
|
+
const selection = document.getSelection();
|
|
34
|
+
const previousRange =
|
|
35
|
+
selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
textarea.focus();
|
|
39
|
+
textarea.select();
|
|
40
|
+
// setSelectionRange is what actually selects on iOS; select() alone is ignored.
|
|
41
|
+
textarea.setSelectionRange(0, text.length);
|
|
42
|
+
return document.execCommand("copy");
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
} finally {
|
|
46
|
+
textarea.remove();
|
|
47
|
+
if (selection && previousRange) {
|
|
48
|
+
selection.removeAllRanges();
|
|
49
|
+
selection.addRange(previousRange);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -60,16 +60,6 @@ export function openGithubIssue(body: string) {
|
|
|
60
60
|
window.open(url, "_blank");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/** Copy text to clipboard */
|
|
64
|
-
export async function copyToClipboard(text: string): Promise<boolean> {
|
|
65
|
-
try {
|
|
66
|
-
await navigator.clipboard.writeText(text);
|
|
67
|
-
return true;
|
|
68
|
-
} catch {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
63
|
/** Open bug report popup globally via custom event */
|
|
74
64
|
export function openBugReportPopup(
|
|
75
65
|
version: string | null,
|