Skip to content

Commit

Permalink
Merge pull request #460 from carbon-bond/add-markdown-support
Browse files Browse the repository at this point in the history
支援 markdown
  • Loading branch information
MROS authored Nov 27, 2022
2 parents 4db6101 + c3d45c2 commit 4e34be8
Show file tree
Hide file tree
Showing 15 changed files with 455 additions and 58 deletions.
7 changes: 6 additions & 1 deletion frontend/app/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"emoji-mart": "3",
"immer": "^9.0.14",
"long": "^4.0.0",
"marked": "^4.1.1",
"normalize.css": "^8.0.1",
"prismjs": "^1.29.0",
"query-string": "^6.13.5",
"react-activation": "^0.11.2",
"react-hook-form": "^6.0.7",
Expand All @@ -39,8 +41,10 @@
"@babel/preset-react": "^7.17.12",
"@types/d3": "^6.2.0",
"@types/emoji-mart": "3",
"@types/marked": "^4.0.7",
"@types/minimist": "^1.2.2",
"@types/node": "^14.14.6",
"@types/prismjs": "^1.26.0",
"@types/prompts": "^2.0.9",
"@types/react-modal": "^3.10.0",
"@types/request": "^2.48.5",
Expand All @@ -56,6 +60,7 @@
"stylelint": "^10.1.0",
"typescript-plugin-css-modules": "^3.4.0",
"vite": "^2.4.2",
"vite-plugin-markdown": "^2.0.2"
"vite-plugin-markdown": "^2.0.2",
"vite-plugin-prismjs": "^0.0.8"
}
}
2 changes: 2 additions & 0 deletions frontend/app/desktop/src/css/board/article_card.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@
}
& .articleTitle {
color: black;
font-size: 20px;
display: inline;
}
& .articleGraphViewIcon {
position: relative;
Expand Down
9 changes: 0 additions & 9 deletions frontend/app/desktop/src/css/law_page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@
& div {
max-width: 800px;
line-height: 24px;
& * {
margin: 20px 0px;
}
& li {
margin: 5px 0px;
}
& ul {
padding-left: 20px;
}
& h1 {
margin-bottom: 40px;
}
Expand Down
40 changes: 40 additions & 0 deletions frontend/app/desktop/src/css/markdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.markdown {
& li {
margin: 2px 0px;
}
& ul {
padding-left: 20px;
}
& ol {
padding-left: 20px;
}
& p {
margin: 15px 0px;
}
& h2 {
margin: 15px 0px;
}
& h3 {
margin: 8px 0px;
}
& blockquote {
background: var(--gray-1);
border-left: 4px solid var(--gray-3);
margin: 8px 5px;
padding: 5px 10px;
}
& pre > code {
width: 100%;
color: var(--gray-2);
display: inline-block;
overflow: scroll;
overflow-y: hidden;
background-color: black;
border-radius: 10px;
padding: 10px;
margin: 10px 0px;
}
& img {
max-width: 100%;
}
}
18 changes: 14 additions & 4 deletions frontend/app/desktop/src/tsx/article_card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Article, Comment, ArticleMeta, Author, Edge, BondInfo, MiniArticleMeta,
import { API_FETCHER, unwrap } from 'carbonbond-api/api_utils';
import { toastErr, useInputValue } from '../utils';
import { ArticleContent } from '../board/article_page';
import { ShowText } from '../display/show_text';
import { ShowPureText } from '../display/show_pure_text';
import { BonderCards } from './bonder';
import { toast } from 'react-toastify';
import { copyToClipboard } from '../../ts/utils';
Expand Down Expand Up @@ -101,12 +101,22 @@ export function ArticleHeader(props: {
</div>;
}

export function ArticleLine(props: { category: string, title: string, id: number, board_info: {board_name: string, board_type: BoardType} }): JSX.Element {
export function ArticleLine(props: {
category: string,
title: string,
id: number,
board_info: { board_name: string, board_type: BoardType }
h1?: boolean,
}): JSX.Element {
let board_info = getBoardInfo(props.board_info);
return <div className={style.articleLine}>
<span className={`${style.articleCategory}`}>{props.category}</span>
<Link to={`${board_info.to_url()}/article/${props.id}`} className="styleless">
<span className={style.articleTitle}>{props.title}</span>
{
props.h1 ?
<h1 className={style.articleTitle}>{props.title}</h1> :
<span className={style.articleTitle}>{props.title}</span>
}
</Link>
<Link className={style.articleGraphViewIcon} to={`${board_info.to_url()}/graph/${props.id}`}><span> 🗺</span></Link>
</div>;
Expand All @@ -119,7 +129,7 @@ export function CommentCard(props: {comment: Comment}): JSX.Element {
<span>{relativeDate(new Date(props.comment.create_time))}</span>
</div>
<div className={style.commentContent}>
<ShowText text={props.comment.content} />
<ShowPureText text={props.comment.content} />
</div>
</div>;
}
Expand Down
11 changes: 7 additions & 4 deletions frontend/app/desktop/src/tsx/board/article_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { ReplyButtons } from '../article_card/bonder';
import { ArticleSidebar } from './right_sidebar';
import { ArticleLocation, LocationState } from '../global_state/location';
import { BoardInfo, useBoardInfo } from '.';
import { ShowText } from '../display/show_text';
import { EditorPanelState } from '../global_state/editor_panel';
import { Format, ShowText } from '../display/show_text';

export function ArticleContent(props: { article: Article }): JSX.Element {
const article = props.article;
Expand All @@ -28,7 +28,9 @@ export function ArticleContent(props: { article: Article }): JSX.Element {
}
{
(field.kind == force.FieldKind.MultiLine || field.kind == force.FieldKind.OneLine) ?
<ShowText text={content[field.name]} /> :
<ShowText
text={content[field.name]}
format={Format.Markdown} /> :
content[field.name]
}
</div>
Expand All @@ -37,7 +39,7 @@ export function ArticleContent(props: { article: Article }): JSX.Element {
</div>;
}

function ArticleDisplayPage(props: { article: Article, board: Board }): JSX.Element {
const ArticleDisplayPage = React.memo((props: { article: Article, board: Board }): JSX.Element => {
let { article, board } = props;
let { useMainScrollToBottom } = useMainScroll();
let scrollHandler = React.useCallback(() => { }, []);
Expand All @@ -54,6 +56,7 @@ function ArticleDisplayPage(props: { article: Article, board: Board }): JSX.Elem
with_button={true} />
<div className={style.articleLineWrap}>
<ArticleLine
h1={true}
board_info={props.board}
id={article.meta.id}
category={category}
Expand All @@ -64,7 +67,7 @@ function ArticleDisplayPage(props: { article: Article, board: Board }): JSX.Elem
<ArticleContent article={article} />
<ArticleFooter article={article.meta} hit={Hit.Comment} />
</div>;
}
});

export function ArticlePage(): JSX.Element {
let params = useParams();
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/desktop/src/tsx/board/right_sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import style from '../../css/board/right_sidebar.module.css';
import { toastErr, useSubscribeBoard } from '../utils';
import { Link } from 'react-router-dom';
import { ProfileRelation, ProfileAction, ProfileDetail, Sentence } from '../profile/user_page';
import { ShowText } from '../display/show_text';
import { ShowPureText } from '../display/show_pure_text';
import { AllChatState, OppositeKind, DirectChatData } from '../global_state/chat';
import { BottomPanelState } from '../global_state/bottom_panel';
import { ConfigState } from '../global_state/config';
Expand Down Expand Up @@ -66,7 +66,7 @@ export function BoardSidebar(props: { board: Board }): JSX.Element {
<div className={style.rightSidebarBlock}>
<div className={style.header}>關於看板</div>
<div className={style.content}>
<ShowText text={props.board.detail} />
<ShowPureText text={props.board.detail} />
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/desktop/src/tsx/chatroom_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Link } from 'react-router-dom';
import { NewChat, server_trigger } from 'carbonbond-api/api_trait';
import { useScroll } from 'react-use';
import ReactDOM from 'react-dom';
import { ShowLine } from './display/show_text';
import { ShowLine } from './display/show_pure_text';

const Picker = React.lazy(() => {
return import('emoji-mart')
Expand Down
56 changes: 56 additions & 0 deletions frontend/app/desktop/src/tsx/display/show_markdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from 'react';
import { marked } from 'marked';
import '../../css/markdown.css';

import Prism from 'prismjs';

const renderer = {
heading(text: string, level: 1 | 2 | 3 | 4 | 5 | 6) {
const escaped_text = text.replace(/[\s]+/g, '_');
const new_level = level <= 5 ? level + 1 : level;
if (level == 1) {
return `
<h${new_level}>
<a name="${escaped_text}" class="anchor" href="#${escaped_text}">
<span class="header-link">⚓</span>
</a>
${text}
</h${new_level}>`;
} else {
return `<h${new_level}>
${text}
</h${new_level}>`;
}
},
link(href: string | null, _title: string | null, text: string) {
return `<a href="${href}" target="_blank">${text}</a>`;
}
};

marked.use({
gfm: true,
breaks: true,
sanitize: true,
renderer,
highlight: function(code: string, lang: string) {
if (lang && Prism.languages[lang]) {
try {
return Prism.highlight(code, Prism.languages[lang], lang);
} catch (__) {
return code;
}
}
return code;
}
});

export function ShowMarkdown(props: {
text: string
}): JSX.Element {
return <div
className="markdown"
dangerouslySetInnerHTML={{
__html: marked.parse(props.text)
}}>
</div>;
}
38 changes: 38 additions & 0 deletions frontend/app/desktop/src/tsx/display/show_pure_text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as React from 'react';
import { isImageLink, isLink } from '../../ts/regex_util';

export function ShowLine(props: {line: string}): JSX.Element {
let key = 0;
let elements: JSX.Element[] = [];
props.line.split(' ').forEach(word => {
if (isLink(word)) {
elements.push(<a key={key++} target="_blank" href={word}>{word}</a>);
} else {
elements.push(<React.Fragment key={key++}>{word}</React.Fragment>);
}
elements.push(<React.Fragment key={key++}> </React.Fragment>);
});
return <>{elements}</>;
}

export function ShowPureText(props: { text: string; }): JSX.Element {
let key = 0;
return <>{props.text.split('\n').map(line => {
if (/^\s*$/.test(line)) {
// 若整行都是空的,換行
return <br key={key++} />;
} else if (isImageLink(line.trim())) {
return <p key={key++}>
<a target="_blank" href={line}>
{line}
<img key={key++} src={line.trim()} width="100%" alt="圖片" />
</a>
</p>;
} else {
return <p key={key++}>
<ShowLine line={line} />
</p>;
}
})}
</>;
}
48 changes: 15 additions & 33 deletions frontend/app/desktop/src/tsx/display/show_text.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,20 @@
import * as React from 'react';
import { isImageLink, isLink } from '../../ts/regex_util';
import { ShowMarkdown } from './show_markdown';
import { ShowPureText } from './show_pure_text';

export function ShowLine(props: {line: string}): JSX.Element {
let key = 0;
let elements: JSX.Element[] = [];
props.line.split(' ').forEach(word => {
if (isLink(word)) {
elements.push(<a key={key++} target="_blank" href={word}>{word}</a>);
} else {
elements.push(<React.Fragment key={key++}>{word}</React.Fragment>);
}
elements.push(<React.Fragment key={key++}> </React.Fragment>);
});
return <>{elements}</>;
export enum Format {
PureText,
Markdown,
}

export function ShowText(props: { text: string; }): JSX.Element {
let key = 0;
return <>{props.text.split('\n').map(line => {
if (/^\s*$/.test(line)) {
// 若整行都是空的,換行
return <br key={key++} />;
} else if (isImageLink(line.trim())) {
return <p key={key++}>
<a target="_blank" href={line}>
{line}
<img key={key++} src={line.trim()} width="100%" alt="圖片" />
</a>
</p>;
} else {
return <p key={key++}>
<ShowLine line={line} />
</p>;
}
})}
</>;
export function ShowText(props: {
text: string,
format: Format
}): JSX.Element {
switch (props.format) {
case Format.PureText:
return <ShowPureText text={props.text} />;
case Format.Markdown:
return <ShowMarkdown text={props.text} />;
}
}
3 changes: 2 additions & 1 deletion frontend/app/desktop/src/tsx/law_page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';

import style from '../css/law_page.module.css';
import '../css/markdown.css';

import { ReactComponent as TermsComponent } from '../md/law/服務條款.md';
import { ReactComponent as RulesComponent } from '../md/law/論壇守則.md';
Expand All @@ -14,7 +15,7 @@ import {

function LawPage(): JSX.Element {
return <div className={style.lawPage}>
<div>
<div className="markdown">
<Routes>
<Route path={encodeURIComponent('服務條款.md')} element={<TermsComponent />} />
<Route path={encodeURIComponent('論壇守則.md')} element={<RulesComponent />} />
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/desktop/src/tsx/profile/user_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { TabPanel, TabPanelItem } from '../components/tab_panel';
import { AllChatState, DirectChatData } from '../global_state/chat';
import { BottomPanelState } from '../global_state/bottom_panel';
import { InvalidMessage } from '../../tsx/components/invalid_message';
import { ShowText } from '../display/show_text';
import { ShowPureText } from '../display/show_pure_text';
import { createBrowserHistory } from 'history';

import aritcle_wrapper_style from '../../css/article_wrapper.module.css';
Expand Down Expand Up @@ -213,7 +213,7 @@ export function ProfileDetail(props: { profile_user: User, setProfileUser: React
}
{
props.profile_user.introduction ? <div className={style.info}>
<ShowText text={props.profile_user.introduction} />
<ShowPureText text={props.profile_user.introduction} />
</div> : <div className={style.noSentence}>
尚無自我介紹
</div>
Expand Down
Loading

0 comments on commit 4e34be8

Please sign in to comment.