mirror of
https://github.com/morten-olsen/refocus.dev.git
synced 2026-02-08 00:46:25 +01:00
feat: markdown, github file and UI improvements
This commit is contained in:
58
packages/widgets/src/github/file/edit.tsx
Normal file
58
packages/widgets/src/github/file/edit.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Props } from './schema';
|
||||
|
||||
type EditorProps = {
|
||||
value?: Props;
|
||||
save: (data: Props) => void;
|
||||
};
|
||||
|
||||
const Edit: React.FC<EditorProps> = ({ value, save }) => {
|
||||
const [owner, setOwner] = useState(value?.owner || '');
|
||||
const [repo, setRepo] = useState(value?.repo || '');
|
||||
const [branch, setBranch] = useState(value?.branch || '');
|
||||
const [path, setPath] = useState(value?.path || '');
|
||||
const [highlight, setHighlight] = useState(value?.highlight || '');
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
save({
|
||||
owner,
|
||||
repo,
|
||||
branch,
|
||||
path,
|
||||
highlight,
|
||||
});
|
||||
}, [owner, repo, branch, path, highlight, save]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
placeholder="Owner"
|
||||
value={owner}
|
||||
onChange={(e) => setOwner(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="Repo"
|
||||
value={repo}
|
||||
onChange={(e) => setRepo(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="Branch"
|
||||
value={branch}
|
||||
onChange={(e) => setBranch(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="Path"
|
||||
value={path}
|
||||
onChange={(e) => setPath(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="Highlights"
|
||||
value={highlight}
|
||||
onChange={(e) => setHighlight(e.target.value)}
|
||||
/>
|
||||
<button onClick={handleSave}>Save</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Edit };
|
||||
29
packages/widgets/src/github/file/index.tsx
Normal file
29
packages/widgets/src/github/file/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Widget } from '@refocus/sdk';
|
||||
import { SiGithub } from 'react-icons/si';
|
||||
import { schema } from './schema';
|
||||
import { Edit } from './edit';
|
||||
import { View } from './view';
|
||||
|
||||
const widget: Widget<typeof schema> = {
|
||||
name: 'Github File',
|
||||
description: 'Display a file from a Github repository',
|
||||
icon: <SiGithub />,
|
||||
id: 'github.file',
|
||||
parseUrl: (url) => {
|
||||
if (url.hostname !== 'github.com') {
|
||||
return;
|
||||
}
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
const [owner, repo, type, branch, ...filePathParts] = pathParts.slice(0);
|
||||
const path = filePathParts.join('/');
|
||||
if (type !== 'blob' || !branch || !path) {
|
||||
return;
|
||||
}
|
||||
return { owner, repo, branch, path };
|
||||
},
|
||||
schema,
|
||||
component: View,
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export default widget;
|
||||
14
packages/widgets/src/github/file/schema.ts
Normal file
14
packages/widgets/src/github/file/schema.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const schema = Type.Object({
|
||||
owner: Type.String(),
|
||||
repo: Type.String(),
|
||||
path: Type.String(),
|
||||
branch: Type.String(),
|
||||
highlight: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
type Props = Static<typeof schema>;
|
||||
|
||||
export type { Props };
|
||||
export { schema };
|
||||
62
packages/widgets/src/github/file/view.tsx
Normal file
62
packages/widgets/src/github/file/view.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
useAutoUpdate,
|
||||
useGithubQuery,
|
||||
useName,
|
||||
withGithub,
|
||||
} from '@refocus/sdk';
|
||||
import { Props } from './schema';
|
||||
import { CodeEditor, Github, View } from '@refocus/ui';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
const FullHeight = styled(View)`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledCodeEditor = styled(CodeEditor)`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const WidgetView = withGithub<Props>(
|
||||
({ owner, repo, branch, path, highlight }) => {
|
||||
const [, setName] = useName();
|
||||
const { data, fetch } = useGithubQuery(async (client, params: Props) => {
|
||||
const response = await client.rest.repos.getContent({
|
||||
owner: params.owner,
|
||||
repo: params.repo,
|
||||
path: params.path,
|
||||
});
|
||||
setName(`${params.owner}/${params.repo}/${params.path}}`);
|
||||
return response.data;
|
||||
});
|
||||
|
||||
useAutoUpdate(
|
||||
{
|
||||
interval: 1000 * 60 * 5,
|
||||
action: async () => fetch({ owner, repo, branch, path }),
|
||||
},
|
||||
[owner, repo, branch, path],
|
||||
);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!data || !('type' in data) || data.type !== 'file') {
|
||||
return <div>Not a file</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<FullHeight $fc>
|
||||
<StyledCodeEditor
|
||||
highlight={highlight}
|
||||
value={atob(data.content)}
|
||||
setValue={() => {}}
|
||||
readOnly
|
||||
/>
|
||||
</FullHeight>
|
||||
);
|
||||
},
|
||||
Github.NotLoggedIn,
|
||||
);
|
||||
|
||||
export { WidgetView as View };
|
||||
@@ -4,6 +4,7 @@ import pullRequest from './pull-request/index.widget';
|
||||
import pullRequstComments from './pull-request-comments/index.widget';
|
||||
import workflowRun from './workflow-run/index.widget';
|
||||
import workflowRuns from './workflow-runs/index.widget';
|
||||
import file from './file';
|
||||
|
||||
const github = [
|
||||
githubProfileWidget,
|
||||
@@ -11,6 +12,7 @@ const github = [
|
||||
pullRequstComments,
|
||||
workflowRun,
|
||||
workflowRuns,
|
||||
file,
|
||||
] satisfies Widget<any>[];
|
||||
|
||||
export { github };
|
||||
|
||||
@@ -2,7 +2,13 @@ import { Widget } from '@refocus/sdk';
|
||||
import { github } from './github';
|
||||
import { linear } from './linear';
|
||||
import { slack } from './slack';
|
||||
import markdown from './markdown';
|
||||
|
||||
const widgets = [...linear, ...github, ...slack] satisfies Widget<any>[];
|
||||
const widgets = [
|
||||
...linear,
|
||||
...github,
|
||||
...slack,
|
||||
markdown,
|
||||
] satisfies Widget<any>[];
|
||||
|
||||
export { widgets };
|
||||
|
||||
@@ -10,10 +10,12 @@ const WidgetView = withLinear<LinearIssueProps>(({ id }) => {
|
||||
const issue = await client.issue(id);
|
||||
const assignee = await issue.assignee;
|
||||
const creator = await issue.creator;
|
||||
const state = await issue.state;
|
||||
return {
|
||||
issue,
|
||||
assignee,
|
||||
creator,
|
||||
state,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -34,6 +36,9 @@ const WidgetView = withLinear<LinearIssueProps>(({ id }) => {
|
||||
onClick={() => window.open(data?.issue.url, '_blank')}
|
||||
>
|
||||
<View>
|
||||
<Typography variant="tiny">
|
||||
{data?.state?.name} - {data?.issue.priorityLabel}
|
||||
</Typography>
|
||||
<Typography variant="title">{data?.issue?.title}</Typography>
|
||||
<Typography variant="tiny">
|
||||
{data?.issue.description?.substring(0, 100)}
|
||||
|
||||
36
packages/widgets/src/markdown/edit.tsx
Normal file
36
packages/widgets/src/markdown/edit.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { CodeEditor, Typography } from '@refocus/ui';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Props } from './schema';
|
||||
|
||||
type EditorProps = {
|
||||
value?: Props;
|
||||
save: (data: Props) => void;
|
||||
};
|
||||
|
||||
const Edit: React.FC<EditorProps> = ({ value, save }) => {
|
||||
const [markdown, setMarkdown] = useState(value?.markdown || '');
|
||||
const [name, setName] = useState(value?.name || '');
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
save({
|
||||
name,
|
||||
markdown,
|
||||
});
|
||||
}, [markdown, save, name]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography
|
||||
as="input"
|
||||
$u
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<CodeEditor language="markdown" value={markdown} setValue={setMarkdown} />
|
||||
<button onClick={handleSave}>Save</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Edit };
|
||||
17
packages/widgets/src/markdown/index.tsx
Normal file
17
packages/widgets/src/markdown/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Widget } from '@refocus/sdk';
|
||||
import { IoLogoMarkdown } from 'react-icons/io';
|
||||
import { schema } from './schema';
|
||||
import { Edit } from './edit';
|
||||
import { View } from './view';
|
||||
|
||||
const widget: Widget<typeof schema> = {
|
||||
name: 'Markdown',
|
||||
description: 'A markdown note',
|
||||
icon: <IoLogoMarkdown />,
|
||||
id: 'text.markdown',
|
||||
schema,
|
||||
component: View,
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export default widget;
|
||||
11
packages/widgets/src/markdown/schema.ts
Normal file
11
packages/widgets/src/markdown/schema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const schema = Type.Object({
|
||||
name: Type.String(),
|
||||
markdown: Type.String(),
|
||||
});
|
||||
|
||||
type Props = Static<typeof schema>;
|
||||
|
||||
export type { Props };
|
||||
export { schema };
|
||||
21
packages/widgets/src/markdown/view.tsx
Normal file
21
packages/widgets/src/markdown/view.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useName } from '@refocus/sdk';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Props } from './schema';
|
||||
import { useEffect } from 'react';
|
||||
import { View } from '@refocus/ui';
|
||||
|
||||
const WidgetView: React.FC<Props> = ({ markdown, name }) => {
|
||||
const [, setName] = useName();
|
||||
|
||||
useEffect(() => {
|
||||
setName(name);
|
||||
}, [name, setName]);
|
||||
|
||||
return (
|
||||
<View $px="md">
|
||||
<ReactMarkdown>{markdown}</ReactMarkdown>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { WidgetView as View };
|
||||
Reference in New Issue
Block a user