Merge pull request #1019 from 30-seconds/graphql_custom_schemas
Create custom 'Snippet' schema in GraphQL
This commit is contained in:
@ -21,4 +21,9 @@ module.exports = {
|
|||||||
moduleName: `_30s`,
|
moduleName: `_30s`,
|
||||||
rollupInputFile: `imports.temp.js`,
|
rollupInputFile: `imports.temp.js`,
|
||||||
testModuleFile: `test/_30s.js`,
|
testModuleFile: `test/_30s.js`,
|
||||||
|
// Requirable JSONs
|
||||||
|
requirables: [
|
||||||
|
`snippets.json`,
|
||||||
|
`archivedSnippets.json`
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
327
gatsby-node.js
327
gatsby-node.js
@ -2,6 +2,14 @@ const path = require(`path`);
|
|||||||
const { createFilePath } = require(`gatsby-source-filesystem`);
|
const { createFilePath } = require(`gatsby-source-filesystem`);
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
|
const { getTextualContent, getCodeBlocks, optimizeAllNodes } = require(`./src/docs/util`);
|
||||||
|
|
||||||
|
const requirables = [];
|
||||||
|
|
||||||
|
config.requirables.forEach(fileName => {
|
||||||
|
requirables.push(require(`./snippet_data/${fileName}`));
|
||||||
|
});
|
||||||
|
|
||||||
const toKebabCase = str =>
|
const toKebabCase = str =>
|
||||||
str &&
|
str &&
|
||||||
str
|
str
|
||||||
@ -9,97 +17,6 @@ const toKebabCase = str =>
|
|||||||
.map(x => x.toLowerCase())
|
.map(x => x.toLowerCase())
|
||||||
.join('-');
|
.join('-');
|
||||||
|
|
||||||
exports.createPages = ({ graphql, actions }) => {
|
|
||||||
const { createPage } = actions;
|
|
||||||
|
|
||||||
const snippetPage = path.resolve(`./src/docs/templates/SnippetPage.js`);
|
|
||||||
const tagPage = path.resolve(`./src/docs/templates/TagPage.js`);
|
|
||||||
return graphql(
|
|
||||||
`
|
|
||||||
{
|
|
||||||
allMarkdownRemark(
|
|
||||||
sort: { fields: [frontmatter___title], order: ASC }
|
|
||||||
limit: 1000
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
frontmatter {
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
fileAbsolutePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
).then(result => {
|
|
||||||
if (result.errors) {
|
|
||||||
throw result.errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create individual snippet pages.
|
|
||||||
const snippets = result.data.allMarkdownRemark.edges;
|
|
||||||
|
|
||||||
snippets.forEach((post, index) => {
|
|
||||||
if(post.node.fileAbsolutePath.indexOf('README') !== -1)
|
|
||||||
return;
|
|
||||||
if (post.node.fileAbsolutePath.indexOf(config.snippetArchivePath) === -1)
|
|
||||||
createPage({
|
|
||||||
path: `/snippet${post.node.fields.slug}`,
|
|
||||||
component: snippetPage,
|
|
||||||
context: {
|
|
||||||
slug: post.node.fields.slug,
|
|
||||||
scope: `./snippets`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
else
|
|
||||||
createPage({
|
|
||||||
path: `/archive${post.node.fields.slug}`,
|
|
||||||
component: snippetPage,
|
|
||||||
context: {
|
|
||||||
slug: post.node.fields.slug,
|
|
||||||
scope: `./snippets_archive`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create tag pages.
|
|
||||||
const tags = snippets.reduce((acc, post) => {
|
|
||||||
if (!post.node.frontmatter || !post.node.frontmatter.tags) return acc;
|
|
||||||
const primaryTag = post.node.frontmatter.tags.split(',')[0];
|
|
||||||
if (!acc.includes(primaryTag)) acc.push(primaryTag);
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
tags.forEach(tag => {
|
|
||||||
const tagPath = `/tag/${toKebabCase(tag)}/`;
|
|
||||||
const tagRegex = `/^\\s*${tag}/`;
|
|
||||||
createPage({
|
|
||||||
path: tagPath,
|
|
||||||
component: tagPage,
|
|
||||||
context: {
|
|
||||||
tag,
|
|
||||||
tagRegex,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
createPage({
|
|
||||||
path: `/beginner`,
|
|
||||||
component: tagPage,
|
|
||||||
context: {
|
|
||||||
tag: `beginner snippets`,
|
|
||||||
tagRegex: `/beginner/`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.onCreateNode = ({ node, actions, getNode }) => {
|
exports.onCreateNode = ({ node, actions, getNode }) => {
|
||||||
const { createNodeField } = actions;
|
const { createNodeField } = actions;
|
||||||
|
|
||||||
@ -112,3 +29,231 @@ exports.onCreateNode = ({ node, actions, getNode }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.sourceNodes = ({ actions, createNodeId, createContentDigest, getNodesByType }) => {
|
||||||
|
const { createTypes, createNode } = actions;
|
||||||
|
const typeDefs = `
|
||||||
|
type Snippet implements Node {
|
||||||
|
html: HtmlData
|
||||||
|
tags: TagData
|
||||||
|
title: String
|
||||||
|
code: CodeData
|
||||||
|
id: String
|
||||||
|
slug: String
|
||||||
|
path: String
|
||||||
|
text: TextData
|
||||||
|
archived: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type HtmlData @infer {
|
||||||
|
full: String
|
||||||
|
text: String
|
||||||
|
code: String
|
||||||
|
example: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodeData @infer {
|
||||||
|
src: String
|
||||||
|
example: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextData @infer {
|
||||||
|
full: String
|
||||||
|
short: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type TagData @infer {
|
||||||
|
primary: String
|
||||||
|
all: [String]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
createTypes(typeDefs);
|
||||||
|
|
||||||
|
const markdownNodes = getNodesByType('MarkdownRemark');
|
||||||
|
|
||||||
|
const snippetNodes = requirables
|
||||||
|
.reduce((acc, sArr) => {
|
||||||
|
const archivedScope = sArr.meta.scope.indexOf('archive') !== -1;
|
||||||
|
return ({
|
||||||
|
...acc,
|
||||||
|
...sArr.data.reduce((snippets, snippet) => {
|
||||||
|
return ({
|
||||||
|
...snippets,
|
||||||
|
[snippet.id]: { ...snippet, archived: archivedScope}
|
||||||
|
});
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
Object.entries(snippetNodes).forEach(([id, sNode]) => {
|
||||||
|
let mNode = markdownNodes.find(mN => mN.frontmatter.title === id);
|
||||||
|
let nodeContent = {
|
||||||
|
id,
|
||||||
|
tags: {
|
||||||
|
all: sNode.attributes.tags,
|
||||||
|
primary: sNode.attributes.tags[0]
|
||||||
|
},
|
||||||
|
title: mNode.frontmatter.title,
|
||||||
|
code: {
|
||||||
|
src: sNode.attributes.codeBlocks.es6,
|
||||||
|
example: sNode.attributes.codeBlocks.example
|
||||||
|
},
|
||||||
|
slug: mNode.fields.slug,
|
||||||
|
path: mNode.fileAbsolutePath,
|
||||||
|
text: {
|
||||||
|
full: sNode.attributes.text,
|
||||||
|
short: sNode.attributes.text.slice(0, sNode.attributes.text.indexOf('\n\n'))
|
||||||
|
},
|
||||||
|
archived: sNode.archived
|
||||||
|
};
|
||||||
|
createNode({
|
||||||
|
id: createNodeId(`snippet-${sNode.meta.hash}`),
|
||||||
|
parent: null,
|
||||||
|
children: [],
|
||||||
|
internal: {
|
||||||
|
type: 'Snippet',
|
||||||
|
content: JSON.stringify(nodeContent),
|
||||||
|
contentDigest: createContentDigest(nodeContent)
|
||||||
|
},
|
||||||
|
...nodeContent
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.createResolvers = ({ createResolvers }) => createResolvers({
|
||||||
|
Snippet: {
|
||||||
|
html: {
|
||||||
|
resolve: async (source, _, context, info) => {
|
||||||
|
const resolver = info.schema.getType("MarkdownRemark").getFields()["html"].resolve;
|
||||||
|
const node = await context.nodeModel.nodeStore.getNodesByType('MarkdownRemark').filter(v => v.frontmatter.title === source.title)[0];
|
||||||
|
const args = {}; // arguments passed to the resolver
|
||||||
|
const html = await resolver(node, args);
|
||||||
|
return {
|
||||||
|
full: `${html}`,
|
||||||
|
text: `${getTextualContent(html, true)}`,
|
||||||
|
code: `${optimizeAllNodes(getCodeBlocks(html).code)}`,
|
||||||
|
example: `${optimizeAllNodes(getCodeBlocks(html).example)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.createPages = ({ graphql, actions }) => {
|
||||||
|
const { createPage } = actions;
|
||||||
|
|
||||||
|
const snippetPage = path.resolve(`./src/docs/templates/SnippetPage.js`);
|
||||||
|
const tagPage = path.resolve(`./src/docs/templates/TagPage.js`);
|
||||||
|
|
||||||
|
return graphql(
|
||||||
|
`
|
||||||
|
{
|
||||||
|
allSnippet(sort: {fields: id}) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
tags {
|
||||||
|
all
|
||||||
|
primary
|
||||||
|
}
|
||||||
|
text {
|
||||||
|
full
|
||||||
|
short
|
||||||
|
}
|
||||||
|
title
|
||||||
|
html {
|
||||||
|
code
|
||||||
|
example
|
||||||
|
full
|
||||||
|
text
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
src
|
||||||
|
example
|
||||||
|
}
|
||||||
|
archived
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
).then(result => {
|
||||||
|
if (result.errors) {
|
||||||
|
throw result.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create individual snippet pages.
|
||||||
|
const snippets = result.data.allSnippet.edges;
|
||||||
|
|
||||||
|
snippets.forEach(snippet => {
|
||||||
|
if (!snippet.node.archived) {
|
||||||
|
createPage({
|
||||||
|
path: `/snippet${snippet.node.slug}`,
|
||||||
|
component: snippetPage,
|
||||||
|
context: {
|
||||||
|
snippet: snippet.node
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createPage({
|
||||||
|
path: `/archive${snippet.node.slug}`,
|
||||||
|
component: snippetPage,
|
||||||
|
context: {
|
||||||
|
snippet: snippet.node
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create tag pages.
|
||||||
|
const tags = [...new Set(
|
||||||
|
snippets.map(snippet => (snippet.node.tags || {primary: null}).primary)
|
||||||
|
)]
|
||||||
|
.filter(Boolean)
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
|
tags.forEach(tag => {
|
||||||
|
const tagPath = `/tag/${toKebabCase(tag)}/`;
|
||||||
|
const taggedSnippets = snippets
|
||||||
|
.filter(snippet => snippet.node.tags.primary === tag)
|
||||||
|
.filter(snippet => !snippet.node.archived)
|
||||||
|
.map(({node}) => ({
|
||||||
|
title: node.title,
|
||||||
|
html: node.html.text,
|
||||||
|
tags: node.tags.all,
|
||||||
|
id: node.slug.slice(1)
|
||||||
|
}));
|
||||||
|
createPage({
|
||||||
|
path: tagPath,
|
||||||
|
component: tagPage,
|
||||||
|
context: {
|
||||||
|
tag,
|
||||||
|
snippets: taggedSnippets
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const beginnerSnippets = snippets
|
||||||
|
.filter(({ node }) => node.tags.all.includes('beginner'))
|
||||||
|
.filter(snippet => !snippet.node.archived)
|
||||||
|
.map(({ node }) => ({
|
||||||
|
title: node.title,
|
||||||
|
html: node.html.text,
|
||||||
|
tags: node.tags.all,
|
||||||
|
id: node.slug.slice(1)
|
||||||
|
}));
|
||||||
|
|
||||||
|
createPage({
|
||||||
|
path: `/beginner`,
|
||||||
|
component: tagPage,
|
||||||
|
context: {
|
||||||
|
tag: `beginner snippets`,
|
||||||
|
snippets: beginnerSnippets
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -42,24 +42,18 @@ const CardCorner = ({ difficulty = 'intermediate' }) => (
|
|||||||
// ===================================================
|
// ===================================================
|
||||||
const FullCard = ({ snippetData, difficulty, isDarkMode }) => {
|
const FullCard = ({ snippetData, difficulty, isDarkMode }) => {
|
||||||
const [examplesOpen, setExamplesOpen] = React.useState(false);
|
const [examplesOpen, setExamplesOpen] = React.useState(false);
|
||||||
const tags = snippetData.tags;
|
|
||||||
let cardCodeHtml = `${optimizeAllNodes(
|
|
||||||
getCodeBlocks(snippetData.html).code,
|
|
||||||
)}`;
|
|
||||||
let cardExamplesHtml = `${optimizeAllNodes(
|
|
||||||
getCodeBlocks(snippetData.html).example,
|
|
||||||
)}`;
|
|
||||||
return (
|
return (
|
||||||
<div className='card'>
|
<div className='card'>
|
||||||
<CardCorner difficulty={difficulty} />
|
<CardCorner difficulty={difficulty} />
|
||||||
<h4 className='card-title'>{snippetData.title}</h4>
|
<h4 className='card-title'>{snippetData.title}</h4>
|
||||||
{tags.map(tag => (
|
{snippetData.tags.map(tag => (
|
||||||
<span className='tag' key={`tag_${tag}`}>{tag}</span>
|
<span className='tag' key={`tag_${tag}`}>{tag}</span>
|
||||||
))}
|
))}
|
||||||
<div
|
<div
|
||||||
className='card-description'
|
className='card-description'
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `${getTextualContent(snippetData.html)}`,
|
__html: snippetData.textHtml,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className='card-bottom'>
|
<div className='card-bottom'>
|
||||||
@ -83,12 +77,9 @@ const FullCard = ({ snippetData, difficulty, isDarkMode }) => {
|
|||||||
aria-label='Copy to clipboard'
|
aria-label='Copy to clipboard'
|
||||||
/>
|
/>
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
{/* <button className="button button-b button-social-sh" aria-label="Share">
|
|
||||||
<ShareIcon />
|
|
||||||
</button> */}
|
|
||||||
<pre
|
<pre
|
||||||
className={`card-code language-${config.language}`}
|
className={`card-code language-${config.language}`}
|
||||||
dangerouslySetInnerHTML={{ __html: cardCodeHtml }}
|
dangerouslySetInnerHTML={{ __html: snippetData.codeHtml }}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className='button button-example-toggler'
|
className='button button-example-toggler'
|
||||||
@ -99,7 +90,7 @@ const FullCard = ({ snippetData, difficulty, isDarkMode }) => {
|
|||||||
{examplesOpen && (
|
{examplesOpen && (
|
||||||
<pre
|
<pre
|
||||||
className='section card-examples language-js'
|
className='section card-examples language-js'
|
||||||
dangerouslySetInnerHTML={{ __html: cardExamplesHtml }}
|
dangerouslySetInnerHTML={{ __html: snippetData.exampleHtml }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -124,13 +115,12 @@ const ShortCard = ({
|
|||||||
<div className='card short'>
|
<div className='card short'>
|
||||||
<CardCorner difficulty={difficulty} />
|
<CardCorner difficulty={difficulty} />
|
||||||
<h4 className='card-title'>
|
<h4 className='card-title'>
|
||||||
|
|
||||||
{snippetData.title}
|
{snippetData.title}
|
||||||
</h4>
|
</h4>
|
||||||
<div
|
<div
|
||||||
className='card-description'
|
className='card-description'
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `${getTextualContent(snippetData.html, true)}`,
|
__html: snippetData.html,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import SnippetCard from '../components/SnippetCard'
|
|||||||
// Individual snippet category/tag page
|
// Individual snippet category/tag page
|
||||||
// ===================================================
|
// ===================================================
|
||||||
const ArchivePage = props => {
|
const ArchivePage = props => {
|
||||||
const posts = props.data.allMarkdownRemark.edges;
|
const snippets = props.data.allSnippet.edges;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
props.dispatch(pushNewPage('Archived', props.path));
|
props.dispatch(pushNewPage('Archived', props.path));
|
||||||
@ -24,17 +24,17 @@ const ArchivePage = props => {
|
|||||||
<h2 className='page-title'>Archived snippets</h2>
|
<h2 className='page-title'>Archived snippets</h2>
|
||||||
<p className='page-sub-title'>These snippets, while useful and interesting, didn't quite make it into the repository due to either having very specific use-cases or being outdated. However we felt like they might still be useful to some readers, so here they are.</p>
|
<p className='page-sub-title'>These snippets, while useful and interesting, didn't quite make it into the repository due to either having very specific use-cases or being outdated. However we felt like they might still be useful to some readers, so here they are.</p>
|
||||||
<p className='light-sub'>Click on a snippet card to view the snippet.</p>
|
<p className='light-sub'>Click on a snippet card to view the snippet.</p>
|
||||||
{posts &&
|
{snippets &&
|
||||||
posts.map(({ node }) => (
|
snippets.map(({ node }) => (
|
||||||
<SnippetCard
|
<SnippetCard
|
||||||
key={`snippet_${node.id}`}
|
key={`snippet_${node.id}`}
|
||||||
short
|
short
|
||||||
archived
|
archived
|
||||||
snippetData={{
|
snippetData={{
|
||||||
title: node.frontmatter.title,
|
title: node.title,
|
||||||
html: node.html,
|
html: node.html.text,
|
||||||
tags: node.frontmatter.tags.split(',').map(v => v.trim()),
|
tags: node.tags.all,
|
||||||
id: node.fields.slug.slice(1),
|
id: node.id
|
||||||
}}
|
}}
|
||||||
isDarkMode={props.isDarkMode}
|
isDarkMode={props.isDarkMode}
|
||||||
/>
|
/>
|
||||||
@ -55,25 +55,19 @@ export default connect(
|
|||||||
)(ArchivePage);
|
)(ArchivePage);
|
||||||
|
|
||||||
export const archivePageQuery = graphql`
|
export const archivePageQuery = graphql`
|
||||||
query ArchivePage {
|
query archiveListing {
|
||||||
allMarkdownRemark(
|
allSnippet(filter: {archived: {eq: true}}) {
|
||||||
limit: 1000
|
|
||||||
sort: { fields: [frontmatter___title], order: ASC }
|
|
||||||
filter: { fileAbsolutePath: { regex: "/snippets_archive/" }, frontmatter: {title: { ne: "" } } }
|
|
||||||
) {
|
|
||||||
totalCount
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
title
|
||||||
|
html {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
tags {
|
||||||
|
all
|
||||||
|
primary
|
||||||
|
}
|
||||||
id
|
id
|
||||||
html
|
|
||||||
rawMarkdownBody
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,14 +13,7 @@ import SimpleCard from '../components/SimpleCard';
|
|||||||
// Home page (splash and search)
|
// Home page (splash and search)
|
||||||
// ===================================================
|
// ===================================================
|
||||||
const IndexPage = props => {
|
const IndexPage = props => {
|
||||||
const snippets = props.data.snippetDataJson.data.map(snippet => ({
|
const snippets = props.data.allSnippet.edges;
|
||||||
title: snippet.title,
|
|
||||||
html: props.data.allMarkdownRemark.edges.find(
|
|
||||||
v => v.node.frontmatter.title === snippet.title,
|
|
||||||
).node.html,
|
|
||||||
tags: snippet.attributes.tags,
|
|
||||||
id: snippet.id
|
|
||||||
}));
|
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = React.useState(props.searchQuery);
|
const [searchQuery, setSearchQuery] = React.useState(props.searchQuery);
|
||||||
const [searchResults, setSearchResults] = React.useState(snippets);
|
const [searchResults, setSearchResults] = React.useState(snippets);
|
||||||
@ -31,9 +24,9 @@ const IndexPage = props => {
|
|||||||
let results = snippets;
|
let results = snippets;
|
||||||
if (q.trim().length)
|
if (q.trim().length)
|
||||||
results = snippets.filter(
|
results = snippets.filter(
|
||||||
v =>
|
({node}) =>
|
||||||
v.tags.filter(t => t.indexOf(q) !== -1).length ||
|
node.tags.all.filter(t => t.indexOf(q) !== -1).length ||
|
||||||
v.title.toLowerCase().indexOf(q) !== -1,
|
node.title.toLowerCase().indexOf(q) !== -1,
|
||||||
);
|
);
|
||||||
setSearchResults(results);
|
setSearchResults(results);
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
@ -80,11 +73,16 @@ const IndexPage = props => {
|
|||||||
Click on a snippet card to view the snippet.
|
Click on a snippet card to view the snippet.
|
||||||
</p>
|
</p>
|
||||||
<h2 className='page-sub-title'>Search results</h2>
|
<h2 className='page-sub-title'>Search results</h2>
|
||||||
{searchResults.map(snippet => (
|
{searchResults.map(({node}) => (
|
||||||
<SnippetCard
|
<SnippetCard
|
||||||
short
|
short
|
||||||
key={`snippet_${snippet.id}`}
|
key={`snippet_${node.id}`}
|
||||||
snippetData={snippet}
|
snippetData={{
|
||||||
|
title: node.title,
|
||||||
|
html: node.html.text,
|
||||||
|
tags: node.tags.all,
|
||||||
|
id: node.id
|
||||||
|
}}
|
||||||
isDarkMode={props.isDarkMode}
|
isDarkMode={props.isDarkMode}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@ -139,22 +137,17 @@ export const indexPageQuery = graphql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) {
|
allSnippet {
|
||||||
data {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
attributes {
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allMarkdownRemark {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
html
|
title
|
||||||
frontmatter {
|
html {
|
||||||
title
|
text
|
||||||
}
|
}
|
||||||
|
tags {
|
||||||
|
all
|
||||||
|
}
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,28 +15,15 @@ import SimpleCard from '../components/SimpleCard';
|
|||||||
// Snippet list page
|
// Snippet list page
|
||||||
// ===================================================
|
// ===================================================
|
||||||
const ListPage = props => {
|
const ListPage = props => {
|
||||||
const snippets = props.data.snippetDataJson.data.map(snippet => ({
|
const snippets = props.data.allSnippet.edges;
|
||||||
title: snippet.title,
|
const archivedSnippets = props.data.allArchivedSnippet.edges;
|
||||||
html: props.data.allMarkdownRemark.edges.find(
|
|
||||||
v => v.node.frontmatter.title === snippet.title,
|
const tags = [...new Set(
|
||||||
).node.html,
|
snippets.map(snippet => (snippet.node.tags || { primary: null }).primary)
|
||||||
tags: snippet.attributes.tags,
|
)]
|
||||||
id: snippet.id,
|
.filter(Boolean)
|
||||||
}));
|
.sort((a, b) => a.localeCompare(b));
|
||||||
const archivedSnippets = props.data.snippetsArchiveDataJson.data.map(snippet => ({
|
|
||||||
title: snippet.title,
|
|
||||||
html: props.data.allMarkdownRemark.edges.find(
|
|
||||||
v => v.node.frontmatter.title === snippet.title,
|
|
||||||
).node.html,
|
|
||||||
tags: snippet.attributes.tags,
|
|
||||||
id: snippet.id,
|
|
||||||
}));
|
|
||||||
const tags = snippets.reduce((acc, snippet) => {
|
|
||||||
if (!snippet.tags) return acc;
|
|
||||||
const primaryTag = snippet.tags[0];
|
|
||||||
if (!acc.includes(primaryTag)) acc.push(primaryTag);
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
const staticPages = [
|
const staticPages = [
|
||||||
{
|
{
|
||||||
url: 'beginner',
|
url: 'beginner',
|
||||||
@ -79,12 +66,17 @@ const ListPage = props => {
|
|||||||
</Link>
|
</Link>
|
||||||
</h3>
|
</h3>
|
||||||
{snippets
|
{snippets
|
||||||
.filter(snippet => snippet.tags[0] === tag)
|
.filter(({node}) => node.tags.primary === tag)
|
||||||
.map(snippet => (
|
.map(({node}) => (
|
||||||
<SnippetCard
|
<SnippetCard
|
||||||
key={`snippet_${snippet.id}`}
|
key={`snippet_${node.id}`}
|
||||||
short
|
short
|
||||||
snippetData={snippet}
|
snippetData={{
|
||||||
|
title: node.title,
|
||||||
|
html: node.html.text,
|
||||||
|
tags: node.tags.all,
|
||||||
|
id: node.id
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@ -95,12 +87,17 @@ const ListPage = props => {
|
|||||||
Archived snippets
|
Archived snippets
|
||||||
</Link></h3>
|
</Link></h3>
|
||||||
{archivedSnippets
|
{archivedSnippets
|
||||||
.map(snippet => (
|
.map(({node}) => (
|
||||||
<SnippetCard
|
<SnippetCard
|
||||||
key={`a_snippet_${snippet.id}`}
|
key={`a_snippet_${node.id}`}
|
||||||
short
|
short
|
||||||
archived
|
archived
|
||||||
snippetData={snippet}
|
snippetData={{
|
||||||
|
title: node.title,
|
||||||
|
html: node.html.text,
|
||||||
|
tags: node.tags.all,
|
||||||
|
id: node.id
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<br/>
|
<br/>
|
||||||
@ -131,31 +128,32 @@ export default connect(
|
|||||||
|
|
||||||
export const listPageQuery = graphql`
|
export const listPageQuery = graphql`
|
||||||
query snippetListing {
|
query snippetListing {
|
||||||
snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) {
|
allSnippet(filter: {archived: {eq: false}}) {
|
||||||
data {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
attributes {
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snippetsArchiveDataJson : snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets_archive"} }) {
|
|
||||||
data {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
attributes {
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allMarkdownRemark {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
html
|
title
|
||||||
frontmatter {
|
html {
|
||||||
title
|
text
|
||||||
}
|
}
|
||||||
|
tags {
|
||||||
|
all
|
||||||
|
primary
|
||||||
|
}
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allArchivedSnippet: allSnippet(filter: {archived: {eq: true}}) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
title
|
||||||
|
html {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
tags {
|
||||||
|
all
|
||||||
|
}
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,14 +12,7 @@ import SnippetCard from '../components/SnippetCard';
|
|||||||
// Search page
|
// Search page
|
||||||
// ===================================================
|
// ===================================================
|
||||||
const SearchPage = props => {
|
const SearchPage = props => {
|
||||||
const snippets = props.data.snippetDataJson.data.map(snippet => ({
|
const snippets = props.data.allSnippet.edges;
|
||||||
title: snippet.title,
|
|
||||||
html: props.data.allMarkdownRemark.edges.find(
|
|
||||||
v => v.node.frontmatter.title === snippet.title,
|
|
||||||
).node.html,
|
|
||||||
tags: snippet.attributes.tags,
|
|
||||||
id: snippet.id,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = React.useState(props.searchQuery);
|
const [searchQuery, setSearchQuery] = React.useState(props.searchQuery);
|
||||||
const [searchResults, setSearchResults] = React.useState(snippets);
|
const [searchResults, setSearchResults] = React.useState(snippets);
|
||||||
@ -30,9 +23,9 @@ const SearchPage = props => {
|
|||||||
let results = snippets;
|
let results = snippets;
|
||||||
if (q.trim().length)
|
if (q.trim().length)
|
||||||
results = snippets.filter(
|
results = snippets.filter(
|
||||||
v =>
|
({ node }) =>
|
||||||
v.tags.filter(t => t.indexOf(q) !== -1).length ||
|
node.tags.all.filter(t => t.indexOf(q) !== -1).length ||
|
||||||
v.title.toLowerCase().indexOf(q) !== -1,
|
node.title.toLowerCase().indexOf(q) !== -1,
|
||||||
);
|
);
|
||||||
setSearchResults(results);
|
setSearchResults(results);
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
@ -75,11 +68,16 @@ const SearchPage = props => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h2 className='page-sub-title'>Search results</h2>
|
<h2 className='page-sub-title'>Search results</h2>
|
||||||
{searchResults.map(snippet => (
|
{searchResults.map(({node}) => (
|
||||||
<SnippetCard
|
<SnippetCard
|
||||||
key={`snippet_${snippet.id}`}
|
key={`snippet_${node.id}`}
|
||||||
short
|
short
|
||||||
snippetData={snippet}
|
snippetData={{
|
||||||
|
title: node.title,
|
||||||
|
html: node.html.text,
|
||||||
|
tags: node.tags.all,
|
||||||
|
id: node.id
|
||||||
|
}}
|
||||||
isDarkMode={props.isDarkMode}
|
isDarkMode={props.isDarkMode}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@ -102,22 +100,17 @@ export default connect(
|
|||||||
|
|
||||||
export const searchPageQuery = graphql`
|
export const searchPageQuery = graphql`
|
||||||
query searchSnippetList {
|
query searchSnippetList {
|
||||||
snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) {
|
allSnippet {
|
||||||
data {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
attributes {
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allMarkdownRemark {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
html
|
title
|
||||||
frontmatter {
|
html {
|
||||||
title
|
text
|
||||||
}
|
}
|
||||||
|
tags {
|
||||||
|
all
|
||||||
|
}
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
69
src/docs/templates/SnippetPage.js
vendored
69
src/docs/templates/SnippetPage.js
vendored
@ -11,14 +11,11 @@ import BackArrowIcon from '../components/SVGs/BackArrowIcon';
|
|||||||
// Individual snippet page template
|
// Individual snippet page template
|
||||||
// ===================================================
|
// ===================================================
|
||||||
const SnippetPage = props => {
|
const SnippetPage = props => {
|
||||||
const post = props.data.markdownRemark;
|
const snippet = props.pageContext.snippet;
|
||||||
const postData = props.data.snippetDataJson.data.find(
|
|
||||||
v => v.title === post.frontmatter.title,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Meta title={post.frontmatter.title} description={post.excerpt} />
|
<Meta title={snippet.title} description={snippet.text.short} />
|
||||||
<Shell>
|
<Shell>
|
||||||
<Link
|
<Link
|
||||||
className='link-back'
|
className='link-back'
|
||||||
@ -29,10 +26,13 @@ const SnippetPage = props => {
|
|||||||
</Link>
|
</Link>
|
||||||
<SnippetCard
|
<SnippetCard
|
||||||
snippetData={{
|
snippetData={{
|
||||||
title: postData.title,
|
title: snippet.title,
|
||||||
html: post.html,
|
html: snippet.html.full,
|
||||||
code: postData.attributes.codeBlocks.code,
|
codeHtml: snippet.html.code,
|
||||||
tags: postData.attributes.tags,
|
exampleHtml: snippet.html.example,
|
||||||
|
textHtml: snippet.html.text,
|
||||||
|
code: snippet.code.src,
|
||||||
|
tags: snippet.tags.all,
|
||||||
}}
|
}}
|
||||||
isDarkMode={props.isDarkMode}
|
isDarkMode={props.isDarkMode}
|
||||||
/>
|
/>
|
||||||
@ -50,54 +50,3 @@ export default connect(
|
|||||||
}),
|
}),
|
||||||
null,
|
null,
|
||||||
)(SnippetPage);
|
)(SnippetPage);
|
||||||
|
|
||||||
export const pageQuery = graphql`
|
|
||||||
query BlogPostBySlug($slug: String!, $scope: String!) {
|
|
||||||
logo: file(absolutePath: { regex: "/logo_reverse_md.png/" }) {
|
|
||||||
id
|
|
||||||
childImageSharp {
|
|
||||||
fixed(height: 45, width: 45) {
|
|
||||||
src
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allMarkdownRemark {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
fileAbsolutePath
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markdownRemark(fields: { slug: { eq: $slug } }) {
|
|
||||||
id
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
excerpt(pruneLength: 160)
|
|
||||||
html
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snippetDataJson(meta: { type: { eq: "snippetArray" }, scope: {eq: $scope} }) {
|
|
||||||
data {
|
|
||||||
title
|
|
||||||
id
|
|
||||||
attributes {
|
|
||||||
text
|
|
||||||
codeBlocks {
|
|
||||||
es6
|
|
||||||
example
|
|
||||||
}
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
41
src/docs/templates/TagPage.js
vendored
41
src/docs/templates/TagPage.js
vendored
@ -13,8 +13,8 @@ import { capitalize, getRawCodeBlocks as getCodeBlocks } from '../util';
|
|||||||
// Individual snippet category/tag page
|
// Individual snippet category/tag page
|
||||||
// ===================================================
|
// ===================================================
|
||||||
const TagRoute = props => {
|
const TagRoute = props => {
|
||||||
const posts = props.data.allMarkdownRemark.edges;
|
|
||||||
const tag = props.pageContext.tag;
|
const tag = props.pageContext.tag;
|
||||||
|
const snippets = props.pageContext.snippets;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
props.dispatch(pushNewPage(capitalize(tag), props.path));
|
props.dispatch(pushNewPage(capitalize(tag), props.path));
|
||||||
@ -26,16 +26,16 @@ const TagRoute = props => {
|
|||||||
<Shell>
|
<Shell>
|
||||||
<h2 className='page-title'>{capitalize(tag)}</h2>
|
<h2 className='page-title'>{capitalize(tag)}</h2>
|
||||||
<p className='light-sub'>Click on a snippet card to view the snippet.</p>
|
<p className='light-sub'>Click on a snippet card to view the snippet.</p>
|
||||||
{posts &&
|
{snippets &&
|
||||||
posts.map(({ node }) => (
|
snippets.map(snippet => (
|
||||||
<SnippetCard
|
<SnippetCard
|
||||||
key={`snippet_${node.id}`}
|
key={`snippet_${snippet.id}`}
|
||||||
short
|
short
|
||||||
snippetData={{
|
snippetData={{
|
||||||
title: node.frontmatter.title,
|
title: snippet.title,
|
||||||
html: node.html,
|
html: snippet.html,
|
||||||
tags: node.frontmatter.tags.split(',').map(v => v.trim()),
|
tags: snippet.tags,
|
||||||
id: node.fields.slug.slice(1),
|
id: snippet.id,
|
||||||
}}
|
}}
|
||||||
isDarkMode={props.isDarkMode}
|
isDarkMode={props.isDarkMode}
|
||||||
/>
|
/>
|
||||||
@ -54,28 +54,3 @@ export default connect(
|
|||||||
}),
|
}),
|
||||||
null,
|
null,
|
||||||
)(TagRoute);
|
)(TagRoute);
|
||||||
|
|
||||||
export const tagPageQuery = graphql`
|
|
||||||
query TagPage($tagRegex: String) {
|
|
||||||
allMarkdownRemark(
|
|
||||||
limit: 1000
|
|
||||||
sort: { fields: [frontmatter___title], order: ASC }
|
|
||||||
filter: { fileAbsolutePath: { regex: "/snippets(?!_archive)/" }, frontmatter: { tags: { regex: $tagRegex } } }
|
|
||||||
) {
|
|
||||||
totalCount
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
html
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
frontmatter {
|
|
||||||
title
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user