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

A quick experiment to get allow React components in Markdown pages and throughout the site #610

Merged
merged 30 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b781e43
Add experimental react support within markdown
chiedo Oct 16, 2020
797adb1
Add a cool table component
chiedo Oct 18, 2020
c5a34e4
Set up foundations for code switcher
chiedo Oct 19, 2020
d264c65
Add better code block component
chiedo Oct 19, 2020
d51688b
Everything working but CodeEditor
chiedo Oct 19, 2020
35c38e5
Add CodeEditor example
chiedo Oct 19, 2020
b1fa93f
Move object hash to dependencies
chiedo Oct 19, 2020
548853f
Add some comments to explain what the code in server does
chiedo Oct 20, 2020
6e5673c
Make code a little less sloppy
chiedo Oct 20, 2020
8ef2822
Add performance optimization
chiedo Oct 20, 2020
89d745b
Remove uneeded code
chiedo Oct 20, 2020
3c71bc7
Minor patches
chiedo Oct 20, 2020
b8c8e0f
Minor performance improvement
chiedo Oct 20, 2020
7573ff2
Add some comments that explain logic
chiedo Oct 20, 2020
fe81204
Make the rendering engine safe
chiedo Oct 21, 2020
a48998c
Keep all the React Engine logic in one file
chiedo Oct 21, 2020
561bd33
Update access-permissions-on-github.md
heiskr Dec 2, 2020
74d43b5
Reset a few more things
heiskr Dec 2, 2020
c084360
Update index.js
heiskr Dec 2, 2020
0c3309d
Reset package.json and package-lock.json
heiskr Dec 2, 2020
07a7822
Merge branch 'main' into experiment-with-react-and-mdx
heiskr Dec 2, 2020
d830284
Readd new packages
heiskr Dec 2, 2020
84473cb
Update engine.js
heiskr Dec 2, 2020
791c7c6
Run lint
heiskr Dec 2, 2020
8731b68
Update engine.js
heiskr Dec 2, 2020
632ef6c
Update check-deps.js
heiskr Dec 2, 2020
2d07086
Add documentation for using React components
chiedo Dec 3, 2020
f89c983
Update README.md
heiskr Dec 3, 2020
92ba8d9
Merge branch 'main' into experiment-with-react-and-mdx
heiskr Dec 3, 2020
c410020
Update engine.js
heiskr Dec 3, 2020
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
25,147 changes: 25,147 additions & 0 deletions assets/js/react-dom.development.js

Large diffs are not rendered by default.

3,318 changes: 3,318 additions & 0 deletions assets/js/react.development.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ intro: 'While you can grant read/write access to collaborators on a personal rep
versions:
free-pro-team: '*'
enterprise-server: '*'
interactive: true
---

### Personal user accounts
Expand All @@ -17,16 +18,42 @@ A repository owned by a user account has two permission levels: the *repository

### Organization accounts

#### Code Block Component
<!--react-->
<CodeBlock language="javascript"
chiedo marked this conversation as resolved.
Show resolved Hide resolved
js={`var i;
for (i = 0; i < cars.length; i++) {
text += cars[i] + "<br>";
}`}

php={`for ($x = 0; $x <= 10; $x++) {
echo "The number is: $x <br>";
}`}

python={`for x in "banana":
print(x)
`}
/>
<!--end-react-->

Organization members can have *owner*{% if currentVersion == "free-pro-team@latest" %}, *billing manager*,{% endif %} or *member* roles. Owners have complete administrative access to your organization{% if currentVersion == "free-pro-team@latest" %}, while billing managers can manage billing settings{% endif %}. Member is the default role for everyone else. You can manage access permissions for multiple members at a time with teams. For more information, see:
- "[Permission levels for an organization](/articles/permission-levels-for-an-organization)"
- "[Project board permissions for an organization](/articles/project-board-permissions-for-an-organization)"
- "[Repository permission levels for an organization](/articles/repository-permission-levels-for-an-organization)"
- "[About teams](/articles/about-teams)"

## Code Editor Component

<!--react-->
<CodeEditor language='javascript' code={`console.log('hello world')`} />
<!--end-react-->


{% if currentVersion == "free-pro-team@latest" %}

### Enterprise accounts


*Enterprise owners* have ultimate power over the enterprise account and can take every action in the enterprise account. *Billing managers* can manage your enterprise account's billing settings. Members and outside collaborators of organizations owned by your enterprise account are automatically members of the enterprise account, although they have no access to the enterprise account itself or its settings. For more information, see "[Roles for an enterprise account](/articles/roles-for-an-enterprise-account)."

{% data reusables.gated-features.enterprise-accounts %}
Expand Down
2 changes: 2 additions & 0 deletions includes/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@
<link rel="stylesheet" href="/dist/index.css">
<link rel="alternate icon" type="image/png" href="/assets/images/site/favicon.png">
<link rel="icon" type="image/svg+xml" href="/assets/images/site/favicon.svg">
<script src="/assets/js/react.development.js" crossorigin></script>
<script src="/assets/js/react-dom.development.js" crossorigin></script>
</head>
11 changes: 11 additions & 0 deletions javascripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import experiment from './experiment'
import copyCode from './copy-code'
import { fillCsrf } from './get-csrf'
import initializeEvents from './events'
import CodeBlock from '../react/CodeBlock'
import CoolTable from '../react/CoolTable'
import CodeEditor from '../react/CodeEditor'

