feat: feed add markdown support
This commit is contained in:
parent
4d15cccd1b
commit
56bbe09005
@ -311,6 +311,9 @@ importers:
|
|||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^6.15.0
|
specifier: ^6.15.0
|
||||||
version: 6.15.0(react-dom@18.2.0)(react@18.2.0)
|
version: 6.15.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
rehype-external-links:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0
|
||||||
socket.io-client:
|
socket.io-client:
|
||||||
specifier: ^4.7.2
|
specifier: ^4.7.2
|
||||||
version: 4.7.2
|
version: 4.7.2
|
||||||
@ -5013,7 +5016,6 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
@ -5023,7 +5025,6 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
@ -5033,7 +5034,6 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
@ -5043,7 +5043,6 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
@ -13642,6 +13641,12 @@ packages:
|
|||||||
vfile-location: 5.0.2
|
vfile-location: 5.0.2
|
||||||
web-namespaces: 2.0.1
|
web-namespaces: 2.0.1
|
||||||
|
|
||||||
|
/hast-util-is-element@3.0.0:
|
||||||
|
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/hast-util-parse-selector@3.1.1:
|
/hast-util-parse-selector@3.1.1:
|
||||||
resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
|
resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -14330,6 +14335,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-QGOS8MRMnj/UiOa+aMIgfyHcvkhqNUsUxb1XzskENvbo+rEfp6TOwqd1KPuDzXC4OnGHcMSVxDGRoilqB8ViqA==}
|
resolution: {integrity: sha512-QGOS8MRMnj/UiOa+aMIgfyHcvkhqNUsUxb1XzskENvbo+rEfp6TOwqd1KPuDzXC4OnGHcMSVxDGRoilqB8ViqA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/is-absolute-url@4.0.1:
|
||||||
|
resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-absolute@1.0.0:
|
/is-absolute@1.0.0:
|
||||||
resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==}
|
resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -21072,6 +21082,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-bEAtp/qrtKucxXSJkD4ebopFZYP0q1+3Vb2WECWv/T8yQEgKxDxJ7ztO285tAMaYZVR6mM1GgI6CCn8FROtL1w==}
|
resolution: {integrity: sha512-bEAtp/qrtKucxXSJkD4ebopFZYP0q1+3Vb2WECWv/T8yQEgKxDxJ7ztO285tAMaYZVR6mM1GgI6CCn8FROtL1w==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/rehype-external-links@3.0.0:
|
||||||
|
resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==}
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@ungap/structured-clone': 1.2.0
|
||||||
|
hast-util-is-element: 3.0.0
|
||||||
|
is-absolute-url: 4.0.1
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/rehype-raw@6.1.1:
|
/rehype-raw@6.1.1:
|
||||||
resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==}
|
resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Editor, EditorProps } from '@bytemd/react';
|
import { Editor, EditorProps } from '@bytemd/react';
|
||||||
import { plugins } from './plugins';
|
import { plugins } from './plugins';
|
||||||
import 'bytemd/dist/index.css';
|
|
||||||
import './style.less';
|
|
||||||
import { useLocale } from './useLocale';
|
import { useLocale } from './useLocale';
|
||||||
|
|
||||||
interface MarkdownEditorProps extends EditorProps {}
|
interface MarkdownEditorProps extends EditorProps {}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import loadable from '@loadable/component';
|
import loadable from '@loadable/component';
|
||||||
|
import 'bytemd/dist/index.css';
|
||||||
|
import './style.less';
|
||||||
|
|
||||||
export const MarkdownEditor = loadable(() =>
|
export const MarkdownEditor = loadable(() =>
|
||||||
import('./editor').then((module) => module.MarkdownEditor)
|
import('./editor').then((module) => module.MarkdownEditor)
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import gfm from '@bytemd/plugin-gfm';
|
import gfm from '@bytemd/plugin-gfm';
|
||||||
|
import { BytemdPlugin } from 'bytemd';
|
||||||
|
import externalLinks from 'rehype-external-links';
|
||||||
|
|
||||||
export const plugins = [
|
export const plugins: BytemdPlugin[] = [
|
||||||
gfm(),
|
gfm(),
|
||||||
// Add more plugins here
|
{
|
||||||
|
rehype: (p) =>
|
||||||
|
p.use(externalLinks, {
|
||||||
|
target: '_blank',
|
||||||
|
}),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
@ -7,12 +7,20 @@
|
|||||||
z-index: 99;
|
z-index: 99;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown {
|
||||||
|
.markdown-body {
|
||||||
|
a {
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
.bytemd {
|
.bytemd {
|
||||||
@apply bg-zinc-900 border-zinc-800 text-foreground;
|
@apply text-foreground border-zinc-800 bg-zinc-900;
|
||||||
|
|
||||||
.bytemd-toolbar {
|
.bytemd-toolbar {
|
||||||
@apply bg-zinc-900 border-zinc-800;
|
@apply border-zinc-800 bg-zinc-900;
|
||||||
|
|
||||||
.bytemd-toolbar-icon:hover {
|
.bytemd-toolbar-icon:hover {
|
||||||
@apply bg-muted;
|
@apply bg-muted;
|
||||||
@ -24,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
@apply bg-zinc-900 text-foreground;
|
@apply text-foreground bg-zinc-900;
|
||||||
|
|
||||||
.CodeMirror-cursor {
|
.CodeMirror-cursor {
|
||||||
@apply border-foreground;
|
@apply border-foreground;
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Viewer } from '@bytemd/react';
|
import { Viewer } from '@bytemd/react';
|
||||||
import { plugins } from './plugins';
|
import { plugins } from './plugins';
|
||||||
import 'bytemd/dist/index.css';
|
|
||||||
import './style.less';
|
|
||||||
|
|
||||||
interface MarkdownViewerProps {
|
interface MarkdownViewerProps {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
export const MarkdownViewer: React.FC<MarkdownViewerProps> = React.memo(
|
export const MarkdownViewer: React.FC<MarkdownViewerProps> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
return <Viewer plugins={plugins} value={props.value ?? ''} />;
|
return (
|
||||||
|
<div className="markdown">
|
||||||
|
<Viewer plugins={plugins} value={props.value ?? ''} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
MarkdownViewer.displayName = 'MarkdownViewer';
|
MarkdownViewer.displayName = 'MarkdownViewer';
|
||||||
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
||||||
|
import { MarkdownViewer } from '../MarkdownEditor';
|
||||||
|
|
||||||
type FeedEventItemType = AppRouterOutput['feed']['events'][number];
|
type FeedEventItemType = AppRouterOutput['feed']['events'][number];
|
||||||
|
|
||||||
@ -12,7 +13,9 @@ export const FeedEventItem: React.FC<{ event: FeedEventItemType }> = React.memo(
|
|||||||
<div className="border-muted flex items-center rounded-lg border px-4 py-2">
|
<div className="border-muted flex items-center rounded-lg border px-4 py-2">
|
||||||
<div className="flex-1 gap-2">
|
<div className="flex-1 gap-2">
|
||||||
<div className="mb-2 flex gap-2">
|
<div className="mb-2 flex gap-2">
|
||||||
<div>{event.eventContent}</div>
|
<div>
|
||||||
|
<MarkdownViewer value={event.eventContent} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
@ -87,6 +87,7 @@
|
|||||||
"react-resizable-panels": "^2.0.12",
|
"react-resizable-panels": "^2.0.12",
|
||||||
"react-router": "^6.15.0",
|
"react-router": "^6.15.0",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
|
"rehype-external-links": "^3.0.0",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
"sonner": "^1.4.3",
|
"sonner": "^1.4.3",
|
||||||
"str2int": "^1.1.0",
|
"str2int": "^1.1.0",
|
||||||
@ -99,10 +100,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@i18next-toolkit/cli": "^1.2.2",
|
"@i18next-toolkit/cli": "^1.2.2",
|
||||||
"@tanstack/router-vite-plugin": "^1.20.5",
|
"@tanstack/router-vite-plugin": "^1.20.5",
|
||||||
|
"@types/jsonexport": "^3.0.5",
|
||||||
"@types/leaflet": "^1.9.8",
|
"@types/leaflet": "^1.9.8",
|
||||||
"@types/loadable__component": "^5.13.8",
|
"@types/loadable__component": "^5.13.8",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/jsonexport": "^3.0.5",
|
|
||||||
"@types/react": "^18.2.22",
|
"@types/react": "^18.2.22",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-grid-layout": "^1.3.5",
|
"@types/react-grid-layout": "^1.3.5",
|
||||||
|
@ -43,9 +43,11 @@ export const feedIntegrationRouter = router({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'push') {
|
if (eventType === 'push') {
|
||||||
const pusher = `${_.get(data, 'pusher.name')}<${_.get(data, 'pusher.email')}>`;
|
const pusherName = _.get(data, 'pusher.name');
|
||||||
|
const pusherEmail = _.get(data, 'pusher.email');
|
||||||
const commits = _.map(_.get(data, 'commits') as any[], 'id').join(', ');
|
const commits = _.map(_.get(data, 'commits') as any[], 'id').join(', ');
|
||||||
const fullName = _.get(data, 'repository.full_name');
|
const fullName = _.get(data, 'repository.full_name');
|
||||||
|
const repoUrl = _.get(data, 'repository.html_url');
|
||||||
const ref = String(_.get(data, 'ref'));
|
const ref = String(_.get(data, 'ref'));
|
||||||
const senderId = String(_.get(data, 'sender.id'));
|
const senderId = String(_.get(data, 'sender.id'));
|
||||||
const senderName = String(_.get(data, 'sender.login'));
|
const senderName = String(_.get(data, 'sender.login'));
|
||||||
@ -54,7 +56,7 @@ export const feedIntegrationRouter = router({
|
|||||||
data: {
|
data: {
|
||||||
channelId: channelId,
|
channelId: channelId,
|
||||||
eventName: eventType,
|
eventName: eventType,
|
||||||
eventContent: `${pusher} push commit ${commits} to [${ref}] in ${fullName}`,
|
eventContent: `[${pusherName}](${pusherEmail}) push commit **${commits}** to [${ref}] in [${fullName}](${repoUrl})`,
|
||||||
tags: [],
|
tags: [],
|
||||||
source: 'github',
|
source: 'github',
|
||||||
senderId,
|
senderId,
|
||||||
@ -69,10 +71,13 @@ export const feedIntegrationRouter = router({
|
|||||||
serializeJSON(event)
|
serializeJSON(event)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('serializeJSON(event)', serializeJSON(event));
|
||||||
|
|
||||||
return 'ok';
|
return 'ok';
|
||||||
} else if (eventType === 'star') {
|
} else if (eventType === 'star') {
|
||||||
const starCount = _.get(data, 'repository.stargazers_count');
|
const starCount = _.get(data, 'repository.stargazers_count');
|
||||||
const fullName = _.get(data, 'repository.full_name');
|
const fullName = _.get(data, 'repository.full_name');
|
||||||
|
const repoUrl = _.get(data, 'repository.html_url');
|
||||||
const senderId = String(_.get(data, 'sender.id'));
|
const senderId = String(_.get(data, 'sender.id'));
|
||||||
const senderName = String(_.get(data, 'sender.login'));
|
const senderName = String(_.get(data, 'sender.login'));
|
||||||
const url = String(_.get(data, 'compare'));
|
const url = String(_.get(data, 'compare'));
|
||||||
@ -80,7 +85,7 @@ export const feedIntegrationRouter = router({
|
|||||||
data: {
|
data: {
|
||||||
channelId: channelId,
|
channelId: channelId,
|
||||||
eventName: eventType,
|
eventName: eventType,
|
||||||
eventContent: `${senderName} star repo [${fullName}], now is ${starCount}.`,
|
eventContent: `${senderName} star repo [${fullName}](${repoUrl}), now is ${starCount}.`,
|
||||||
tags: [],
|
tags: [],
|
||||||
source: 'github',
|
source: 'github',
|
||||||
senderId,
|
senderId,
|
||||||
@ -100,6 +105,7 @@ export const feedIntegrationRouter = router({
|
|||||||
const action = _.get(data, 'action') as 'opened' | 'closed';
|
const action = _.get(data, 'action') as 'opened' | 'closed';
|
||||||
const starCount = _.get(data, 'repository.stargazers_count');
|
const starCount = _.get(data, 'repository.stargazers_count');
|
||||||
const fullName = _.get(data, 'repository.full_name');
|
const fullName = _.get(data, 'repository.full_name');
|
||||||
|
const repoUrl = _.get(data, 'repository.html_url');
|
||||||
const senderId = String(_.get(data, 'sender.id'));
|
const senderId = String(_.get(data, 'sender.id'));
|
||||||
const senderName = String(_.get(data, 'sender.login'));
|
const senderName = String(_.get(data, 'sender.login'));
|
||||||
const url = String(_.get(data, 'issue.url'));
|
const url = String(_.get(data, 'issue.url'));
|
||||||
@ -109,10 +115,10 @@ export const feedIntegrationRouter = router({
|
|||||||
let eventContent = '';
|
let eventContent = '';
|
||||||
if (action === 'opened') {
|
if (action === 'opened') {
|
||||||
eventName = 'open_issue';
|
eventName = 'open_issue';
|
||||||
eventContent = `${senderName} open issue [${title}] in repo [${fullName}]`;
|
eventContent = `${senderName} open issue [${title}] in repo [${fullName}](${repoUrl})`;
|
||||||
} else if (action === 'closed') {
|
} else if (action === 'closed') {
|
||||||
eventName = 'close_issue';
|
eventName = 'close_issue';
|
||||||
eventContent = `${senderName} close issue [${title}] in repo [${fullName}]`;
|
eventContent = `${senderName} close issue [${title}] in repo [${fullName}](${repoUrl})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventContent) {
|
if (eventContent) {
|
||||||
|
Loading…
Reference in New Issue
Block a user