From bb393becc44e4e9c564977c47e0aa077240e86a7 Mon Sep 17 00:00:00 2001 From: Roman Timashev Date: Thu, 19 Mar 2020 22:34:28 +0300 Subject: [PATCH] render page content based on tokens; ignore rendered content field --- package-lock.json | 35 ++++++++++++++++ package.json | 2 + pages/[...slug].tsx | 67 +++++++++++++++++++++++++++++-- src/components/Markdown/index.tsx | 19 +++++++++ src/components/Page/index.tsx | 8 +--- src/lib/tokens.ts | 36 +++++++++++++++++ src/style/article.css | 4 -- src/types.ts | 32 +++++++++++++++ 8 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 src/components/Markdown/index.tsx create mode 100644 src/lib/tokens.ts diff --git a/package-lock.json b/package-lock.json index cb5cdfb7..2880e81e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4451,6 +4451,14 @@ } } }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -4667,6 +4675,18 @@ "object-visit": "^1.0.0" } }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -4684,6 +4704,11 @@ } } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -8077,6 +8102,16 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "typograf": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/typograf/-/typograf-6.11.0.tgz", + "integrity": "sha512-7PcX6gyocBoNoCnLNmgqzd7ohp7SrW44U6YIqk91+7x4ivVlzpRltN4JK7mtBov9CVPsfX6W8yXTnoUxImSzwQ==" + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "underscore": { "version": "1.8.3", "resolved": "http://storage.mds.yandex.net/get-npm/35308/underscore-1.8.3.tgz", diff --git a/package.json b/package.json index 89e7e3dc..d26bd04f 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,12 @@ "classnames": "^2.2.6", "date-fns": "^2.11.0", "lodash": "^4.17.15", + "markdown-it": "^10.0.0", "next": "^9.3.1", "react": "^16.13.0", "react-dom": "^16.13.0", "sitemap": "^1.5.0", + "typograf": "^6.11.0", "use-media": "^1.4.0" }, "devDependencies": { diff --git a/pages/[...slug].tsx b/pages/[...slug].tsx index b7a4031b..f47235f2 100644 --- a/pages/[...slug].tsx +++ b/pages/[...slug].tsx @@ -2,11 +2,13 @@ import Head from 'next/head' import { App } from 'src/components/App' import { tail } from 'lodash' import { Page } from 'src/components/Page' +import { Markdown } from 'src/components/Markdown' import { Meta } from 'src/components/Meta' import { MetaBuilder } from 'src/lib/meta' import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib' import { NextPage } from 'next' -import { IBreadcumbsPart, IMeta, IPage, ITag } from 'src/types' +import { IBreadcumbsPart, IMeta, IPage, ITag, FileTokenData, Token } from 'src/types' +import { joinTokens } from 'src/lib/tokens' function array(value: T | T[]) { return Array.isArray(value) @@ -14,13 +16,33 @@ function array(value: T | T[]) { : [value] } +const File: React.SFC = props => ( +
+ +
+ {props['title']} +
+
+ + + + +
+) + type Props = { title: string tags: ITag[] date: string - content: string breadcrumb: IBreadcumbsPart[] meta?: IMeta + tokens?: Token[] } const Index: NextPage = props => ( @@ -42,7 +64,43 @@ const Index: NextPage = props => ( tags={props.tags} date={props.date ? new Date(props.date) : null} > - {props.content} +
+ {joinTokens(props.tokens ?? []).map((x, i) => { + switch (x.token) { + case 'text': + return ( + + ) + + case 'image': + return ( +
+
+ {x.data.alt} +
{x.data.caption}
+
+
+ ) + + case 'file': + return ( + + ) + + default: + return ( +
+                                    {JSON.stringify(x)}
+                                
+ ) + } + })} +
) @@ -66,10 +124,11 @@ export const getStaticProps = async (ctx: any) => { .setTitle(page.title) .setDescription(description) .build() + const tokens = page.tokens return { props: { - content: page.data, + tokens, title: page.title, tags: page.tags, date: page.date, diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx new file mode 100644 index 00000000..2ede54d2 --- /dev/null +++ b/src/components/Markdown/index.tsx @@ -0,0 +1,19 @@ +import { Html } from 'src/components/Html' +import MarkdownIt from 'markdown-it' +import Typograf from 'typograf' +import { memo } from 'react' + +const tp = new Typograf({ locale: ['ru'] }) +const md = new MarkdownIt({ + html: true, +}) + +export type MarkdownProps = { + data: string +} + +export const Markdown: React.SFC = memo(props => ( + +)) diff --git a/src/components/Page/index.tsx b/src/components/Page/index.tsx index efb74fb5..64b83694 100644 --- a/src/components/Page/index.tsx +++ b/src/components/Page/index.tsx @@ -7,19 +7,13 @@ import { ITag } from 'src/types' export type PageProps = { style?: React.CSSProperties - children: string date: Date tags: ITag[] } export const Page: React.FC = props => (
- - + {props.children}
diff --git a/src/lib/tokens.ts b/src/lib/tokens.ts new file mode 100644 index 00000000..fedd2cd8 --- /dev/null +++ b/src/lib/tokens.ts @@ -0,0 +1,36 @@ +import { TextToken, Token } from 'src/types' + +export function joinTextTokens(tokens: TextToken[]): TextToken { + return { + token: 'text', + data: tokens + .map(x => x.data) + .join('\n\n'), + } +} + +export function joinTokens(tokens: Token[]): Token[] { + let buffer: TextToken[] = [] + const result: Token[] = [] + + for (const token of tokens) { + if (token.token === 'text') { + buffer.push(token) + continue + } + + const compiledToken = joinTextTokens(buffer) + buffer = [] + + result.push(compiledToken) + result.push(token) + } + + if (buffer.length) { + result.push( + joinTextTokens(buffer) + ) + } + + return result +} diff --git a/src/style/article.css b/src/style/article.css index c53f0270..d5b23251 100644 --- a/src/style/article.css +++ b/src/style/article.css @@ -27,10 +27,6 @@ line-height: var(--article-line-height); } -.article p:last-child { - margin-bottom: 0; -} - .article blockquote { padding: var(--size-m); font-size: var(--font-size-accent); diff --git a/src/types.ts b/src/types.ts index e2b41594..bab0b2df 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,3 +103,35 @@ export type ImageDefinition = { height: number src: string } + +export type TextToken = { + token: 'text' + data: string +} + +export type ImageToken = { + token: 'image' + data: { + src: string + alt: string + caption: string + } +} + +export type FileTokenData = { + url: string + slug: string + image_url: string + file_url: string + title: string + file_size: number + file_format: 'application/pdf' +} + +export type FileToken = { + token: 'file' + data: FileTokenData +} + +export type Token = TextToken | ImageToken | FileToken +