mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 17:52:49 +00:00
feat(plugin): render Markdown content with host UI (#7579)
* feat(plugin): expose Markdown content component * docs(plugin): translate Markdown component reference
This commit is contained in:
@@ -10,7 +10,7 @@ import { connectNewWorkspaceDaemonClient } from "../support/helpers/new-workspac
|
||||
const PLUGIN_ID = "plugin-host-ui-e2e";
|
||||
|
||||
const PLUGIN_SOURCE = `import { usePaseo } from "@getpaseo/plugin/client";
|
||||
import { Icon, Modal, useToast } from "@getpaseo/plugin/client/ui";
|
||||
import { Icon, Modal, useToast, Markdown } from "@getpaseo/plugin/client/ui";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import React, { useState } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
@@ -27,6 +27,9 @@ function ModalBody({ onSaved }) {
|
||||
|
||||
return <View>
|
||||
<Text>Plugin modal contexts ready</Text>
|
||||
<Markdown>{"## Plugin report"}</Markdown>
|
||||
<Markdown>{"Checks **passed**. [Documentation](https://paseo.sh/docs/plugins)"}</Markdown>
|
||||
<Markdown>{""}</Markdown>
|
||||
<Pressable accessibilityRole="button" onPress={save}>
|
||||
<Text>Save issue</Text>
|
||||
</Pressable>
|
||||
@@ -92,10 +95,20 @@ async function openPluginModal(page: Page): Promise<void> {
|
||||
await page.getByRole("button", { name: "Open plugin modal", exact: true }).click();
|
||||
}
|
||||
|
||||
async function expectPluginMarkdown(page: Page): Promise<void> {
|
||||
await expect(page.getByRole("heading", { name: "Plugin report" })).toBeVisible();
|
||||
await expect(page.getByText("Checks passed.", { exact: false })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "Documentation", exact: true })).toHaveAttribute(
|
||||
"href",
|
||||
"https://paseo.sh/docs/plugins",
|
||||
);
|
||||
}
|
||||
|
||||
async function expectCenteredPluginDialog(page: Page): Promise<void> {
|
||||
const dialog = page.getByRole("dialog");
|
||||
await expect(dialog.getByText("Edit plugin issue", { exact: true })).toBeVisible();
|
||||
await expect(dialog.getByText("Plugin modal contexts ready", { exact: true })).toBeVisible();
|
||||
await expectPluginMarkdown(page);
|
||||
}
|
||||
|
||||
async function closeCenteredPluginDialog(page: Page): Promise<void> {
|
||||
@@ -107,6 +120,7 @@ async function closeCenteredPluginDialog(page: Page): Promise<void> {
|
||||
async function expectCompactPluginSheet(page: Page): Promise<void> {
|
||||
await expect(page.getByText("Edit plugin issue", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Plugin modal contexts ready", { exact: true })).toBeVisible();
|
||||
await expectPluginMarkdown(page);
|
||||
}
|
||||
|
||||
async function savePluginIssue(page: Page): Promise<void> {
|
||||
|
||||
@@ -695,7 +695,8 @@ describe("evaluatePluginClientBundle", () => {
|
||||
`(function(require) {
|
||||
const shared = require("@getpaseo/plugin");
|
||||
const client = require("@getpaseo/plugin/client");
|
||||
const { ExternalLink, Loading } = require("@getpaseo/plugin/client/ui");
|
||||
const { ExternalLink, Loading, Markdown } = require("@getpaseo/plugin/client/ui");
|
||||
if (typeof Markdown !== "function") throw new Error("Markdown");
|
||||
if (typeof ExternalLink !== "function") throw new Error("ExternalLink");
|
||||
if (typeof Loading !== "function") throw new Error("Loading");
|
||||
for (const name of ["usePaseo", "useRpc", "useSettings", "useAgent", "useWorkspace", "openExternalUrl", "useFileText", "useFileBytes", "useFileUrl", "PluginFileConflictError", "PluginFileDeletedError"]) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { MarkdownProps } from "@getpaseo/plugin/client/ui";
|
||||
import { MarkdownRenderer } from "@/components/markdown/renderer";
|
||||
|
||||
export function Markdown({ children }: MarkdownProps) {
|
||||
return children ? <MarkdownRenderer text={children} /> : null;
|
||||
}
|
||||
@@ -26,3 +26,5 @@ export { TextInput } from "./text-input";
|
||||
export { copyText } from "./clipboard";
|
||||
export { useToast } from "./toast";
|
||||
export { StreamedText } from "./streamed-text";
|
||||
|
||||
export { Markdown } from "./markdown";
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { lightHighlightColors } from "@getpaseo/highlight";
|
||||
|
||||
const testTheme = {
|
||||
colorScheme: "light",
|
||||
colors: {
|
||||
syntax: lightHighlightColors,
|
||||
foreground: "#111111",
|
||||
foregroundMuted: "#666666",
|
||||
statusSuccess: "#15803d",
|
||||
|
||||
@@ -104,6 +104,7 @@ export default defineConfig({
|
||||
deps: {
|
||||
fallbackCJS: true,
|
||||
inline: [
|
||||
"react-native-enriched-markdown",
|
||||
"zustand",
|
||||
"@tanstack/react-query",
|
||||
"react-native-web",
|
||||
|
||||
@@ -178,3 +178,10 @@ export declare const Loading: ComponentType;
|
||||
|
||||
/** Render a local document URL in an isolated frame. */
|
||||
export declare const DocumentFrame: ComponentType<{ url: string; testID?: string }>;
|
||||
|
||||
export interface MarkdownProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
/** Renders Markdown using Paseo’s theme, selection, and link behavior. */
|
||||
export declare const Markdown: ComponentType<MarkdownProps>;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@getpaseo/plugin/client";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { ExternalLink } from "@getpaseo/plugin/client/ui";
|
||||
import { ExternalLink, Markdown } from "@getpaseo/plugin/client/ui";
|
||||
import { incrementRpc } from "../shared/increment";
|
||||
|
||||
export function contributeClient(client: PluginClientContext) {
|
||||
@@ -107,7 +107,7 @@ export function ExamplePanel({
|
||||
|
||||
return (
|
||||
<View style={styles.screen}>
|
||||
<Text style={styles.title}>Workspace plugin panel</Text>
|
||||
<Markdown>{"## Workspace plugin panel\n\nCall a **daemon RPC** from this panel."}</Markdown>
|
||||
<ExternalLink href="https://paseo.sh/docs/plugins">Plugin documentation</ExternalLink>
|
||||
{openBrowser ? (
|
||||
<Pressable accessibilityRole="button" onPress={handleOpenBrowser} style={styles.button}>
|
||||
|
||||
@@ -1220,6 +1220,26 @@ export function IssueActions({ theme }: PluginSurfaceProps) {
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown
|
||||
|
||||
Import `Markdown` from `@getpaseo/plugin/client/ui` to render a string with Paseo's
|
||||
Markdown presentation on web, iOS, and Android:
|
||||
|
||||
```tsx
|
||||
import { Markdown } from "@getpaseo/plugin/client/ui";
|
||||
|
||||
<Markdown>{"## Review\n\nThe checks **passed**."}</Markdown>;
|
||||
```
|
||||
|
||||
`children` is the only prop and must be a string. Updating it renders the new content
|
||||
immediately; an empty string renders nothing. The host supplies theme, text selection,
|
||||
code blocks, tables, diagrams, and link handling. Workspace file links and images use
|
||||
workspace context when the containing surface supplies it.
|
||||
|
||||
The component adds no page padding or scroll container. Use a surrounding layout or
|
||||
scroll view when needed. Fetching content and showing loading or error states belong
|
||||
to your plugin.
|
||||
|
||||
### Modal
|
||||
|
||||
`Modal` uses a bottom sheet on compact layouts and a centered dialog otherwise. The plugin owns
|
||||
|
||||
@@ -1061,6 +1061,24 @@ export function IssueActions({ theme }: PluginSurfaceProps) {
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown {#markdown}
|
||||
|
||||
从 `@getpaseo/plugin/client/ui` 导入 `Markdown`,即可在网页、iOS 和 Android 上
|
||||
使用 Paseo 的 Markdown 样式渲染字符串:
|
||||
|
||||
```tsx
|
||||
import { Markdown } from "@getpaseo/plugin/client/ui";
|
||||
|
||||
<Markdown>{"## 审查\n\n检查**通过**。"}</Markdown>;
|
||||
```
|
||||
|
||||
`children` 是唯一的属性,必须为字符串。更新它会立即渲染新内容;空字符串不渲染任何内容。
|
||||
宿主提供主题、文本选择、代码块、表格、图表和链接处理。
|
||||
当所在界面提供工作区上下文时,工作区文件链接和图片会使用该上下文。
|
||||
|
||||
组件不添加页面内边距或滚动容器。需要时请在外层使用布局或滚动视图。
|
||||
内容获取以及加载和错误状态由插件负责。
|
||||
|
||||
### Modal {#modal}
|
||||
|
||||
`Modal` 在紧凑布局中使用底部面板,否则使用居中的对话框。`open` 状态由插件负责。
|
||||
|
||||
Reference in New Issue
Block a user