Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive synopsis Fix #2024 #3140

Merged
merged 29 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fe55d51
added html to react
yathomasi Dec 28, 2021
7eb7a3c
utility html to react parser
yathomasi Dec 28, 2021
d2491c4
using code component and minor changes
yathomasi Dec 28, 2021
0357010
added args linker plugin to add ids to options
yathomasi Dec 30, 2021
2db56e1
removed hover css
yathomasi Dec 30, 2021
551aae5
fixed selection logic
yathomasi Dec 30, 2021
65cd894
removed html to react dependency
yathomasi Dec 30, 2021
0897dbe
added anchor icon in front of list items
yathomasi Dec 31, 2021
aced399
update to fix only use args from square brackets
yathomasi Jan 3, 2022
532c3ec
show anchor icon on list hover
yathomasi Jan 4, 2022
5ab5723
added outline
yathomasi Jan 4, 2022
f433cc9
linkified whole arguments with parameters
yathomasi Jan 4, 2022
fee75df
allowed two level of square brackets nestings
yathomasi Jan 4, 2022
1ff0a76
made link icon visible on focus
yathomasi Jan 4, 2022
1d98d06
updated command linker to add hash links for args
yathomasi Jan 4, 2022
8392d9f
fix codeclimate
yathomasi Jan 4, 2022
9ed8ed8
added id attribute to respective inline code block
yathomasi Jan 5, 2022
27960ac
added id to list and paragraph instead
yathomasi Jan 5, 2022
b8d44a4
updated code component to use all props
yathomasi Jan 6, 2022
ce1530f
added ids for argument to respective code block
yathomasi Jan 6, 2022
d083248
used lodash has property for key check
yathomasi Jan 6, 2022
6515b73
replaced custom component with prism hook
yathomasi Jan 7, 2022
6013dd2
generalized the arg regex
yathomasi Jan 8, 2022
3debb34
updated command linker
yathomasi Jan 8, 2022
aebacd4
reduced line for code climate fix
yathomasi Jan 8, 2022
ee52a0e
used same link icon source for consistency
yathomasi Jan 10, 2022
301b0aa
added pathname params
yathomasi Jan 11, 2022
b99fd5c
code fixes
yathomasi Jan 11, 2022
59363d3
aligned the icons
yathomasi Jan 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require('dotenv').config()
const path = require('path')

require('./config/prismjs/dvc')
require('./config/prismjs/usage')
// require('./config/prismjs/usage')
julieg18 marked this conversation as resolved.
Show resolved Hide resolved
require('./config/prismjs/dvctable')

