├── .eslintrc ├── .gitignore ├── .prettierrc ├── README.md ├── data.json ├── package.json ├── public ├── assets │ ├── 2021-04-30_20.38.58_1619786417157_0.gif │ ├── 2021-04-30_21.10.23_1619788837457_0.gif │ ├── 2021-05-21_18.41.54_1621593786418_0.gif │ ├── 2021-05-21_18.45.31_1621593958149_0.gif │ ├── 2021-05-25_02.52.53_1621882676163_0.gif │ ├── Arch-After-Refactoring_1619596960391_0.png │ ├── Architecture-before-refactoring_1619596948551_0.png │ ├── CleanShot_202021-05-29_20at_2000.51.24@2x_1622220701200_0.png │ ├── google_1622942691813_0.png │ ├── image_1622119180558_0.png │ ├── logseq-tree_1619596972997_0.png │ ├── migrate_1621935713657_0.gif │ ├── pages_Publishing (Desktop App Only)_1615917396171_0.png │ ├── pages_knowledge_graph_1612308811958_0.png │ ├── pages_knowledge_graph_1612308816189_0.png │ ├── pages_knowledge_graph_1612309081173_0.png │ ├── pages_one year in logseq_1616235182683_0.png │ ├── pages_one year in logseq_1616235368782_0.png │ ├── pages_one year in logseq_1616235681415_0.png │ ├── pages_one year in logseq_1616235711538_0.png │ ├── pages_one year in logseq_1616235763365_0.png │ ├── pages_one year in logseq_1616235785959_0.png │ ├── pages_one year in logseq_1616235941695_0.png │ ├── pages_one year in logseq_1616237665640_0.png │ ├── preview_1622110883134_0.gif │ ├── template2_1621928922947_0.gif │ ├── template3_1621929392325_0.gif │ ├── template_1621928689810_0.gif │ ├── toggle-brackets_1622105057516_0.gif │ └── tree_1619596988525_0.png └── favicon.ico ├── src ├── components │ ├── ExternalLink.js │ ├── LSBlock.js │ ├── LSBlockReference.js │ ├── LSBlocks.js │ ├── LSHeading.js │ ├── LSInline.js │ ├── LSInlines.js │ ├── LSLink.js │ ├── LSLinkedReferences.js │ ├── LSPage.js │ ├── LSSrc.js │ ├── LSTable.js │ ├── Layout.js │ ├── PageLink.js │ ├── PagesNav.js │ └── TOC.js ├── pages │ ├── _app.js │ ├── index.js │ └── pages │ │ └── [name].js ├── styles │ ├── global.css │ └── markdown.css └── utils.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Status 2 | 3 | This project is no longer maintained. See https://github.com/pengx17/logseq-publish and https://github.com/sawhney17/logseq-schrodinger for alternative approaches to publishing Logseq graphs. 4 | 5 | # Logseq Publish 6 | 7 | Logseq Publish is a tool that generates a fast, SEO-friendly, and scalable website from your Logseq content. 8 | 9 | ## Demo 10 | 11 | https://eloquent-keller-6512b6.netlify.app/ 12 | 13 | ## How to use 14 | 15 | We plan to integrate the publish feature into the Logseq app and provide a one-click publish flow. 16 | 17 | For now, you need to: 18 | 19 | 1. "Export as JSON" in the Logseq desktop app. 20 | 2. Clone this repo. 21 | 3. Copy the exported JSON to the root as data.json. 22 | 4. Copy the whole `assets` folder from the Logseq repo to the `public` folder. 23 | 5. Run `yarn && yarn dev` to start the site locally. 24 | 6. Run `yarn export` to generate the published static website in the folder `out`. You can deploy it to anywhere which supports static hosting, for example [Github Pages](https://guides.github.com/features/pages/) 25 | 26 | ## Super Early! 27 | 28 | We release Logseq Publish in its early stage to better involve the community in its development. Let's help make it better by: 29 | 30 | - Use it with caution 31 | - Contribute to missing features 32 | - Report bugs (https://github.com/logseq/publish/issues) 33 | - Send feature requests (https://discuss.logseq.com/c/feature-requests/7) 34 | - Discuss on the Discord channel (https://discord.com/channels/725182569297215569/858995989801074708) 35 | 36 | ## Why 37 | 38 | You want to share your Logseq knowledge graph with the world. While currently in Logseq you can "export public pages", which simply bundle the whole Logseq web app with your public pages and let you deploy them as a static website. 39 | 40 | This approach has some limitations though: 41 | 42 | - With all its rich features, the code size of Logseq can be big. Combining with the fact that it renders on the client using JavaScript, the exported pages can be slow on a poor network and is also not SEO friendly. 43 | - You want different features and UI/UX for sharing than for content creation. 44 | - You want to interpret your Logseq data in different ways than the Logseq app. 45 | 46 | Logseq Publish is a new approach for sharing your Logseq content more lightly. 47 | 48 | ## How does it work 49 | 50 | 1. Logseq exports a JSON file, which a tree structure consisting of various kinds of block types. (the sample `data.json` and `assets` are from the official Logseq documentation) 51 | 2. Logseq Publish provides code to transform the JSON data into structures more suitable for static rendering. 52 | 3. Logseq Publish provides React components for various Logseq block types and other UI elements. You can check them at `src/components`. Component names begin with "LS" are for Logseq-specific. 53 | 4. Logseq Publish utilizes [Next.js](https://nextjs.org/) to generate static HTML at build time. 54 | 55 | ## FAQ 56 | 57 | ### How to set the homepage? 58 | 59 | By default the homepage is `Contents`. You can change the homepage by update the `homepage` key in `data.json`. 60 | 61 | ### What's the "Missing renderer for type:" warning message? 62 | 63 | Logseq Publish does not implement all block types of Logseq. Since it's in early stage, if you the types that you need are missing, please send a feature request or (better!) consider contributing. 64 | 65 | ### How can I customize Logseq Publish? 66 | 67 | There are a few options: 68 | 69 | - Since the code is open-sourced, you can change the publishing experience by contribution or fork. 70 | - Because the single source of truth is the exported JSON, you build your own publishing toolchain with the data. If some parts of Logseq Publish is useful for you, just copy it!. In fact, community members are already doing that (https://github.com/believer/devlog). 71 | - (TODO) We will extrat a `publish.css` in which you can set CSS variables to customize the style, much like how you customize Logseq by editing `custom.css`. 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logseq-publish", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "export": "yarn build && next export" 10 | }, 11 | "dependencies": { 12 | "@chakra-ui/icons": "^1.0.14", 13 | "@chakra-ui/react": "^1.6.5", 14 | "@emotion/react": "^11.4.0", 15 | "@emotion/styled": "^11.3.0", 16 | "@react-hookz/web": "^6.0.1", 17 | "autoprefixer": "^10.3.1", 18 | "framer-motion": "^4.1.17", 19 | "js-cookie": "^3.0.0", 20 | "next": "11.0.1", 21 | "react": "17.0.2", 22 | "react-dom": "17.0.2", 23 | "react-syntax-highlighter": "^15.4.4", 24 | "tippy.js": "^6.3.1" 25 | }, 26 | "devDependencies": { 27 | "eslint": "^7.31.0", 28 | "eslint-config-next": "^11.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/assets/2021-04-30_20.38.58_1619786417157_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/2021-04-30_20.38.58_1619786417157_0.gif -------------------------------------------------------------------------------- /public/assets/2021-04-30_21.10.23_1619788837457_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/2021-04-30_21.10.23_1619788837457_0.gif -------------------------------------------------------------------------------- /public/assets/2021-05-21_18.41.54_1621593786418_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/2021-05-21_18.41.54_1621593786418_0.gif -------------------------------------------------------------------------------- /public/assets/2021-05-21_18.45.31_1621593958149_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/2021-05-21_18.45.31_1621593958149_0.gif -------------------------------------------------------------------------------- /public/assets/2021-05-25_02.52.53_1621882676163_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/2021-05-25_02.52.53_1621882676163_0.gif -------------------------------------------------------------------------------- /public/assets/Arch-After-Refactoring_1619596960391_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/Arch-After-Refactoring_1619596960391_0.png -------------------------------------------------------------------------------- /public/assets/Architecture-before-refactoring_1619596948551_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/Architecture-before-refactoring_1619596948551_0.png -------------------------------------------------------------------------------- /public/assets/CleanShot_202021-05-29_20at_2000.51.24@2x_1622220701200_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/CleanShot_202021-05-29_20at_2000.51.24@2x_1622220701200_0.png -------------------------------------------------------------------------------- /public/assets/google_1622942691813_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/google_1622942691813_0.png -------------------------------------------------------------------------------- /public/assets/image_1622119180558_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/image_1622119180558_0.png -------------------------------------------------------------------------------- /public/assets/logseq-tree_1619596972997_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/logseq-tree_1619596972997_0.png -------------------------------------------------------------------------------- /public/assets/migrate_1621935713657_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/migrate_1621935713657_0.gif -------------------------------------------------------------------------------- /public/assets/pages_Publishing (Desktop App Only)_1615917396171_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_Publishing (Desktop App Only)_1615917396171_0.png -------------------------------------------------------------------------------- /public/assets/pages_knowledge_graph_1612308811958_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_knowledge_graph_1612308811958_0.png -------------------------------------------------------------------------------- /public/assets/pages_knowledge_graph_1612308816189_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_knowledge_graph_1612308816189_0.png -------------------------------------------------------------------------------- /public/assets/pages_knowledge_graph_1612309081173_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_knowledge_graph_1612309081173_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616235182683_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616235182683_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616235368782_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616235368782_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616235681415_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616235681415_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616235711538_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616235711538_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616235763365_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616235763365_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616235785959_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616235785959_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616235941695_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616235941695_0.png -------------------------------------------------------------------------------- /public/assets/pages_one year in logseq_1616237665640_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/pages_one year in logseq_1616237665640_0.png -------------------------------------------------------------------------------- /public/assets/preview_1622110883134_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/preview_1622110883134_0.gif -------------------------------------------------------------------------------- /public/assets/template2_1621928922947_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/template2_1621928922947_0.gif -------------------------------------------------------------------------------- /public/assets/template3_1621929392325_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/template3_1621929392325_0.gif -------------------------------------------------------------------------------- /public/assets/template_1621928689810_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/template_1621928689810_0.gif -------------------------------------------------------------------------------- /public/assets/toggle-brackets_1622105057516_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/toggle-brackets_1622105057516_0.gif -------------------------------------------------------------------------------- /public/assets/tree_1619596988525_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/assets/tree_1619596988525_0.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logseq/publish/eb1c87782871a7e3ee9ca790fe4b540dc0e4791d/public/favicon.ico -------------------------------------------------------------------------------- /src/components/ExternalLink.js: -------------------------------------------------------------------------------- 1 | import { Link } from '@chakra-ui/react' 2 | import { ExternalLinkIcon } from '@chakra-ui/icons' 3 | 4 | export default function ExternalLink({ href, children }) { 5 | return ( 6 | 7 | {children} 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/LSBlock.js: -------------------------------------------------------------------------------- 1 | import { ChevronDownIcon, ChevronRightIcon } from '@chakra-ui/icons' 2 | import { Box } from '@chakra-ui/react' 3 | import { useLocalStorageValue } from '@react-hookz/web' 4 | import { isHeading } from '../utils' 5 | import LSBlocks from './LSBlocks' 6 | import LSHeading from './LSHeading' 7 | import LSInlines from './LSInlines' 8 | 9 | export default function LSBlock({ b }) { 10 | const [collpased, setCollpased] = useLocalStorageValue( 11 | `collpase-state-${b.id}`, 12 | false, 13 | { initializeWithStorageValue: false }, 14 | ) 15 | 16 | const showToggleIcon = Array.isArray(b.children) && b.children.length > 0 17 | 18 | const toggle = showToggleIcon && ( 19 | setCollpased(!collpased)} 24 | > 25 | {collpased ? : } 26 | 27 | ) 28 | 29 | const blockIsHeading = isHeading(b) 30 | 31 | let title 32 | if (blockIsHeading) { 33 | title = 34 | } else if (Array.isArray(b.title)) { 35 | title = 36 | } 37 | 38 | let body 39 | if (Array.isArray(b.body)) { 40 | body = 41 | } 42 | 43 | const children = 44 | 45 | return ( 46 | <> 47 | 48 | {!blockIsHeading && toggle} 49 | {title} 50 | 51 | 52 | {!collpased && ( 53 | 54 | {body} 55 | {children} 56 | 57 | )} 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /src/components/LSBlockReference.js: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import { createPagePath, getBlockByID, getPageNameByID } from '../utils' 3 | import Link from 'next/link' 4 | import LSBlock from './LSBlock' 5 | 6 | export default function LSBlockReference({ c: id }) { 7 | const referedBlock = getBlockByID(id) 8 | 9 | if (referedBlock) { 10 | const pageName = getPageNameByID(id) 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | } else { 20 | return
{id}
21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/LSBlocks.js: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/layout' 2 | import LSBlock from './LSBlock' 3 | 4 | export default function LSBlocks({ blocks }) { 5 | return ( 6 | Array.isArray(blocks) && 7 | blocks.length > 0 && ( 8 |
    9 | {blocks.map((b) => { 10 | return ( 11 | 12 | 13 | 14 | ) 15 | })} 16 |