import 'react-tabs/style/react-tabs.css'

document.addEventListener('DOMContentLoaded', async () => {
displayPlatformSpecificContent()
Expand All @@ -35,3 +40,9 @@ document.addEventListener('DOMContentLoaded', async () => {
copyCode()
initializeEvents()
})

module.export = {
CodeBlock,
CoolTable,
CodeEditor
}
3 changes: 3 additions & 0 deletions lib/frontmatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ const schema = {
quickstart: { type: 'string' },
learn: { type: 'string' }
}
},
interactive: {
type: 'boolean'
}
}
}
Expand Down
27 changes: 26 additions & 1 deletion lib/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ const pathUtils = require('./path-utils')
const Permalink = require('./permalink')
const languages = require('./languages')
const renderContent = require('./render-content')
const { renderReact } = require('./react/engine')
const frontmatter = require('./frontmatter')
const products = require('./all-products')
const slash = require('slash')


class Page {
constructor (opts) {
assert(opts.relativePath, 'relativePath is required')
Expand Down Expand Up @@ -119,9 +121,30 @@ class Page {
this.title = await renderContent(this.rawTitle, context, { textOnly: true, encodeEntities: true })
this.shortTitle = await renderContent(this.shortTitle, context, { textOnly: true, encodeEntities: true })

const markdown = this.mapTopic
let markdown = this.mapTopic
? getMapTopicContent(this, context.pages, context.redirects)
: this.markdown

// If the article is interactive parse the React!
if (this.interactive) {
// Search for the react code comments to find the react components
const reactComponents = markdown.match(/<!--react-->(.*?)<!--end-react-->/gms)
chiedo marked this conversation as resolved.
Show resolved Hide resolved

// Render each of the react components in the markdown
chiedo marked this conversation as resolved.
Show resolved Hide resolved
for (const index in reactComponents) {
let componentStr = reactComponents[index]

// Remove the React comment indicators
componentStr = componentStr.replace('<!--react-->\n', '').replace('<!--react-->', '')
Copy link
Contributor

Choose a reason for hiding this comment

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

Possible to use a RegExp with replace, like

Suggested change
componentStr = componentStr.replace('<!--react-->\n', '').replace('<!--react-->', '')
componentStr = componentStr.replace(/<!--react-->\n?/g, '')

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you confirm this works @heiskr and if it does can you commit it? I get confused by the first replace having a new line character and the next one not. I personally like coding verbose. But that's a personal preference.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think its fine as is, just an FYI more than anything.

componentStr = componentStr.replace('\n<!--end-react-->', '').replace('<!--end-react-->', '')

// Get the rendered component
const renderedComponent = await renderReact(componentStr)

// Replace the react component with the rendered markdown
markdown = markdown.replace(reactComponents[index], renderedComponent)
}
}

const html = await renderContent(markdown, context)

Expand Down Expand Up @@ -163,6 +186,7 @@ class Page {
return cleanedHTML
}


// Allow other modules (like custom liquid tags) to make one-off requests
// for a page's rendered properties like `title` and `intro`
async renderProp (propName, context, opts = { unwrap: false }) {
Expand All @@ -179,6 +203,7 @@ class Page {

const html = await renderContent(prop, context, opts)


if (!opts.unwrap) return html

// The unwrap option removes surrounding tags from a string, preserving any inner HTML
Expand Down
22 changes: 22 additions & 0 deletions lib/react/babel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const babel = require('@babel/core')

const reactBabelOptions = {
presets: [
'@babel/preset-env',
'@babel/preset-react'
],
plugins: [
'@babel/plugin-transform-react-jsx',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-class-properties',
'@babel/transform-runtime'
]
}

const transform = code =>
babel.transform(code, reactBabelOptions).code

module.exports = {
transform: transform,
reactBabelOptions: reactBabelOptions
}
23 changes: 23 additions & 0 deletions lib/react/engine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { renderToString } = require('react-dom/server')
const { transform } = require('./babel')

// These all need to be here even though eslint doesn't think so
/* eslint-disable */
const React = require('react')
const CodeBlock = require('../../dist/react/CodeBlock')
const CoolTable = require('../../dist/react/CoolTable')
const CodeEditor = require('../../dist/react/CodeEditor')
/* eslint-enable */

const renderReact = async componentStr => {
const componentName = componentStr.match(/[^<]([a-zA-Z])*/gm)[0]
chiedo marked this conversation as resolved.
Show resolved Hide resolved
const jsx = `<div className="react-component-${componentName}">\n${componentStr}\n</div>`
const component = transform(jsx)

// eslint-disable-next-line
return renderToString(eval(component))
chiedo marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = {
renderReact
}
Loading