const apiMiddleware = require('./src/server/middleware/api')
Expand Down Expand Up @@ -66,6 +66,7 @@ const plugins = [
plugins: [
'gatsby-remark-embedder',
'gatsby-remark-dvc-linker',
'gatsby-remark-args-linker',
{
resolve: 'gatsby-remark-prismjs',
options: {
Expand Down
92 changes: 92 additions & 0 deletions plugins/gatsby-remark-args-linker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const visit = require('unist-util-visit')

const argsRegex = new RegExp(/\-{1,2}[a-zA-Z-]*/, 'ig')

function patch(context, key, value) {
if (!context[key]) {
yathomasi marked this conversation as resolved.
Show resolved Hide resolved
context[key] = value
}

return context[key]
}
const svgIcon = `<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path 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"></path></svg>`
yathomasi marked this conversation as resolved.
Show resolved Hide resolved

const addIdAttrToNode = (node, id) => {
const data = patch(node, `data`, {})

patch(data, `id`, id)
patch(data, `htmlAttributes`, {})
patch(data, `hProperties`, {})
patch(data.htmlAttributes, `id`, id)
patch(data.hProperties, `id`, id)
}

module.exports = (
{ markdownAST },
{ className = 'anchor', isIconAfterHeader = false }
) => {
visit(
markdownAST,
node =>
node.type === 'listItem' &&
node.children.some(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will it match lists like "- some text --flag"? also does it mean that we math lists outside the Options section?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it matches list with 1st line start with inline Code block i.e `` and that starts with -. Yes, If it exactly matches this then it's possible outside options too. We can work on this more if it collide but I found that unique for my quick overlook over the docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a list that matches the pattern in our DVCLive docs: https://dvc-org-interactive-syn-zo8hwd.herokuapp.com/doc/dvclive/dvclive-with-dvc#--live-no-cache. Not sure if it does any harm to have links there... Though if we wanted, we could have gatsby-remark-args-linker only look through pages in our /command-reference/ directory 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, Julie and Thomas. q: how hard it is to make it strict and well-defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The possible way I could think of was to make it strict or well defined is by checking heading Options and looking into a consecutive list and to the list item.
But, I found it difficult to do so with Markdown AST. We could easily achieve that if we had something easy/well-defined APIs/functions as in DOM manipulation. Otherwise, we need to come up with some hacky way to do so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, but it's also about replacing node.children.some with something like node.children.first

'docs/command-reference' - we need to make it a param? since it might be different across different websites later.

Copy link
Contributor

@julieg18 julieg18 Jan 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, but it's also about replacing node.children.some with something like node.children.first

This would check the first child in the list, right? While this would work for almost all use cases /repro options starts with targets:

image

It is the only one as far as I know though :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops sorry, I missed this conversation.

Copy link
Contributor Author

@yathomasi yathomasi Jan 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'docs/command-reference' - we need to make it a param? since it might be different across different websites later.

I have added the params for paths which can be a string(path) or an array of paths. For now, I have set it to docs/command-reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, but it's also about replacing node.children.some with something like node.children.first

I have made some changes and pushed.

item =>
item.type === 'paragraph' &&
item.children[0]?.type === 'inlineCode' &&
String(item.children[0]?.value).startsWith('-')
),
listItemNode => {
const isParagraphNode = listItemNode.children?.[0].type === 'paragraph'
if (!isParagraphNode) return
const paragraphNode = listItemNode.children[0]
const isFirstArgNode =
paragraphNode.children[0]?.type === 'inlineCode' &&
String(paragraphNode.children[0]?.value).startsWith('-')
if (isFirstArgNode) {
const firstArgNode = paragraphNode.children[0]
const value = firstArgNode.value
const id = value.match(argsRegex)[0]
addIdAttrToNode(listItemNode, id)

const data = patch(listItemNode, `data`, {})
patch(data, `htmlAttributes`, {})
patch(data, `hProperties`, {})
patch(data.hProperties, `style`, `position:relative;`)
const label = id.split(`-`).join(` `)
const method = isIconAfterHeader ? `push` : `unshift`
listItemNode.children[method]({
type: `link`,
url: `#${id}`,
title: null,
children: [],
data: {
hProperties: {
'aria-label': `option ${label.trim()} permalink`,
class: `${className} ${isIconAfterHeader ? `after` : `before`}`
},
hChildren: [
{
type: `raw`,
// The Octicon link icon is the default. But users can set their own icon via the "icon" option.
value: svgIcon
}
]
}
})

const isSecondArgNode =
String(paragraphNode.children[1]?.value).trim() === ',' &&
paragraphNode.children[2]?.type === 'inlineCode' &&
String(paragraphNode.children[2].value).startsWith('-')
if (isSecondArgNode) {
const secondArgNode = paragraphNode.children[2]
const value = secondArgNode.value
const id = value.match(argsRegex)[0]
addIdAttrToNode(paragraphNode, id)
}
}
}
)
// Manipulate AST
return markdownAST
}
12 changes: 12 additions & 0 deletions plugins/gatsby-remark-args-linker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "gatsby-remark-args-linker",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
28 changes: 13 additions & 15 deletions plugins/gatsby-remark-dvc-linker/commandLinker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,29 @@ const { getItemByPath } = require('../../src/utils/shared/sidebar')

const DVC_REGEXP = /dvc\s+[a-z][a-z-.]*/
const COMMAND_REGEXP = /^[a-z][a-z-]*$/
const ARGS_REGEXP = new RegExp(/\-{1,2}[a-zA-Z-]*/, 'ig')
const COMMAND_ROOT = '/doc/command-reference/'

module.exports = astNode => {
const node = astNode[0]
const parent = astNode[2]

if (parent.type !== 'link' && DVC_REGEXP.test(node.value)) {
const parts = node.value.split(/\s+/)
const baseUrl = `${COMMAND_ROOT}${parts[1]}`
julieg18 marked this conversation as resolved.
Show resolved Hide resolved
let url

const hasThirdSegment = parts[2] && COMMAND_REGEXP.test(parts[2])
const isCommandPageExists = getItemByPath(`${COMMAND_ROOT}${parts[1]}`)
const isSubcommandPageExists =
isCommandPageExists &&
hasThirdSegment &&
getItemByPath(`${COMMAND_ROOT}${parts[1]}/${parts[2]}`)

if (isSubcommandPageExists) {
url = `${COMMAND_ROOT}${parts[1]}/${parts[2]}`
} else if (isCommandPageExists && hasThirdSegment) {
url = `${COMMAND_ROOT}${parts[1]}#${parts[2]}`
} else if (isCommandPageExists) {
url = `${COMMAND_ROOT}${parts[1]}`
if (isCommandPageExists) {
url = baseUrl
}
for (const arg of parts.slice(2)) {
if (arg && COMMAND_REGEXP.test(arg) && getItemByPath(`${url}/${arg}`)) {
url = `${url}/${arg}`
} else if (arg && ARGS_REGEXP.test(arg)) {
const id = arg.match(ARGS_REGEXP)[0]
url = `${url}#${id}`
break
}
}

createLinkNode(url, astNode)
}

Expand Down
78 changes: 78 additions & 0 deletions src/components/Code/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react'
yathomasi marked this conversation as resolved.
Show resolved Hide resolved
import cn from 'classnames'

import dvc from '../../../config/prismjs/dvc-commands'

const dvcRegex = new RegExp(`dvc (?:${dvc.join('|')})`, 'ig')
const usageRegex = new RegExp(
`(^|\n)\s*(usage|positional arguments|optional arguments)`,
'ig'
)
const squareArgsRegex = new RegExp(
/(?<=\[)(?:[^\]\[]+|\[(?:[^\]\[]+|\[[^\]\[]*\])*\])*(?=\])/,
'ig'
) // regex that matches a square bracketed argument that allows two levels of nested square brackets
const argsRegex = new RegExp(/\-{1,2}[a-zA-Z-]*/, 'ig')

const encodeChars = (rawText: string) => {
return rawText.replaceAll('<', '&lt;')
}
const pipe = (...args: any[]) => args.reduce((acc, el) => el(acc))
const linkifyArgs = (args: string) => {
if (args.includes('|')) {
const argsArr = args.split('|')
const argsWrappedArr = argsArr.map(linkifyArg)
return argsWrappedArr.join('|')
}
return linkifyArg(args)
}

const linkifyArg = (arg: string) => {
const dashArgs = arg.match(argsRegex)
if (!dashArgs) return arg
if (dashArgs.length > 1) {
return linkify(arg)
}
return `<a class="token args" href='#${dashArgs[0]}'>${arg}</a>`
}
const linkify = (str: string) => {
return str.replace(argsRegex, linker)
}
const linker = (str: string) => {
return `<a class="token args" href='#${str.replaceAll(' ', '_')}'>${str}</a>`
}

const wrapUsage = (text: string) =>
text.replace(usageRegex, `<span class="token usage">$&</span>`)
const wrapDvc = (text: string) =>
text.replace(dvcRegex, `<span class="token dvc">$&</span>`)
const linkifyArgsSquared = (text: string) =>
text.replace(squareArgsRegex, linkifyArgs)

export const wrapWithTags = (text: string) => {
text = encodeChars(text)
return pipe(text, wrapUsage, wrapDvc, linkifyArgsSquared)
}

const formatText = (text: string) => {
const wrapped = wrapWithTags(text)
return wrapped
}

const Code: React.FC<{ className: string }> = ({ className, children }) => {
if (className === 'language-usage') {
julieg18 marked this conversation as resolved.
Show resolved Hide resolved
const codeText = Array.isArray(children)
? children.join('')
: String(children) || ''
const ReactText = formatText(codeText)
return (
<code
className={cn(className)}
dangerouslySetInnerHTML={{ __html: ReactText }}
></code>
)
}
return <code className={cn(className)}>{children}</code>
}

export default Code
17 changes: 17 additions & 0 deletions src/components/Documentation/Markdown/Main/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,27 @@
}
}

a.token.args {
color: var(--color-light-blue);
julieg18 marked this conversation as resolved.
Show resolved Hide resolved
outline-color: var(--color-light-blue-hover);
}

:global(a.gatsby-resp-image-link)::after {
content: unset;
}

li .anchor svg {
visibility: hidden;
}

li:hover .anchor svg {
visibility: visible;
}

li .anchor:focus svg {
visibility: visible;
}

.anchor {
margin-left: -24px;
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Documentation/Markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Collapsible from 'react-collapsible'

import Main from './Main'
import Link from '../../Link'
import Code from '../../Code'
import Tooltip from './Tooltip'

import * as styles from './styles.module.css'
Expand Down Expand Up @@ -204,7 +205,8 @@ const renderAst = new (rehypeReact as any)({
cards: Cards,
details: Details,
toggle: Toggle,
tab: Tab
tab: Tab,
code: Code
}
}).Compiler

Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"target": "es5",
"module": "commonjs",
"resolveJsonModule": true,
"lib": ["dom", "es2015", "es2017"],
"lib": ["dom", "es2015", "es2017", "es2021"],
julieg18 marked this conversation as resolved.
Show resolved Hide resolved
"jsx": "react",
"sourceMap": true,
"strict": true,
Expand Down
Loading