Skip to content
This repository has been archived by the owner on Sep 22, 2023. It is now read-only.

Integrate blog #33

Merged
merged 38 commits into from
Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
59f9a73
Add addTrailingSlashToSlug utility
Chalarangelo Jan 8, 2020
ab59d37
Update Anchor to add trailing slash if needed
Chalarangelo Jan 8, 2020
4cf2d2e
Safeguard Tag atom for empty tags
Chalarangelo Jan 14, 2020
4fc8dfd
Remove capitalization logic from Tag atom
Chalarangelo Jan 18, 2020
78d3da1
Update EXPERTISE_LEVELS
Chalarangelo Jan 18, 2020
37c0edd
Introduce tag transformer
Chalarangelo Jan 18, 2020
074db32
Add custom transformation logic for tags
Chalarangelo Jan 18, 2020
9cba367
Update reducers to use the tag transformer
Chalarangelo Jan 18, 2020
3f4872e
Use tag transformer in snippet context parser
Chalarangelo Jan 18, 2020
cf63fd2
Use tag transformer in snippet index transformation
Chalarangelo Jan 18, 2020
7b9b251
Fix failing tests
Chalarangelo Jan 18, 2020
18daf64
Install gatsby-plugin-catch-links
Chalarangelo Jan 21, 2020
52e0144
Update expertise scores when ranking
Chalarangelo Jan 20, 2020
1c469fe
Configure dynamic image imports
Chalarangelo Jan 10, 2020
2e52f20
Extract full width card section logic to mixin
Chalarangelo Jan 10, 2020
7309885
Allow social-only CTAs
Chalarangelo Jan 11, 2020
7bc2cc3
Pass source node to resolver
Chalarangelo Jan 10, 2020
2c7a337
Add 30blog content source
Chalarangelo Dec 23, 2019
1c42e1f
Add resolver and reducer for blog
Chalarangelo Dec 23, 2019
23c3a85
Handle blog attributes in GraphQL
Chalarangelo Dec 23, 2019
0f40e65
Add blog-specific query to GraphQL
Chalarangelo Dec 30, 2019
c3d8b48
Add images query to GraphQL
Chalarangelo Jan 10, 2020
b1ab0ee
Query images from GraphQL
Chalarangelo Jan 10, 2020
64dfd40
Parse image context and custom context for blog
Chalarangelo Jan 10, 2020
db91434
Update type definitions for blog
Chalarangelo Jan 27, 2020
1cfb7f7
Update page creation for blog pages
Chalarangelo Dec 30, 2019
2bf2e25
Handle blog page metadata in SnippetPage
Chalarangelo Dec 30, 2019
aadbd8a
Parse blog property in listing metas
Chalarangelo Jan 14, 2020
03eb033
Pass all snippets to snippet page creation
Chalarangelo Jan 20, 2020
d442891
Add logic for language in tag transformer
Chalarangelo Jan 20, 2020
c929d47
Handle language for blog
Chalarangelo Jan 20, 2020
6a7fffc
Enable blog snippets in snippet listings
Chalarangelo Jan 20, 2020
2ab35db
Handle blog snippets in ranking engine
Chalarangelo Jan 20, 2020
90f1793
Handle blogs in recommendation engine
Chalarangelo Jan 20, 2020
3ba9828
Customize SnippetList intro message
Chalarangelo Jan 14, 2020
f55df8b
Add context to PreviewCard molecule
Chalarangelo Jan 14, 2020
7b182bc
Add custom snippet card type for blog
Chalarangelo Jan 18, 2020
81df87f
Update specs, clear warnings
Chalarangelo Jan 27, 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "content/sources/30react"]
path = content/sources/30react
url = https://github.com/30-seconds/30-seconds-of-react
[submodule "content/sources/30blog"]
path = content/sources/30blog
url = https://github.com/30-seconds/30-seconds-blog
6 changes: 3 additions & 3 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ module.exports = {
],
// Snippet ranking engine parameters
rankingEngine: {
tagScorelimit: 12,
tagScoreLimit: 12,
expertiseScoreLimit: 8,
timeScoreLimitMultiplier: 0.6,
beginnerExpertiseScore: 6,
beginnerExpertiseScore: 3,
intermediateExpertiseScore: 8,
advancedExpertiseScore: 2,
advancedExpertiseScore: 5,
firstSeenScorePercentage: 0.47,
lastUpdateScorePercentage: 0.36,
updateCountScorePercentage: 0.17,
Expand Down
36 changes: 36 additions & 0 deletions content/configs/30blog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export default {
name: '30 seconds Blog',
dirName: '30blog',
repoUrl: 'https://github.com/30-seconds/30-seconds-blog',
snippetPath: 'blog_posts',
requirables: [
'blog_data/snippets.json',
// 'blog_data/blog_authors.json',
],
slug: 'blog',
reducer: 'blogReducer',
resolver: 'blogResolver',
isBlog: true,
theme: {
backColor: '#1f253d',
foreColor: '#edf0fc',
},
biasPenaltyMultiplier: 1.01,
images: {
name: 'blog_images',
path: 'blog_images',
},
tagScores: {
'javascript': 5,
'react': 4,
'css': 4,
'array': 4,
'list': 4,
'object': 4,
'python': 3,
'webdev': 3,
'devtools': 2,
'csharp': 2,
'php': 1,
},
};
48 changes: 48 additions & 0 deletions content/reducers/blogReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
rankingEngine as rankSnippet,
searchIndexingEngine as tokenizeSnippet
} from 'engines';
import { convertToSeoSlug, uniqueElements } from 'functions/utils';
import { determineExpertiseFromTags } from 'functions/transformers';
// TODO: Consider parsing this via a new parser or similar
// The argument against is that it's a single case and might not extend to other repos in the future
import authors from '../sources/30blog/blog_data/blog_authors';

