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`,
|
||||
rollupInputFile: `imports.temp.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 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 =>
|
||||
str &&
|
||||
str
|
||||
@ -9,97 +17,6 @@ const toKebabCase = str =>
|
||||
.map(x => x.toLowerCase())
|
||||
.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 }) => {
|
||||
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 [examplesOpen, setExamplesOpen] = React.useState(false);
|
||||
const tags = snippetData.tags;
|
||||
let cardCodeHtml = `${optimizeAllNodes(
|
||||
getCodeBlocks(snippetData.html).code,
|
||||
)}`;
|
||||
let cardExamplesHtml = `${optimizeAllNodes(
|
||||
getCodeBlocks(snippetData.html).example,
|
||||
)}`;
|
||||
|
||||
return (
|
||||
<div className='card'>
|
||||
<CardCorner difficulty={difficulty} />
|
||||
<h4 className='card-title'>{snippetData.title}</h4>
|
||||
{tags.map(tag => (
|
||||
{snippetData.tags.map(tag => (
|
||||
<span className='tag' key={`tag_${tag}`}>{tag}</span>
|
||||
))}
|
||||
<div
|
||||
className='card-description'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `${getTextualContent(snippetData.html)}`,
|
||||
__html: snippetData.textHtml,
|
||||
}}
|
||||
/>
|
||||
<div className='card-bottom'>
|
||||
@ -83,12 +77,9 @@ const FullCard = ({ snippetData, difficulty, isDarkMode }) => {
|
||||
aria-label='Copy to clipboard'
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
{/* <button className="button button-b button-social-sh" aria-label="Share">
|
||||
<ShareIcon />
|
||||
</button> */}
|
||||
<pre
|
||||
className={`card-code language-${config.language}`}
|
||||
dangerouslySetInnerHTML={{ __html: cardCodeHtml }}
|
||||
dangerouslySetInnerHTML={{ __html: snippetData.codeHtml }}
|
||||
/>
|
||||
<button
|
||||
className='button button-example-toggler'
|
||||
@ -99,7 +90,7 @@ const FullCard = ({ snippetData, difficulty, isDarkMode }) => {
|
||||
{examplesOpen && (
|
||||
<pre
|
||||
className='section card-examples language-js'
|
||||
dangerouslySetInnerHTML={{ __html: cardExamplesHtml }}
|
||||
dangerouslySetInnerHTML={{ __html: snippetData.exampleHtml }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -124,13 +115,12 @@ const ShortCard = ({
|
||||
<div className='card short'>
|
||||
<CardCorner difficulty={difficulty} />
|
||||
<h4 className='card-title'>
|
||||
|
||||
{snippetData.title}
|
||||
</h4>
|
||||
<div
|
||||
className='card-description'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `${getTextualContent(snippetData.html, true)}`,
|
||||
__html: snippetData.html,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -11,7 +11,7 @@ import SnippetCard from '../components/SnippetCard'
|
||||
// Individual snippet category/tag page
|
||||
// ===================================================
|
||||
const ArchivePage = props => {
|
||||
const posts = props.data.allMarkdownRemark.edges;
|
||||
const snippets = props.data.allSnippet.edges;
|
||||
|
||||
React.useEffect(() => {
|
||||
props.dispatch(pushNewPage('Archived', props.path));
|
||||
@ -24,17 +24,17 @@ const ArchivePage = props => {
|
||||
<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='light-sub'>Click on a snippet card to view the snippet.</p>
|
||||
{posts &&
|
||||
posts.map(({ node }) => (
|
||||
{snippets &&
|
||||
snippets.map(({ node }) => (
|
||||
<SnippetCard
|
||||
key={`snippet_${node.id}`}
|
||||
short
|
||||
archived
|
||||
snippetData={{
|
||||
title: node.frontmatter.title,
|
||||
html: node.html,
|
||||
tags: node.frontmatter.tags.split(',').map(v => v.trim()),
|
||||
id: node.fields.slug.slice(1),
|
||||
title: node.title,
|
||||
html: node.html.text,
|
||||
tags: node.tags.all,
|
||||
id: node.id
|
||||
}}
|
||||
isDarkMode={props.isDarkMode}
|
||||
/>
|
||||
@ -55,25 +55,19 @@ export default connect(
|
||||
)(ArchivePage);
|
||||
|
||||
export const archivePageQuery = graphql`
|
||||
query ArchivePage {
|
||||
allMarkdownRemark(
|
||||
limit: 1000
|
||||
sort: { fields: [frontmatter___title], order: ASC }
|
||||
filter: { fileAbsolutePath: { regex: "/snippets_archive/" }, frontmatter: {title: { ne: "" } } }
|
||||
) {
|
||||
totalCount
|
||||
query archiveListing {
|
||||
allSnippet(filter: {archived: {eq: true}}) {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
html {
|
||||
text
|
||||
}
|
||||
tags {
|
||||
all
|
||||
primary
|
||||
}
|
||||
id
|
||||
html
|
||||
rawMarkdownBody
|
||||
fields {
|
||||
slug
|
||||
}
|
||||
frontmatter {
|
||||
title
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,14 +13,7 @@ import SimpleCard from '../components/SimpleCard';
|
||||
// Home page (splash and search)
|
||||
// ===================================================
|
||||
const IndexPage = props => {
|
||||
const snippets = props.data.snippetDataJson.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 snippets = props.data.allSnippet.edges;
|
||||
|
||||
const [searchQuery, setSearchQuery] = React.useState(props.searchQuery);
|
||||
const [searchResults, setSearchResults] = React.useState(snippets);
|
||||
@ -31,9 +24,9 @@ const IndexPage = props => {
|
||||
let results = snippets;
|
||||
if (q.trim().length)
|
||||
results = snippets.filter(
|
||||
v =>
|
||||
v.tags.filter(t => t.indexOf(q) !== -1).length ||
|
||||
v.title.toLowerCase().indexOf(q) !== -1,
|
||||
({node}) =>
|
||||
node.tags.all.filter(t => t.indexOf(q) !== -1).length ||
|
||||
node.title.toLowerCase().indexOf(q) !== -1,
|
||||
);
|
||||
setSearchResults(results);
|
||||
}, [searchQuery]);
|
||||
@ -80,11 +73,16 @@ const IndexPage = props => {
|
||||
Click on a snippet card to view the snippet.
|
||||
</p>
|
||||
<h2 className='page-sub-title'>Search results</h2>
|
||||
{searchResults.map(snippet => (
|
||||
{searchResults.map(({node}) => (
|
||||
<SnippetCard
|
||||
short
|
||||
key={`snippet_${snippet.id}`}
|
||||
snippetData={snippet}
|
||||
key={`snippet_${node.id}`}
|
||||
snippetData={{
|
||||
title: node.title,
|
||||
html: node.html.text,
|
||||
tags: node.tags.all,
|
||||
id: node.id
|
||||
}}
|
||||
isDarkMode={props.isDarkMode}
|
||||
/>
|
||||
))}
|
||||
@ -139,22 +137,17 @@ export const indexPageQuery = graphql`
|
||||
}
|
||||
}
|
||||
}
|
||||
snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) {
|
||||
data {
|
||||
id
|
||||
title
|
||||
attributes {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
allMarkdownRemark {
|
||||
allSnippet {
|
||||
edges {
|
||||
node {
|
||||
html
|
||||
frontmatter {
|
||||
title
|
||||
title
|
||||
html {
|
||||
text
|
||||
}
|
||||
tags {
|
||||
all
|
||||
}
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,28 +15,15 @@ import SimpleCard from '../components/SimpleCard';
|
||||
// Snippet list page
|
||||
// ===================================================
|
||||
const ListPage = props => {
|
||||
const snippets = props.data.snippetDataJson.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 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 snippets = props.data.allSnippet.edges;
|
||||
const archivedSnippets = props.data.allArchivedSnippet.edges;
|
||||
|
||||
const tags = [...new Set(
|
||||
snippets.map(snippet => (snippet.node.tags || { primary: null }).primary)
|
||||
)]
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
const staticPages = [
|
||||
{
|
||||
url: 'beginner',
|
||||
@ -79,12 +66,17 @@ const ListPage = props => {
|
||||
</Link>
|
||||
</h3>
|
||||
{snippets
|
||||
.filter(snippet => snippet.tags[0] === tag)
|
||||
.map(snippet => (
|
||||
.filter(({node}) => node.tags.primary === tag)
|
||||
.map(({node}) => (
|
||||
<SnippetCard
|
||||
key={`snippet_${snippet.id}`}
|
||||
key={`snippet_${node.id}`}
|
||||
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
|
||||
</Link></h3>
|
||||
{archivedSnippets
|
||||
.map(snippet => (
|
||||
.map(({node}) => (
|
||||
<SnippetCard
|
||||
key={`a_snippet_${snippet.id}`}
|
||||
key={`a_snippet_${node.id}`}
|
||||
short
|
||||
archived
|
||||
snippetData={snippet}
|
||||
snippetData={{
|
||||
title: node.title,
|
||||
html: node.html.text,
|
||||
tags: node.tags.all,
|
||||
id: node.id
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<br/>
|
||||
@ -131,31 +128,32 @@ export default connect(
|
||||
|
||||
export const listPageQuery = graphql`
|
||||
query snippetListing {
|
||||
snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) {
|
||||
data {
|
||||
id
|
||||
title
|
||||
attributes {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
snippetsArchiveDataJson : snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets_archive"} }) {
|
||||
data {
|
||||
id
|
||||
title
|
||||
attributes {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
allMarkdownRemark {
|
||||
allSnippet(filter: {archived: {eq: false}}) {
|
||||
edges {
|
||||
node {
|
||||
html
|
||||
frontmatter {
|
||||
title
|
||||
title
|
||||
html {
|
||||
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
|
||||
// ===================================================
|
||||
const SearchPage = props => {
|
||||
const snippets = props.data.snippetDataJson.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 snippets = props.data.allSnippet.edges;
|
||||
|
||||
const [searchQuery, setSearchQuery] = React.useState(props.searchQuery);
|
||||
const [searchResults, setSearchResults] = React.useState(snippets);
|
||||
@ -30,9 +23,9 @@ const SearchPage = props => {
|
||||
let results = snippets;
|
||||
if (q.trim().length)
|
||||
results = snippets.filter(
|
||||
v =>
|
||||
v.tags.filter(t => t.indexOf(q) !== -1).length ||
|
||||
v.title.toLowerCase().indexOf(q) !== -1,
|
||||
({ node }) =>
|
||||
node.tags.all.filter(t => t.indexOf(q) !== -1).length ||
|
||||
node.title.toLowerCase().indexOf(q) !== -1,
|
||||
);
|
||||
setSearchResults(results);
|
||||
}, [searchQuery]);
|
||||
@ -75,11 +68,16 @@ const SearchPage = props => {
|
||||
) : (
|
||||
<>
|
||||
<h2 className='page-sub-title'>Search results</h2>
|
||||
{searchResults.map(snippet => (
|
||||
{searchResults.map(({node}) => (
|
||||
<SnippetCard
|
||||
key={`snippet_${snippet.id}`}
|
||||
key={`snippet_${node.id}`}
|
||||
short
|
||||
snippetData={snippet}
|
||||
snippetData={{
|
||||
title: node.title,
|
||||
html: node.html.text,
|
||||
tags: node.tags.all,
|
||||
id: node.id
|
||||
}}
|
||||
isDarkMode={props.isDarkMode}
|
||||
/>
|
||||
))}
|
||||
@ -102,22 +100,17 @@ export default connect(
|
||||
|
||||
export const searchPageQuery = graphql`
|
||||
query searchSnippetList {
|
||||
snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) {
|
||||
data {
|
||||
id
|
||||
title
|
||||
attributes {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
allMarkdownRemark {
|
||||
allSnippet {
|
||||
edges {
|
||||
node {
|
||||
html
|
||||
frontmatter {
|
||||
title
|
||||
title
|
||||
html {
|
||||
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
|
||||
// ===================================================
|
||||
const SnippetPage = props => {
|
||||
const post = props.data.markdownRemark;
|
||||
const postData = props.data.snippetDataJson.data.find(
|
||||
v => v.title === post.frontmatter.title,
|
||||
);
|
||||
const snippet = props.pageContext.snippet;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title={post.frontmatter.title} description={post.excerpt} />
|
||||
<Meta title={snippet.title} description={snippet.text.short} />
|
||||
<Shell>
|
||||
<Link
|
||||
className='link-back'
|
||||
@ -29,10 +26,13 @@ const SnippetPage = props => {
|
||||
</Link>
|
||||
<SnippetCard
|
||||
snippetData={{
|
||||
title: postData.title,
|
||||
html: post.html,
|
||||
code: postData.attributes.codeBlocks.code,
|
||||
tags: postData.attributes.tags,
|
||||
title: snippet.title,
|
||||
html: snippet.html.full,
|
||||
codeHtml: snippet.html.code,
|
||||
exampleHtml: snippet.html.example,
|
||||
textHtml: snippet.html.text,
|
||||
code: snippet.code.src,
|
||||
tags: snippet.tags.all,
|
||||
}}
|
||||
isDarkMode={props.isDarkMode}
|
||||
/>
|
||||
@ -50,54 +50,3 @@ export default connect(
|
||||
}),
|
||||
null,
|
||||
)(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
|
||||
// ===================================================
|
||||
const TagRoute = props => {
|
||||
const posts = props.data.allMarkdownRemark.edges;
|
||||
const tag = props.pageContext.tag;
|
||||
const snippets = props.pageContext.snippets;
|
||||
|
||||
React.useEffect(() => {
|
||||
props.dispatch(pushNewPage(capitalize(tag), props.path));
|
||||
@ -26,16 +26,16 @@ const TagRoute = props => {
|
||||
<Shell>
|
||||
<h2 className='page-title'>{capitalize(tag)}</h2>
|
||||
<p className='light-sub'>Click on a snippet card to view the snippet.</p>
|
||||
{posts &&
|
||||
posts.map(({ node }) => (
|
||||
{snippets &&
|
||||
snippets.map(snippet => (
|
||||
<SnippetCard
|
||||
key={`snippet_${node.id}`}
|
||||
key={`snippet_${snippet.id}`}
|
||||
short
|
||||
snippetData={{
|
||||
title: node.frontmatter.title,
|
||||
html: node.html,
|
||||
tags: node.frontmatter.tags.split(',').map(v => v.trim()),
|
||||
id: node.fields.slug.slice(1),
|
||||
title: snippet.title,
|
||||
html: snippet.html,
|
||||
tags: snippet.tags,
|
||||
id: snippet.id,
|
||||
}}
|
||||
isDarkMode={props.isDarkMode}
|
||||
/>
|
||||
@ -54,28 +54,3 @@ export default connect(
|
||||
}),
|
||||
null,
|
||||
)(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