17 | ) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/components/LSHeading.js: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import LSInlines from './LSInlines' 3 | 4 | export default function LSHeading({ b, toggle }) { 5 | const inlines = 6 | const content = 7 | toggle != null ? ( 8 | 9 | {toggle} 10 | {inlines} 11 | 12 | ) : ( 13 | inlines 14 | ) 15 | const level = b['heading-level'] 16 | const { id } = b 17 | 18 | switch (level) { 19 | case 1: 20 | return

{content}

21 | case 2: 22 | return

{content}

23 | case 3: 24 | return

{content}

25 | case 4: 26 | return

{content}

27 | case 5: 28 | return
{content}
29 | case 6: 30 | return
{content}
31 | 32 | default: 33 | return
{content}
34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/LSInline.js: -------------------------------------------------------------------------------- 1 | import { getInlineContent, getInlineType, isInlineContainer } from '../utils' 2 | import LSBlockReference from './LSBlockReference' 3 | import LSInlines from './LSInlines' 4 | import LSLink from './LSLink' 5 | import LSSrc from './LSSrc' 6 | import LSTable from './LSTable' 7 | 8 | function Plain({ c }) { 9 | return {c} 10 | } 11 | 12 | function Code({ c }) { 13 | return {c} 14 | } 15 | 16 | function Break_Line() { 17 | return
18 | } 19 | 20 | function Emphasis({ c }) { 21 | let kind = c[0][0] 22 | let content = 23 | switch (kind) { 24 | case 'Bold': 25 | return {content} 26 | default: 27 | return content 28 | } 29 | } 30 | 31 | const INLINE_RENDERERS = { 32 | Plain, 33 | Link: LSLink, 34 | Block_reference: LSBlockReference, 35 | Code, 36 | Src: LSSrc, 37 | Emphasis, 38 | Table: LSTable, 39 | Break_Line, 40 | } 41 | 42 | export default function Inline({ inline }) { 43 | const content = getInlineContent(inline) 44 | const type = getInlineType(inline) 45 | const InlineComponent = INLINE_RENDERERS[type] 46 | if (isInlineContainer(inline)) { 47 | return 48 | } else if (InlineComponent) { 49 | return 50 | } else { 51 | console.warn('Missing renderer for type: ', type) 52 | return null 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/LSInlines.js: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import LSInline from './LSInline' 3 | 4 | export default function LSInlines({ inlines }) { 5 | return inlines.map((inline, index) => ( 6 | 7 | )) 8 | } 9 | -------------------------------------------------------------------------------- /src/components/LSLink.js: -------------------------------------------------------------------------------- 1 | import { pageNames } from '../utils' 2 | import ExternalLink from './ExternalLink' 3 | import PageLink from './PageLink' 4 | 5 | export default function LSLink({ c }) { 6 | const linkContent = c.url 7 | const linkType = linkContent?.[0] 8 | if (linkType === 'Search') { 9 | const toPage = c.url?.[1] 10 | if (pageNames.includes(toPage)) { 11 | return 12 | } else { 13 | return toPage 14 | } 15 | } else if (linkType === 'Complex') { 16 | return ( 17 | {c.label?.[0]?.[1]} 18 | ) 19 | } else if (linkType === 'File') { 20 | const path = linkContent[1] 21 | const src = path.replace('..', '') 22 | 23 | return 24 | } else { 25 | return null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/LSLinkedReferences.js: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import Link from 'next/link' 3 | import { createPagePath } from '../utils' 4 | import LSBlockReference from './LSBlockReference' 5 | 6 | export default function LSLinkedReferences({ linkedRefs }) { 7 | if (linkedRefs == null) { 8 | return null 9 | } 10 | 11 | return ( 12 | 13 | 14 | Linked References 15 | 16 | 17 | {linkedRefs.map((ref) => { 18 | const { block, pageName } = ref 19 | return ( 20 | 21 | 22 | 23 | ) 24 | })} 25 | 26 | 27 | ) 28 | } 29 | 30 | function Reference({ pageName, block }) { 31 | const { id } = block 32 | 33 | return ( 34 | 35 | 42 | {pageName} 43 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/components/LSPage.js: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import { getLinkedRefs } from '../utils' 3 | import LSBlocks from './LSBlocks' 4 | import LSLinkedReferences from './LSLinkedReferences' 5 | 6 | export default function LSPage({ page }) { 7 | return ( 8 | 16 |

{page.pageName}

17 | 18 | 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/components/LSSrc.js: -------------------------------------------------------------------------------- 1 | import SyntaxHighlighter from 'react-syntax-highlighter' 2 | 3 | export default function LSSrc({ c }) { 4 | const { lines, language } = c 5 | const code = lines.slice(0, lines.length - 1).join('') 6 | return {code} 7 | } 8 | -------------------------------------------------------------------------------- /src/components/LSTable.js: -------------------------------------------------------------------------------- 1 | export default function LSTable({ c }) { 2 | return ( 3 | 4 | 5 | 6 | {c.header.map((h, i) => { 7 | return 8 | })} 9 | 10 | 11 | 12 | {c.groups[0].map((rows, i) => { 13 | return ( 14 | 15 | {rows.map((r, i) => { 16 | return 17 | })} 18 | 19 | ) 20 | })} 21 | 22 |
{h[0][1]}
{r[0][1]}
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import { Box, Flex } from '@chakra-ui/react' 2 | import Link from 'next/link' 3 | 4 | export default function Layout({ nav, content, toc }) { 5 | const logo = ( 6 | 7 | 8 | Logseq Publish 9 | 10 | 11 | ) 12 | 13 | return ( 14 | 15 | 23 | {logo} 24 | {nav} 25 | 26 | 27 | 28 | {logo} 29 | 30 | {content} 31 | 32 | {toc} 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/PageLink.js: -------------------------------------------------------------------------------- 1 | import { Button } from '@chakra-ui/react' 2 | import Link from 'next/link' 3 | import { useRouter } from 'next/router' 4 | import { useEffect, useRef } from 'react' 5 | import tippy from 'tippy.js' 6 | import 'tippy.js/dist/tippy.css' 7 | import 'tippy.js/themes/light.css' 8 | import { createPagePath } from '../utils' 9 | 10 | export default function PageLink({ pageName }) { 11 | const linkRef = useRef() 12 | const router = useRouter() 13 | const currentPageName = router.query.name 14 | const pagePath = createPagePath(pageName) 15 | 16 | useEffect(() => { 17 | if (currentPageName != pageName) { 18 | const onShow = async (instance) => { 19 | const res = await fetch(pagePath) 20 | const html = await res.text() 21 | const el = document.createElement('html') 22 | el.innerHTML = html 23 | const lsPage = el.querySelector('.ls-page') 24 | lsPage.classList.add('ls-page-preview') 25 | instance.setContent(lsPage) 26 | } 27 | const instance = tippy(linkRef.current, { 28 | onShow, 29 | maxWidth: 'none', 30 | placement: 'auto', 31 | theme: 'light', 32 | interactive: true, 33 | }) 34 | return () => { 35 | instance.destroy() 36 | } 37 | } 38 | }, [currentPageName, pageName, pagePath]) 39 | 40 | return ( 41 | 42 | 45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /src/components/PagesNav.js: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Input, 4 | Flex, 5 | InputGroup, 6 | InputLeftElement, 7 | InputRightElement, 8 | } from '@chakra-ui/react' 9 | import Link from 'next/link' 10 | import { SearchIcon, CloseIcon } from '@chakra-ui/icons' 11 | import { useState } from 'react' 12 | import { createPagePath } from '../utils' 13 | 14 | export default function PagesNav({ pages }) { 15 | const [search, setSearch] = useState('') 16 | 17 | const searchNotEmpty = search.length > 0 18 | 19 | const filteredPages = searchNotEmpty 20 | ? pages.filter((pageName) => 21 | pageName.toLowerCase().includes(search.toLowerCase()), 22 | ) 23 | : pages 24 | 25 | return ( 26 | 27 | 28 | 29 | 30 | 31 | setSearch(event.target.value)} 34 | /> 35 | {searchNotEmpty && ( 36 | setSearch('')}> 37 | 38 | 39 | )} 40 | 41 | 42 | {filteredPages.map((page) => ( 43 | 44 | 52 | {page} 53 | 54 | 55 | ))} 56 | 57 | 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /src/components/TOC.js: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import React from 'react' 3 | import { isHeading } from '../utils' 4 | import LSHeading from './LSHeading' 5 | 6 | function renderHeadings(blocks) { 7 | return blocks.map((b) => { 8 | let heading 9 | if (isHeading(b)) { 10 | console.log('isHeading') 11 | heading = ( 12 | (window.location.hash = b.id)}> 13 | 14 | 15 | ) 16 | } 17 | 18 | return ( 19 | 20 | {heading} 21 | {renderHeadings(b.children)} 22 | 23 | ) 24 | }) 25 | } 26 | 27 | export default function TOC({ page }) { 28 | return ( 29 | 41 | (window.location.hash = 'ls-page-title')} 46 | > 47 | On this Page 48 | 49 | {renderHeadings(page.children)} 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/pages/_app.js: -------------------------------------------------------------------------------- 1 | import { ChakraProvider } from '@chakra-ui/react' 2 | import '../styles/global.css' 3 | import '../styles/markdown.css' 4 | 5 | function MyApp({ Component, pageProps }) { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default MyApp 14 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/Layout' 2 | import PagesNav from '../components/PagesNav' 3 | import { pageNames, homepage, getPageByName } from '../utils' 4 | import LSPage from '../components/LSPage' 5 | import TOC from '../components/TOC' 6 | 7 | export default function Home() { 8 | const nav = 9 | const page = getPageByName(homepage) 10 | const content = 11 | const toc = 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/pages/[name].js: -------------------------------------------------------------------------------- 1 | import Layout from '../../components/Layout' 2 | import LSPage from '../../components/LSPage' 3 | import PagesNav from '../../components/PagesNav' 4 | import TOC from '../../components/TOC' 5 | import { getPageByName, pageNames } from '../../utils' 6 | 7 | export default function Page({ name }) { 8 | const nav = 9 | const page = getPageByName(name) 10 | const content = 11 | const toc = 12 | 13 | return 14 | } 15 | 16 | export async function getStaticPaths() { 17 | const paths = pageNames.map((name) => ({ 18 | params: { name }, 19 | })) 20 | 21 | return { paths, fallback: false } 22 | } 23 | 24 | export async function getStaticProps({ params }) { 25 | const { name } = params 26 | return { props: { name } } 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | .ls-page-preview { 2 | max-height: 70vh; 3 | overflow-y: auto; 4 | } 5 | 6 | .ls-page.markdown-body ul { 7 | padding-left: 0; 8 | } 9 | 10 | .ls-toc h2 { 11 | margin-left: 4px; 12 | } 13 | .ls-toc h3 { 14 | margin-left: 6px; 15 | } 16 | .ls-toc h4 { 17 | margin-left: 8px; 18 | } 19 | .ls-toc h5 { 20 | margin-left: 10px; 21 | } 22 | .ls-toc h6 { 23 | margin-left: 12px; 24 | } 25 | -------------------------------------------------------------------------------- /src/styles/markdown.css: -------------------------------------------------------------------------------- 1 | /* https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown.css */ 2 | 3 | .markdown-body .octicon { 4 | display: inline-block; 5 | fill: currentColor; 6 | vertical-align: text-bottom; 7 | } 8 | 9 | .markdown-body .anchor { 10 | float: left; 11 | line-height: 1; 12 | margin-left: -20px; 13 | padding-right: 4px; 14 | } 15 | 16 | .markdown-body .anchor:focus { 17 | outline: none; 18 | } 19 | 20 | .markdown-body h1 .octicon-link, 21 | .markdown-body h2 .octicon-link, 22 | .markdown-body h3 .octicon-link, 23 | .markdown-body h4 .octicon-link, 24 | .markdown-body h5 .octicon-link, 25 | .markdown-body h6 .octicon-link { 26 | color: #1b1f23; 27 | vertical-align: middle; 28 | visibility: hidden; 29 | } 30 | 31 | .markdown-body h1:hover .anchor, 32 | .markdown-body h2:hover .anchor, 33 | .markdown-body h3:hover .anchor, 34 | .markdown-body h4:hover .anchor, 35 | .markdown-body h5:hover .anchor, 36 | .markdown-body h6:hover .anchor { 37 | text-decoration: none; 38 | } 39 | 40 | .markdown-body h1:hover .anchor .octicon-link, 41 | .markdown-body h2:hover .anchor .octicon-link, 42 | .markdown-body h3:hover .anchor .octicon-link, 43 | .markdown-body h4:hover .anchor .octicon-link, 44 | .markdown-body h5:hover .anchor .octicon-link, 45 | .markdown-body h6:hover .anchor .octicon-link { 46 | visibility: visible; 47 | } 48 | 49 | .markdown-body h1:hover .anchor .octicon-link:before, 50 | .markdown-body h2:hover .anchor .octicon-link:before, 51 | .markdown-body h3:hover .anchor .octicon-link:before, 52 | .markdown-body h4:hover .anchor .octicon-link:before, 53 | .markdown-body h5:hover .anchor .octicon-link:before, 54 | .markdown-body h6:hover .anchor .octicon-link:before { 55 | width: 16px; 56 | height: 16px; 57 | content: ' '; 58 | display: inline-block; 59 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z'%3E%3C/path%3E%3C/svg%3E"); 60 | } 61 | .markdown-body { 62 | -ms-text-size-adjust: 100%; 63 | -webkit-text-size-adjust: 100%; 64 | line-height: 1.5; 65 | color: #24292e; 66 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, 67 | sans-serif, Apple Color Emoji, Segoe UI Emoji; 68 | font-size: 16px; 69 | line-height: 1.5; 70 | word-wrap: break-word; 71 | } 72 | 73 | .markdown-body details { 74 | display: block; 75 | } 76 | 77 | .markdown-body summary { 78 | display: list-item; 79 | } 80 | 81 | .markdown-body a { 82 | background-color: initial; 83 | } 84 | 85 | .markdown-body a:active, 86 | .markdown-body a:hover { 87 | outline-width: 0; 88 | } 89 | 90 | .markdown-body strong { 91 | font-weight: inherit; 92 | font-weight: bolder; 93 | } 94 | 95 | .markdown-body h1 { 96 | font-size: 2em; 97 | margin: 0.67em 0; 98 | } 99 | 100 | .markdown-body img { 101 | border-style: none; 102 | } 103 | 104 | .markdown-body code, 105 | .markdown-body kbd, 106 | .markdown-body pre { 107 | font-family: monospace, monospace; 108 | font-size: 1em; 109 | } 110 | 111 | .markdown-body hr { 112 | box-sizing: initial; 113 | height: 0; 114 | overflow: visible; 115 | } 116 | 117 | .markdown-body input { 118 | font: inherit; 119 | margin: 0; 120 | } 121 | 122 | .markdown-body input { 123 | overflow: visible; 124 | } 125 | 126 | .markdown-body [type='checkbox'] { 127 | box-sizing: border-box; 128 | padding: 0; 129 | } 130 | 131 | .markdown-body * { 132 | box-sizing: border-box; 133 | } 134 | 135 | .markdown-body input { 136 | font-family: inherit; 137 | font-size: inherit; 138 | line-height: inherit; 139 | } 140 | 141 | .markdown-body a { 142 | color: #0366d6; 143 | text-decoration: none; 144 | } 145 | 146 | .markdown-body a:hover { 147 | text-decoration: underline; 148 | } 149 | 150 | .markdown-body strong { 151 | font-weight: 600; 152 | } 153 | 154 | .markdown-body hr { 155 | height: 0; 156 | margin: 15px 0; 157 | overflow: hidden; 158 | background: transparent; 159 | border: 0; 160 | border-bottom: 1px solid #dfe2e5; 161 | } 162 | 163 | .markdown-body hr:after, 164 | .markdown-body hr:before { 165 | display: table; 166 | content: ''; 167 | } 168 | 169 | .markdown-body hr:after { 170 | clear: both; 171 | } 172 | 173 | .markdown-body table { 174 | border-spacing: 0; 175 | border-collapse: collapse; 176 | } 177 | 178 | .markdown-body td, 179 | .markdown-body th { 180 | padding: 0; 181 | } 182 | 183 | .markdown-body details summary { 184 | cursor: pointer; 185 | } 186 | 187 | .markdown-body kbd { 188 | display: inline-block; 189 | padding: 3px 5px; 190 | font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; 191 | line-height: 10px; 192 | color: #444d56; 193 | vertical-align: middle; 194 | background-color: #fafbfc; 195 | border: 1px solid #d1d5da; 196 | border-radius: 3px; 197 | box-shadow: inset 0 -1px 0 #d1d5da; 198 | } 199 | 200 | .markdown-body h1, 201 | .markdown-body h2, 202 | .markdown-body h3, 203 | .markdown-body h4, 204 | .markdown-body h5, 205 | .markdown-body h6 { 206 | margin-top: 0; 207 | margin-bottom: 0; 208 | } 209 | 210 | .markdown-body h1 { 211 | font-size: 32px; 212 | } 213 | 214 | .markdown-body h1, 215 | .markdown-body h2 { 216 | font-weight: 600; 217 | } 218 | 219 | .markdown-body h2 { 220 | font-size: 24px; 221 | } 222 | 223 | .markdown-body h3 { 224 | font-size: 20px; 225 | } 226 | 227 | .markdown-body h3, 228 | .markdown-body h4 { 229 | font-weight: 600; 230 | } 231 | 232 | .markdown-body h4 { 233 | font-size: 16px; 234 | } 235 | 236 | .markdown-body h5 { 237 | font-size: 14px; 238 | } 239 | 240 | .markdown-body h5, 241 | .markdown-body h6 { 242 | font-weight: 600; 243 | } 244 | 245 | .markdown-body h6 { 246 | font-size: 12px; 247 | } 248 | 249 | .markdown-body p { 250 | margin-top: 0; 251 | margin-bottom: 10px; 252 | } 253 | 254 | .markdown-body blockquote { 255 | margin: 0; 256 | } 257 | 258 | .markdown-body ol, 259 | .markdown-body ul { 260 | padding-left: 0; 261 | margin-top: 0; 262 | margin-bottom: 0; 263 | } 264 | 265 | .markdown-body ol ol, 266 | .markdown-body ul ol { 267 | list-style-type: lower-roman; 268 | } 269 | 270 | .markdown-body ol ol ol, 271 | .markdown-body ol ul ol, 272 | .markdown-body ul ol ol, 273 | .markdown-body ul ul ol { 274 | list-style-type: lower-alpha; 275 | } 276 | 277 | .markdown-body dd { 278 | margin-left: 0; 279 | } 280 | 281 | .markdown-body code, 282 | .markdown-body pre { 283 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; 284 | font-size: 12px; 285 | } 286 | 287 | .markdown-body pre { 288 | margin-top: 0; 289 | margin-bottom: 0; 290 | } 291 | 292 | .markdown-body input::-webkit-inner-spin-button, 293 | .markdown-body input::-webkit-outer-spin-button { 294 | margin: 0; 295 | -webkit-appearance: none; 296 | appearance: none; 297 | } 298 | 299 | .markdown-body :checked + .radio-label { 300 | position: relative; 301 | z-index: 1; 302 | border-color: #0366d6; 303 | } 304 | 305 | .markdown-body .border { 306 | border: 1px solid #e1e4e8 !important; 307 | } 308 | 309 | .markdown-body .border-0 { 310 | border: 0 !important; 311 | } 312 | 313 | .markdown-body .border-bottom { 314 | border-bottom: 1px solid #e1e4e8 !important; 315 | } 316 | 317 | .markdown-body .rounded-1 { 318 | border-radius: 3px !important; 319 | } 320 | 321 | .markdown-body .bg-white { 322 | background-color: #fff !important; 323 | } 324 | 325 | .markdown-body .bg-gray-light { 326 | background-color: #fafbfc !important; 327 | } 328 | 329 | .markdown-body .text-gray-light { 330 | color: #6a737d !important; 331 | } 332 | 333 | .markdown-body .mb-0 { 334 | margin-bottom: 0 !important; 335 | } 336 | 337 | .markdown-body .my-2 { 338 | margin-top: 8px !important; 339 | margin-bottom: 8px !important; 340 | } 341 | 342 | .markdown-body .pl-0 { 343 | padding-left: 0 !important; 344 | } 345 | 346 | .markdown-body .py-0 { 347 | padding-top: 0 !important; 348 | padding-bottom: 0 !important; 349 | } 350 | 351 | .markdown-body .pl-1 { 352 | padding-left: 4px !important; 353 | } 354 | 355 | .markdown-body .pl-2 { 356 | padding-left: 8px !important; 357 | } 358 | 359 | .markdown-body .py-2 { 360 | padding-top: 8px !important; 361 | padding-bottom: 8px !important; 362 | } 363 | 364 | .markdown-body .pl-3, 365 | .markdown-body .px-3 { 366 | padding-left: 16px !important; 367 | } 368 | 369 | .markdown-body .px-3 { 370 | padding-right: 16px !important; 371 | } 372 | 373 | .markdown-body .pl-4 { 374 | padding-left: 24px !important; 375 | } 376 | 377 | .markdown-body .pl-5 { 378 | padding-left: 32px !important; 379 | } 380 | 381 | .markdown-body .pl-6 { 382 | padding-left: 40px !important; 383 | } 384 | 385 | .markdown-body .f6 { 386 | font-size: 12px !important; 387 | } 388 | 389 | .markdown-body .lh-condensed { 390 | line-height: 1.25 !important; 391 | } 392 | 393 | .markdown-body .text-bold { 394 | font-weight: 600 !important; 395 | } 396 | 397 | .markdown-body .pl-c { 398 | color: #6a737d; 399 | } 400 | 401 | .markdown-body .pl-c1, 402 | .markdown-body .pl-s .pl-v { 403 | color: #005cc5; 404 | } 405 | 406 | .markdown-body .pl-e, 407 | .markdown-body .pl-en { 408 | color: #6f42c1; 409 | } 410 | 411 | .markdown-body .pl-s .pl-s1, 412 | .markdown-body .pl-smi { 413 | color: #24292e; 414 | } 415 | 416 | .markdown-body .pl-ent { 417 | color: #22863a; 418 | } 419 | 420 | .markdown-body .pl-k { 421 | color: #d73a49; 422 | } 423 | 424 | .markdown-body .pl-pds, 425 | .markdown-body .pl-s, 426 | .markdown-body .pl-s .pl-pse .pl-s1, 427 | .markdown-body .pl-sr, 428 | .markdown-body .pl-sr .pl-cce, 429 | .markdown-body .pl-sr .pl-sra, 430 | .markdown-body .pl-sr .pl-sre { 431 | color: #032f62; 432 | } 433 | 434 | .markdown-body .pl-smw, 435 | .markdown-body .pl-v { 436 | color: #e36209; 437 | } 438 | 439 | .markdown-body .pl-bu { 440 | color: #b31d28; 441 | } 442 | 443 | .markdown-body .pl-ii { 444 | color: #fafbfc; 445 | background-color: #b31d28; 446 | } 447 | 448 | .markdown-body .pl-c2 { 449 | color: #fafbfc; 450 | background-color: #d73a49; 451 | } 452 | 453 | .markdown-body .pl-c2:before { 454 | content: '^M'; 455 | } 456 | 457 | .markdown-body .pl-sr .pl-cce { 458 | font-weight: 700; 459 | color: #22863a; 460 | } 461 | 462 | .markdown-body .pl-ml { 463 | color: #735c0f; 464 | } 465 | 466 | .markdown-body .pl-mh, 467 | .markdown-body .pl-mh .pl-en, 468 | .markdown-body .pl-ms { 469 | font-weight: 700; 470 | color: #005cc5; 471 | } 472 | 473 | .markdown-body .pl-mi { 474 | font-style: italic; 475 | color: #24292e; 476 | } 477 | 478 | .markdown-body .pl-mb { 479 | font-weight: 700; 480 | color: #24292e; 481 | } 482 | 483 | .markdown-body .pl-md { 484 | color: #b31d28; 485 | background-color: #ffeef0; 486 | } 487 | 488 | .markdown-body .pl-mi1 { 489 | color: #22863a; 490 | background-color: #f0fff4; 491 | } 492 | 493 | .markdown-body .pl-mc { 494 | color: #e36209; 495 | background-color: #ffebda; 496 | } 497 | 498 | .markdown-body .pl-mi2 { 499 | color: #f6f8fa; 500 | background-color: #005cc5; 501 | } 502 | 503 | .markdown-body .pl-mdr { 504 | font-weight: 700; 505 | color: #6f42c1; 506 | } 507 | 508 | .markdown-body .pl-ba { 509 | color: #586069; 510 | } 511 | 512 | .markdown-body .pl-sg { 513 | color: #959da5; 514 | } 515 | 516 | .markdown-body .pl-corl { 517 | text-decoration: underline; 518 | color: #032f62; 519 | } 520 | 521 | .markdown-body .mb-0 { 522 | margin-bottom: 0 !important; 523 | } 524 | 525 | .markdown-body .my-2 { 526 | margin-bottom: 8px !important; 527 | } 528 | 529 | .markdown-body .my-2 { 530 | margin-top: 8px !important; 531 | } 532 | 533 | .markdown-body .pl-0 { 534 | padding-left: 0 !important; 535 | } 536 | 537 | .markdown-body .py-0 { 538 | padding-top: 0 !important; 539 | padding-bottom: 0 !important; 540 | } 541 | 542 | .markdown-body .pl-1 { 543 | padding-left: 4px !important; 544 | } 545 | 546 | .markdown-body .pl-2 { 547 | padding-left: 8px !important; 548 | } 549 | 550 | .markdown-body .py-2 { 551 | padding-top: 8px !important; 552 | padding-bottom: 8px !important; 553 | } 554 | 555 | .markdown-body .pl-3 { 556 | padding-left: 16px !important; 557 | } 558 | 559 | .markdown-body .pl-4 { 560 | padding-left: 24px !important; 561 | } 562 | 563 | .markdown-body .pl-5 { 564 | padding-left: 32px !important; 565 | } 566 | 567 | .markdown-body .pl-6 { 568 | padding-left: 40px !important; 569 | } 570 | 571 | .markdown-body .pl-7 { 572 | padding-left: 48px !important; 573 | } 574 | 575 | .markdown-body .pl-8 { 576 | padding-left: 64px !important; 577 | } 578 | 579 | .markdown-body .pl-9 { 580 | padding-left: 80px !important; 581 | } 582 | 583 | .markdown-body .pl-10 { 584 | padding-left: 96px !important; 585 | } 586 | 587 | .markdown-body .pl-11 { 588 | padding-left: 112px !important; 589 | } 590 | 591 | .markdown-body .pl-12 { 592 | padding-left: 128px !important; 593 | } 594 | 595 | .markdown-body hr { 596 | border-bottom-color: #eee; 597 | } 598 | 599 | .markdown-body kbd { 600 | display: inline-block; 601 | padding: 3px 5px; 602 | font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; 603 | line-height: 10px; 604 | color: #444d56; 605 | vertical-align: middle; 606 | background-color: #fafbfc; 607 | border: 1px solid #d1d5da; 608 | border-radius: 3px; 609 | box-shadow: inset 0 -1px 0 #d1d5da; 610 | } 611 | 612 | .markdown-body:after, 613 | .markdown-body:before { 614 | display: table; 615 | content: ''; 616 | } 617 | 618 | .markdown-body:after { 619 | clear: both; 620 | } 621 | 622 | .markdown-body > :first-child { 623 | margin-top: 0 !important; 624 | } 625 | 626 | .markdown-body > :last-child { 627 | margin-bottom: 0 !important; 628 | } 629 | 630 | .markdown-body a:not([href]) { 631 | color: inherit; 632 | text-decoration: none; 633 | } 634 | 635 | .markdown-body blockquote, 636 | .markdown-body details, 637 | .markdown-body dl, 638 | .markdown-body ol, 639 | .markdown-body p, 640 | .markdown-body pre, 641 | .markdown-body table, 642 | .markdown-body ul { 643 | margin-top: 0; 644 | margin-bottom: 16px; 645 | } 646 | 647 | .markdown-body hr { 648 | height: 0.25em; 649 | padding: 0; 650 | margin: 24px 0; 651 | background-color: #e1e4e8; 652 | border: 0; 653 | } 654 | 655 | .markdown-body blockquote { 656 | padding: 0 1em; 657 | color: #6a737d; 658 | border-left: 0.25em solid #dfe2e5; 659 | } 660 | 661 | .markdown-body blockquote > :first-child { 662 | margin-top: 0; 663 | } 664 | 665 | .markdown-body blockquote > :last-child { 666 | margin-bottom: 0; 667 | } 668 | 669 | .markdown-body h1, 670 | .markdown-body h2, 671 | .markdown-body h3, 672 | .markdown-body h4, 673 | .markdown-body h5, 674 | .markdown-body h6 { 675 | margin-top: 24px; 676 | margin-bottom: 16px; 677 | font-weight: 600; 678 | line-height: 1.25; 679 | } 680 | 681 | .markdown-body h1 { 682 | font-size: 2em; 683 | } 684 | 685 | .markdown-body h1, 686 | .markdown-body h2 { 687 | padding-bottom: 0.3em; 688 | border-bottom: 1px solid #eaecef; 689 | } 690 | 691 | .markdown-body h2 { 692 | font-size: 1.5em; 693 | } 694 | 695 | .markdown-body h3 { 696 | font-size: 1.25em; 697 | } 698 | 699 | .markdown-body h4 { 700 | font-size: 1em; 701 | } 702 | 703 | .markdown-body h5 { 704 | font-size: 0.875em; 705 | } 706 | 707 | .markdown-body h6 { 708 | font-size: 0.85em; 709 | color: #6a737d; 710 | } 711 | 712 | .markdown-body ol, 713 | .markdown-body ul { 714 | padding-left: 2em; 715 | } 716 | 717 | .markdown-body ol ol, 718 | .markdown-body ol ul, 719 | .markdown-body ul ol, 720 | .markdown-body ul ul { 721 | margin-top: 0; 722 | margin-bottom: 0; 723 | } 724 | 725 | .markdown-body li { 726 | word-wrap: break-all; 727 | } 728 | 729 | .markdown-body li > p { 730 | margin-top: 16px; 731 | } 732 | 733 | .markdown-body li + li { 734 | margin-top: 0.25em; 735 | } 736 | 737 | .markdown-body dl { 738 | padding: 0; 739 | } 740 | 741 | .markdown-body dl dt { 742 | padding: 0; 743 | margin-top: 16px; 744 | font-size: 1em; 745 | font-style: italic; 746 | font-weight: 600; 747 | } 748 | 749 | .markdown-body dl dd { 750 | padding: 0 16px; 751 | margin-bottom: 16px; 752 | } 753 | 754 | .markdown-body table { 755 | display: block; 756 | width: 100%; 757 | overflow: auto; 758 | } 759 | 760 | .markdown-body table th { 761 | font-weight: 600; 762 | } 763 | 764 | .markdown-body table td, 765 | .markdown-body table th { 766 | padding: 6px 13px; 767 | border: 1px solid #dfe2e5; 768 | } 769 | 770 | .markdown-body table tr { 771 | background-color: #fff; 772 | border-top: 1px solid #c6cbd1; 773 | } 774 | 775 | .markdown-body table tr:nth-child(2n) { 776 | background-color: #f6f8fa; 777 | } 778 | 779 | .markdown-body img { 780 | max-width: 100%; 781 | box-sizing: initial; 782 | background-color: #fff; 783 | } 784 | 785 | .markdown-body img[align='right'] { 786 | padding-left: 20px; 787 | } 788 | 789 | .markdown-body img[align='left'] { 790 | padding-right: 20px; 791 | } 792 | 793 | .markdown-body code { 794 | padding: 0.2em 0.4em; 795 | margin: 0; 796 | font-size: 85%; 797 | background-color: rgba(27, 31, 35, 0.05); 798 | border-radius: 3px; 799 | } 800 | 801 | .markdown-body pre { 802 | word-wrap: normal; 803 | } 804 | 805 | .markdown-body pre > code { 806 | padding: 0; 807 | margin: 0; 808 | font-size: 100%; 809 | word-break: normal; 810 | white-space: pre; 811 | background: transparent; 812 | border: 0; 813 | } 814 | 815 | .markdown-body .highlight { 816 | margin-bottom: 16px; 817 | } 818 | 819 | .markdown-body .highlight pre { 820 | margin-bottom: 0; 821 | word-break: normal; 822 | } 823 | 824 | .markdown-body .highlight pre, 825 | .markdown-body pre { 826 | padding: 16px; 827 | overflow: auto; 828 | font-size: 85%; 829 | line-height: 1.45; 830 | background-color: #f6f8fa; 831 | border-radius: 3px; 832 | } 833 | 834 | .markdown-body pre code { 835 | display: inline; 836 | max-width: auto; 837 | padding: 0; 838 | margin: 0; 839 | overflow: visible; 840 | line-height: inherit; 841 | word-wrap: normal; 842 | background-color: initial; 843 | border: 0; 844 | } 845 | 846 | .markdown-body .commit-tease-sha { 847 | display: inline-block; 848 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; 849 | font-size: 90%; 850 | color: #444d56; 851 | } 852 | 853 | .markdown-body .full-commit .btn-outline:not(:disabled):hover { 854 | color: #005cc5; 855 | border-color: #005cc5; 856 | } 857 | 858 | .markdown-body .blob-wrapper { 859 | overflow-x: auto; 860 | overflow-y: hidden; 861 | } 862 | 863 | .markdown-body .blob-wrapper-embedded { 864 | max-height: 240px; 865 | overflow-y: auto; 866 | } 867 | 868 | .markdown-body .blob-num { 869 | width: 1%; 870 | min-width: 50px; 871 | padding-right: 10px; 872 | padding-left: 10px; 873 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; 874 | font-size: 12px; 875 | line-height: 20px; 876 | color: rgba(27, 31, 35, 0.3); 877 | text-align: right; 878 | white-space: nowrap; 879 | vertical-align: top; 880 | cursor: pointer; 881 | -webkit-user-select: none; 882 | -moz-user-select: none; 883 | -ms-user-select: none; 884 | user-select: none; 885 | } 886 | 887 | .markdown-body .blob-num:hover { 888 | color: rgba(27, 31, 35, 0.6); 889 | } 890 | 891 | .markdown-body .blob-num:before { 892 | content: attr(data-line-number); 893 | } 894 | 895 | .markdown-body .blob-code { 896 | position: relative; 897 | padding-right: 10px; 898 | padding-left: 10px; 899 | line-height: 20px; 900 | vertical-align: top; 901 | } 902 | 903 | .markdown-body .blob-code-inner { 904 | overflow: visible; 905 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; 906 | font-size: 12px; 907 | color: #24292e; 908 | word-wrap: normal; 909 | white-space: pre; 910 | } 911 | 912 | .markdown-body .pl-token.active, 913 | .markdown-body .pl-token:hover { 914 | cursor: pointer; 915 | background: #ffea7f; 916 | } 917 | 918 | .markdown-body .tab-size[data-tab-size='1'] { 919 | -moz-tab-size: 1; 920 | tab-size: 1; 921 | } 922 | 923 | .markdown-body .tab-size[data-tab-size='2'] { 924 | -moz-tab-size: 2; 925 | tab-size: 2; 926 | } 927 | 928 | .markdown-body .tab-size[data-tab-size='3'] { 929 | -moz-tab-size: 3; 930 | tab-size: 3; 931 | } 932 | 933 | .markdown-body .tab-size[data-tab-size='4'] { 934 | -moz-tab-size: 4; 935 | tab-size: 4; 936 | } 937 | 938 | .markdown-body .tab-size[data-tab-size='5'] { 939 | -moz-tab-size: 5; 940 | tab-size: 5; 941 | } 942 | 943 | .markdown-body .tab-size[data-tab-size='6'] { 944 | -moz-tab-size: 6; 945 | tab-size: 6; 946 | } 947 | 948 | .markdown-body .tab-size[data-tab-size='7'] { 949 | -moz-tab-size: 7; 950 | tab-size: 7; 951 | } 952 | 953 | .markdown-body .tab-size[data-tab-size='8'] { 954 | -moz-tab-size: 8; 955 | tab-size: 8; 956 | } 957 | 958 | .markdown-body .tab-size[data-tab-size='9'] { 959 | -moz-tab-size: 9; 960 | tab-size: 9; 961 | } 962 | 963 | .markdown-body .tab-size[data-tab-size='10'] { 964 | -moz-tab-size: 10; 965 | tab-size: 10; 966 | } 967 | 968 | .markdown-body .tab-size[data-tab-size='11'] { 969 | -moz-tab-size: 11; 970 | tab-size: 11; 971 | } 972 | 973 | .markdown-body .tab-size[data-tab-size='12'] { 974 | -moz-tab-size: 12; 975 | tab-size: 12; 976 | } 977 | 978 | .markdown-body .task-list-item { 979 | list-style-type: none; 980 | } 981 | 982 | .markdown-body .task-list-item + .task-list-item { 983 | margin-top: 3px; 984 | } 985 | 986 | .markdown-body .task-list-item input { 987 | margin: 0 0.2em 0.25em -1.6em; 988 | vertical-align: middle; 989 | } 990 | 991 | .markdown-body ul { 992 | list-style: none; 993 | padding-left: 1em; 994 | } 995 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import d from '../data.json' 2 | 3 | const pages = d.blocks.map((p) => { 4 | let pageName = p['page-name'] 5 | if (pageName != null) { 6 | pageName = pageName.split('/').join(' | ') 7 | } 8 | 9 | return { 10 | ...p, 11 | pageName, 12 | } 13 | }) 14 | 15 | const DEFAULT_HOME_PAGE = 'Contents' 16 | export const homepage = d.homepage ?? DEFAULT_HOME_PAGE 17 | 18 | export const pageNames = pages.map((b) => b.pageName).sort() 19 | export function getPageByName(name) { 20 | return pages.find((p) => p.pageName === name) 21 | } 22 | 23 | let pageNameToLinkedRefs = {} 24 | export function getLinkedRefs(pageName) { 25 | return pageNameToLinkedRefs[pageName] 26 | } 27 | function addPageNameToLinkedRefs(inline, block, pageName) { 28 | const type = getInlineType(inline) 29 | const content = getInlineContent(inline) 30 | if (type === 'Link') { 31 | const linkContent = content.url 32 | const linkType = linkContent?.[0] 33 | if (linkType === 'Search') { 34 | const toPage = linkContent?.[1] 35 | 36 | if (toPage.includes('Gloss')) { 37 | } 38 | 39 | if (pageNames.includes(toPage)) { 40 | const param = { block, pageName } 41 | if (pageNameToLinkedRefs[toPage] == null) { 42 | pageNameToLinkedRefs[toPage] = [param] 43 | } else { 44 | pageNameToLinkedRefs[toPage].push(param) 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | // in case the inline structure changes 52 | export function getInlineType(inline) { 53 | return inline[0] 54 | } 55 | 56 | export function getInlineContent(inline) { 57 | return inline[1] 58 | } 59 | 60 | const CONTAINER_INLINE_TYPES = ['Paragraph'] 61 | export function isInlineContainer(inline) { 62 | const type = getInlineType(inline) 63 | return CONTAINER_INLINE_TYPES.includes(type) 64 | } 65 | 66 | let idToBlock = {} 67 | function addIDToBlock(block, pageName) { 68 | idToBlock[block.id] = { block, pageName } 69 | 70 | block.children.forEach((b) => addIDToBlock(b, pageName)) 71 | } 72 | export function getBlockByID(id) { 73 | return idToBlock[id].block 74 | } 75 | export function getPageNameByID(id) { 76 | return idToBlock[id].pageName 77 | } 78 | 79 | const blockPipeline = [addIDToBlock] 80 | function runBlockPipeline(block, pageName) { 81 | blockPipeline.forEach((fun) => fun(block, pageName)) 82 | } 83 | 84 | const inlinePipeline = [addPageNameToLinkedRefs] 85 | function runInlinePipeline(inline, block, pageName) { 86 | inlinePipeline.forEach((fun) => fun(inline, block, pageName)) 87 | } 88 | 89 | function walkPagesTreeAndCreateSupportData() { 90 | pages.forEach((page) => { 91 | const pageName = page.pageName 92 | walkBlocks(page.children, runBlockPipeline, runInlinePipeline, pageName) 93 | }) 94 | } 95 | 96 | function walkBlocks(blocks, doBlock, doInline, pageName) { 97 | blocks.forEach((b) => { 98 | doBlock(b, pageName) 99 | 100 | if (Array.isArray(b.title)) { 101 | walkInlines(b.title, b, doInline, pageName) 102 | } 103 | 104 | if (Array.isArray(b.body)) { 105 | walkInlines(b.body, b, doInline, pageName) 106 | } 107 | 108 | walkBlocks(b.children, doBlock, doInline, pageName) 109 | }) 110 | } 111 | 112 | function walkInlines(inlines, block, doInline, pageName) { 113 | inlines.forEach((inline) => walkInline(inline, block, doInline, pageName)) 114 | } 115 | 116 | function walkInline(inline, block, doInline, pageName) { 117 | if (isInlineContainer(inline)) { 118 | walkInlines(getInlineContent(inline), block, doInline, pageName) 119 | } else { 120 | doInline(inline, block, pageName) 121 | } 122 | } 123 | 124 | walkPagesTreeAndCreateSupportData() 125 | 126 | export function createPagePath(pageName, id) { 127 | const hash = id != null ? `#${id}` : '' 128 | return `/pages/${encodeURIComponent(pageName)}${hash}` 129 | } 130 | 131 | export function isHeading(b) { 132 | return b['heading-level'] != null 133 | } 134 | --------------------------------------------------------------------------------