export default (id, snippetNode, markdownNode) => {
const shortSliceIndex = snippetNode.attributes.text.indexOf('\n\n') <= 180 ? snippetNode.attributes.text.indexOf('\n\n') : snippetNode.attributes.text.indexOf(' ', 160);
return {
id,
tags: {
all: snippetNode.attributes.tags,
primary: snippetNode.attributes.tags[0],
},
blogType: snippetNode.type,
cover: snippetNode.attributes.cover,
authors: snippetNode.attributes.authors.map(a => authors[a]),
blog: true,
expertise: '',
title: snippetNode.title,
code: { },
slug: `/${snippetNode.slugPrefix}${convertToSeoSlug(markdownNode.fields.slug)}`,
url: `${snippetNode.repoUrlPrefix}${markdownNode.fields.slug.slice(0, -1)}.md`,
path: markdownNode.fileAbsolutePath,
text: {
full: snippetNode.attributes.text,
short: snippetNode.attributes.excerpt && snippetNode.attributes.excerpt.trim().length !== 0
? snippetNode.attributes.excerpt
: `${ snippetNode.attributes.text.slice(0, shortSliceIndex)}...`,
},
archived: snippetNode.archived,
language: {},
ranking: rankSnippet(snippetNode, { timeSensitive: true }),
recommendationRanking: snippetNode.recommendationRanking,
firstSeen: new Date(+`${snippetNode.meta.firstSeen}000`),
lastUpdated: new Date(+`${snippetNode.meta.lastUpdated}000`),
searchTokens: uniqueElements([
...snippetNode.attributes.tags.filter(tag => tag !== 'beginner' && tag !== 'intermediate' && tag !== 'advanced'),
...tokenizeSnippet(
`${snippetNode.attributes.excerpt} ${snippetNode.title}`
),
]).join(' ').toLowerCase(),
};
};
3 changes: 2 additions & 1 deletion content/reducers/cssReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
rankingEngine as rankSnippet,
searchIndexingEngine as tokenizeSnippet
} from 'engines';
import { determineExpertiseFromTags, convertToSeoSlug, uniqueElements } from 'functions/utils';
import { convertToSeoSlug, uniqueElements } from 'functions/utils';
import { determineExpertiseFromTags } from 'functions/transformers';

export default (id, snippetNode, markdownNode) => {
return {
Expand Down
3 changes: 2 additions & 1 deletion content/reducers/es6Reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
rankingEngine as rankSnippet,
searchIndexingEngine as tokenizeSnippet
} from 'engines';
import { determineExpertiseFromTags, convertToSeoSlug, uniqueElements } from 'functions/utils';
import { convertToSeoSlug, uniqueElements } from 'functions/utils';
import { determineExpertiseFromTags } from 'functions/transformers';

export default (id, snippetNode, markdownNode) => {
return {
Expand Down
3 changes: 2 additions & 1 deletion content/reducers/jsxReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
rankingEngine as rankSnippet,
searchIndexingEngine as tokenizeSnippet
} from 'engines';
import { determineExpertiseFromTags, convertToSeoSlug, uniqueElements } from 'functions/utils';
import { convertToSeoSlug, uniqueElements } from 'functions/utils';
import { determineExpertiseFromTags } from 'functions/transformers';

export default (id, snippetNode, markdownNode) => {
return {
Expand Down
3 changes: 2 additions & 1 deletion content/reducers/stdReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
rankingEngine as rankSnippet,
searchIndexingEngine as tokenizeSnippet
} from 'engines';
import { determineExpertiseFromTags, convertToSeoSlug, uniqueElements } from 'functions/utils';
import { convertToSeoSlug, uniqueElements } from 'functions/utils';
import { determineExpertiseFromTags } from 'functions/transformers';

export default (id, snippetNode, markdownNode) => {
return {
Expand Down
64 changes: 64 additions & 0 deletions content/resolvers/blogResolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/** Gets the short description for a blog post */
const getShortDescription = str => {
return `<p>${str}</p>`;
};

/** Get the textual content for a blog post */
const getTextualContent = (str, blogType) => {
let _str = str;
const transformers = [
// Inject class into blog lists' <ol> elements
{
blogType: 'blog.list',
matcher: /<ol>/g,
replacer: '<ol class="blog-list">',
},
// Inject paragraphs and class into blog lists' <li> elements
{
blogType: 'blog.list',
matcher: /<li>(.+?)\n(.+?)<\/li>/g,
replacer: '<li class="blog-list-item"><p>$1</p><p>$2</p></li>',
},
// Add 'rel' and 'target' to external links
{
blogType: 'any',
matcher: /(href="https?:\/\/)/g,
replacer: 'target="_blank" rel="nofollow noopener noreferrer" $1',
},
// Convert blog post code to the appropriate elements
{
blogType: 'any',
matcher: /<pre class="language-[^"]+"><code class="language-([^"]+)">([\s\S]*?)<\/code><\/pre>/g,
replacer: '<pre class="blog-code language-$1">$2</pre>',
},
// Convert blog blockquotes to the appropriate elements
{
blogType: 'any',
matcher: /<blockquote>\s*\n*\s*<p>([\s\S]*?)<\/p>\s*\n*\s<\/blockquote>/g,
replacer: '<blockquote class="blog-quote">$1</blockquote>',
},
// Convert image credit to the appropriate element
{
blogType: 'any',
matcher: /<p>\s*\n*\s*<strong>Image credit:<\/strong>([\s\S]*?)<\/p>/g,
replacer: '<p class="blog-image-credit">Image credit: $1</p>',
},
];

transformers.forEach(t => {
if (t.blogType === 'any' || t.blogType === blogType)
_str = _str.replace(t.matcher, t.replacer);
});
return _str;
};

export default (str, source) => {
const description = getShortDescription(source.text.short);
const fullDescription = getTextualContent(str, source.blogType);
return {
description,
fullDescription,
code: '',
example: '',
};
};
1 change: 1 addition & 0 deletions content/sources/30blog
Submodule 30blog added at 2d0aa8
1 change: 1 addition & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module.exports = {
icon: `assets/30s-icon.png`, // This path is relative to the root of the site.
},
},
`gatsby-plugin-catch-links`,
`gatsby-plugin-offline`,
`gatsby-plugin-react-helmet`,
`gatsby-plugin-netlify`,
Expand Down
3 changes: 2 additions & 1 deletion gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
const {
createPagesQuery,
getLogoSrc,
getImages,
getSearchIndex,
} = require(`./src/queries`);
const {
Expand All @@ -33,7 +34,7 @@ console.log(`${green('success')} parse resolvers`);
const templates = parseTemplates(config.templates, config.templatesPath);
console.log(`${green('success')} parse templates`);

const pagesQuery = parseQueries(getLogoSrc, createPagesQuery, getSearchIndex);
const pagesQuery = parseQueries(getLogoSrc, createPagesQuery, getSearchIndex, getImages);
console.log(`${green('success')} parse queries`);

exports.createPages = createPages(pagesQuery, templates, requirables);
Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"front-matter": "^3.0.2",
"fs-extra": "^8.1.0",
"gatsby": "^2.12.0",
"gatsby-plugin-catch-links": "^2.1.24",
"gatsby-plugin-manifest": "^2.2.3",
"gatsby-plugin-netlify": "^2.1.3",
"gatsby-plugin-offline": "^2.2.4",
Expand Down Expand Up @@ -127,7 +128,9 @@
"setupFiles": [
"./.jest/loadershim.js"
],
"testMatch": [ "**/?(*.)+(test).[jt]s?(x)" ]
"testMatch": [
"**/?(*.)+(test).[jt]s?(x)"
]
},
"browserslist": [
"defaults and last 4 versions and not dead and not ie>0 and not ie_mob>0 and not op_mini all and not baidu>0 and not kaios>0 and not and_uc>0 and not and_qq>0 and not <0.2%"
Expand Down
4 changes: 2 additions & 2 deletions src/atoms/anchor/regularAnchor/anchor.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ describe('<Anchor />', () => {
anchor = wrapper.find('a');
});

it('should link the appropriate internal URl', () => {
expect(anchor.prop('href')).toBe(`/${internalLink.url}`);
it('should link the appropriate internal URL', () => {
expect(anchor.prop('href')).toBe(`/${internalLink.url}/`);
});
});

Expand Down
3 changes: 2 additions & 1 deletion src/atoms/anchor/regularAnchor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { Link } from 'gatsby';
import PropTypes from 'prop-types';
import { Link as LinkPropType } from 'typedefs';
import { addTrailingSlashToSlug } from 'functions/utils';

const Anchor = ({
children,
Expand All @@ -11,7 +12,7 @@ const Anchor = ({
return link.internal ?
(
<Link
to={ link.url }
to={ addTrailingSlashToSlug(link.url) }
rel={ link.rel }
target={ link.target }
{ ...rest }
Expand Down
1 change: 1 addition & 0 deletions src/atoms/card/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@

li {
line-height: 2;
margin: 0.75rem 0 0.5rem;
}
}
}
6 changes: 6 additions & 0 deletions src/atoms/card/_mixins.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@mixin card-full-width-section {
position: relative;
margin-left: -1rem;
margin-right: -1rem;
margin: 1.375rem -1rem 0;
}
2 changes: 1 addition & 1 deletion src/atoms/expertise/expertise.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('<Expertise />', () => {
});

it('should get the appropriate class from expertise level', () => {
expect(wrapper).toContainMatchingElement(`.expertise.${level}`);
expect(wrapper).toContainMatchingElement(`.expertise.${level.toLowerCase()}`);
});

});
Expand Down
4 changes: 2 additions & 2 deletions src/atoms/expertise/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import _ from 'lang';
const _l = _('en');

const Expertise = ({
level = 'intermediate',
level = 'Intermediate',
}) => (
<span className={ trimWhiteSpace`expertise ${level}` }>
<span className={ trimWhiteSpace`expertise ${level.toLowerCase()}` }>
{ _l`Expertise${level}` }
</span>
);
Expand Down
17 changes: 10 additions & 7 deletions src/atoms/tag/index.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { capitalize } from 'functions/utils';

const Tag = ({
name,
}) => (
<span className="tag">
{ capitalize(name) }
</span>
);
}) =>
name ?
(
<span className="tag">
{ name }
</span>
)
: null
;

Tag.propTypes = {
/** Tag string literal */
name: PropTypes.string.isRequired,
name: PropTypes.string,
};

export default Tag;
Loading