├── .dockerignore ├── __mocks__ ├── filemock.js ├── gatsby.js └── gatsby-link.js ├── src ├── gatsby-plugin-meta-redirect │ ├── index.js │ ├── README.md │ ├── getMetaRedirect.js │ ├── package.json │ ├── LICENSE │ └── gatsby-node.js ├── css-legacy-components │ ├── css │ │ ├── future │ │ │ ├── side-ad.css │ │ │ ├── headline-ad.css │ │ │ ├── page-actions.css │ │ │ ├── announcements-list.css │ │ │ ├── search-footer-container.css │ │ │ ├── notice-box.css │ │ │ ├── hint-mark.css │ │ │ ├── missing-message.css │ │ │ └── announcements-item.css │ │ ├── push-button.css │ │ ├── body-area.css │ │ ├── codefund-sponsor.css │ │ ├── comments-section.css │ │ ├── main-heading.css │ │ ├── related-posts-section.css │ │ └── top-sheet.css │ └── index.tsx ├── types │ ├── elasticlunr.d.ts │ ├── jsx-to-string.d.ts │ ├── css.d.ts │ ├── react-frame-component.d.ts │ ├── group-by.d.ts │ └── types.ts ├── helpers │ ├── noop.js │ ├── mdToAst.tsx │ ├── index.ts │ └── site_page.tsx ├── web-icons │ ├── package.json │ ├── ionicons │ │ ├── _ionicons_svg_md-arrow-back.svg │ │ ├── _ionicons_svg_logo-facebook.svg │ │ ├── _ionicons_svg_logo-twitter.svg │ │ └── _ionicons_svg_logo-github.svg │ ├── index.d.ts │ ├── icons │ │ ├── home-solid.svg │ │ ├── home-line.svg │ │ ├── search-line.svg │ │ ├── talk-bubbles-solid.svg │ │ └── talk-bubbles-line.svg │ └── index.js ├── web-search │ ├── index.tsx │ ├── comps │ │ ├── SearchModal.module.css │ │ ├── SearchItem.module.css │ │ ├── SearchItem.tsx │ │ ├── SearchBox.tsx │ │ ├── SearchModal.tsx │ │ ├── SearchProvider.tsx │ │ └── SearchBox.module.css │ ├── LiveSearchInput.module.css │ └── LiveSearchInput.tsx ├── gatsby-node │ ├── README.md │ ├── onCreateWebpackConfig.tsx │ ├── onCreateNode.tsx │ ├── helpers.tsx │ └── createPages.tsx ├── rehype-wrapify │ ├── lib │ │ └── helpers │ │ │ ├── h_jsx.js │ │ │ ├── hast_to_react.js │ │ │ └── hast.js │ ├── package.json │ └── index.js ├── css-base │ ├── index.tsx │ ├── utils │ │ ├── has-container.css │ │ ├── heading-style.css │ │ ├── gutter-padding.css │ │ └── section-gutter.css │ ├── base │ │ └── base.css │ └── variables.css ├── uipad │ ├── index.jsx │ ├── Br.tsx │ ├── lib │ │ ├── padify.tsx │ │ ├── FrameWrapper.tsx │ │ ├── useIFrameSize.tsx │ │ └── HarvestHeadStyles.tsx │ ├── types.ts │ ├── Canvas.tsx │ ├── OptionsContext.tsx │ ├── Frame.tsx │ └── Group.tsx ├── web │ ├── components │ │ ├── FeaturedPagesLink.module.css │ │ ├── __tests__ │ │ │ └── PreFooter.test.tsx │ │ ├── PreFooter.module.css │ │ ├── PreFooter.tsx │ │ ├── MainHeading.tsx │ │ ├── FeaturedPageLink.tsx │ │ ├── HomeButton.tsx │ │ ├── BackButton.tsx │ │ ├── SocialList.module.css │ │ ├── SpanPushButton.tsx │ │ ├── HomeButton.module.css │ │ ├── SpanPushButton.module.css │ │ ├── IntroContent.tsx │ │ ├── PageLink.tsx │ │ ├── SearchFooter.tsx │ │ ├── AttributePeg.tsx │ │ ├── PageActions.module.css │ │ ├── SearchFooterSection.tsx │ │ ├── RelatedPostsCallout.tsx │ │ ├── RelatedPostsSection.tsx │ │ ├── BackButton.module.css │ │ ├── RelatedPostItem.tsx │ │ ├── PagesList.tsx │ │ ├── RelatedPostsArea.tsx │ │ ├── RelatedPostsCallout.module.css │ │ ├── SiteHeader.tsx │ │ ├── PageActions.tsx │ │ ├── PagesList.module.css │ │ ├── RelatedPostItem.module.css │ │ ├── RootPage.tsx │ │ ├── ExternalSearchLinks.tsx │ │ ├── RelatedPostsGroup.tsx │ │ ├── FeaturedPages.tsx │ │ ├── TopNav.tsx │ │ ├── MiniMarkdown.tsx │ │ ├── TopNav.module.css │ │ └── SocialList.tsx │ └── layouts │ │ └── index.tsx ├── css-markdown │ ├── sections │ │ ├── h2-section.css │ │ └── h3-section-list.css │ ├── css │ │ ├── a-em.css │ │ ├── ul.css │ │ ├── headings.css │ │ ├── prism.css │ │ ├── p.css │ │ ├── code.css │ │ └── table.css │ └── index.tsx ├── pages │ ├── ui │ │ ├── blank.tsx │ │ ├── widgets.tsx │ │ ├── markdown.tsx │ │ └── scenarios.tsx │ ├── 404.tsx │ └── index.tsx ├── web-comments │ ├── comps │ │ ├── CommentsSection.tsx │ │ ├── CommentsAreaSummary.tsx │ │ ├── CommentsAreaSummary.module.css │ │ ├── DisqusDiscussion.tsx │ │ └── DisqusScript.tsx │ ├── CommentsArea.module.css │ └── CommentsArea.tsx ├── gatsby-shell │ ├── Meta.tsx │ ├── comps │ │ ├── CommonHead.tsx │ │ └── SheetTemplateView.tsx │ ├── MetaTags.tsx │ └── SheetTemplate.tsx ├── web-post-content │ ├── lib │ │ ├── doPostTransform.tsx │ │ ├── transform.tsx │ │ ├── isotopify.tsx │ │ └── prism.test.tsx │ ├── Elements.tsx │ └── PostContent.tsx └── gatsby-hooks │ └── useSiteContent.ts ├── support ├── Dockerfile ├── register.js ├── jest.preprocess.js └── start.sh ├── .prettierrc ├── gatsby-node.js ├── netlify.toml ├── tests └── jest.setup.js ├── .gitignore ├── .travis.yml ├── docker-compose.yml ├── .github ├── stale.yml └── workflows │ └── nodejs.yml ├── gatsby-node.ts ├── tslint.json ├── tsconfig.json ├── jest.config.js ├── static └── admin │ └── config.yml ├── sheets ├── devhints.md ├── emacs.md ├── sketch.md ├── devhints │ ├── tables.md │ └── classes.md └── ranger.md ├── docs ├── writing_cheatsheets.md └── architecture.md ├── LICENSE.md ├── postcss.config.js ├── Makefile ├── README.md ├── gatsby-config.js ├── siteMetadata.js └── CONTRIBUTING.md /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /__mocks__/filemock.js: -------------------------------------------------------------------------------- 1 | module.exports = '' 2 | -------------------------------------------------------------------------------- /src/gatsby-plugin-meta-redirect/index.js: -------------------------------------------------------------------------------- 1 | // noop 2 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/side-ad.css: -------------------------------------------------------------------------------- 1 | /* TODO */ 2 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/push-button.css: -------------------------------------------------------------------------------- 1 | /* TODO */ 2 | -------------------------------------------------------------------------------- /src/types/elasticlunr.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'elasticlunr' 2 | -------------------------------------------------------------------------------- /src/types/jsx-to-string.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jsx-to-string' 2 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/headline-ad.css: -------------------------------------------------------------------------------- 1 | /* TODO */ 2 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/page-actions.css: -------------------------------------------------------------------------------- 1 | /* TODO */ 2 | -------------------------------------------------------------------------------- /src/types/css.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss' 2 | declare module '*.css' 3 | -------------------------------------------------------------------------------- /support/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | RUN mkdir -p /app 3 | WORKDIR /app 4 | -------------------------------------------------------------------------------- /src/types/react-frame-component.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-frame-component' 2 | -------------------------------------------------------------------------------- /src/helpers/noop.js: -------------------------------------------------------------------------------- 1 | /* Intentionally left blank. Keep this file as .js, please */ 2 | -------------------------------------------------------------------------------- /src/web-icons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devhints-icons", 3 | "version": "0.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "jsxSingleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | require('./support/register')() 2 | 3 | module.exports = require('./gatsby-node.ts') 4 | -------------------------------------------------------------------------------- /src/web-search/index.tsx: -------------------------------------------------------------------------------- 1 | // Main entry point 2 | export { default as LiveSearchInput } from './LiveSearchInput' 3 | -------------------------------------------------------------------------------- /src/gatsby-node/README.md: -------------------------------------------------------------------------------- 1 | # Gatsby support files 2 | 3 | These are files that are used by the `gatsby-*.js` config (eg, `gatsby-node.js`). 4 | -------------------------------------------------------------------------------- /__mocks__/gatsby.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | const gatsby = jest.requireActual('gatsby') 3 | module.exports = { ...gatsby, graphql: jest.fn(), Link: 'Link' } 4 | -------------------------------------------------------------------------------- /src/rehype-wrapify/lib/helpers/h_jsx.js: -------------------------------------------------------------------------------- 1 | import h from 'hastscript' 2 | 3 | // Shim for JSX 4 | export default (name, props, ...kids) => h(name, props, kids) 5 | -------------------------------------------------------------------------------- /src/css-base/index.tsx: -------------------------------------------------------------------------------- 1 | // 3rd-party CSS libraries 2 | import 'hint.css/hint.css' 3 | import 'sanitize.css/sanitize.css' 4 | 5 | // Base 6 | import './base/base.css' 7 | -------------------------------------------------------------------------------- /src/uipad/index.jsx: -------------------------------------------------------------------------------- 1 | import Canvas from './Canvas' 2 | import Frame from './Frame' 3 | import Group from './Group' 4 | import Br from './Br' 5 | export { Frame, Canvas, Group, Br } 6 | -------------------------------------------------------------------------------- /src/web/components/FeaturedPagesLink.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | text-decoration: none; 3 | display: inline-block; 4 | padding: 8px 12px; 5 | margin: 4px; 6 | border: solid 1px #8883; 7 | } 8 | -------------------------------------------------------------------------------- /__mocks__/gatsby-link.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const MockLink = props => 4 | React.createElement('a', { href: props.to, ...props }, props.children) 5 | 6 | export default MockLink 7 | -------------------------------------------------------------------------------- /support/register.js: -------------------------------------------------------------------------------- 1 | module.exports = () => 2 | require('@babel/register')({ 3 | presets: ['@babel/typescript', '@babel/env'], 4 | extensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs'] 5 | }) 6 | -------------------------------------------------------------------------------- /support/jest.preprocess.js: -------------------------------------------------------------------------------- 1 | const babelOptions = { 2 | presets: ['@babel/preset-typescript', 'babel-preset-gatsby'] 3 | } 4 | 5 | module.exports = require('babel-jest').createTransformer(babelOptions) 6 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = 'public' 3 | command = 'yarn run build' 4 | 5 | [build.environment] 6 | NODE_VERSION = '10.15.0' 7 | 8 | [context.deploy-preview.environment] 9 | DEPLOY_PREVIEW = '1' 10 | -------------------------------------------------------------------------------- /tests/jest.setup.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme' 2 | import Adapter from 'enzyme-adapter-react-16' 3 | 4 | Enzyme.configure({ adapter: new Adapter() }) 5 | 6 | global.___loader = { 7 | enqueue: jest.fn() 8 | } 9 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/body-area.css: -------------------------------------------------------------------------------- 1 | .body-area { 2 | @extend %gutter-padding; 3 | max-width: var(--area-width); 4 | margin: 0 auto; 5 | 6 | &.-slim { 7 | max-width: 740px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/codefund-sponsor.css: -------------------------------------------------------------------------------- 1 | .codefund-sponsor { 2 | & { 3 | min-height: 114px; 4 | } 5 | 6 | & .cf-wrapper { 7 | margin-left: auto; 8 | margin-right: auto; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/web-icons/ionicons/_ionicons_svg_md-arrow-back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/css-base/utils/has-container.css: -------------------------------------------------------------------------------- 1 | %container { 2 | & { 3 | @extend %gutter-padding-left; 4 | } 5 | & { 6 | @extend %gutter-padding-right; 7 | } 8 | max-width: var(--area-width); 9 | margin: 0 auto; 10 | } 11 | -------------------------------------------------------------------------------- /src/web/components/__tests__/PreFooter.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import { mount } from 'enzyme' 3 | import React from 'react' 4 | import PreFooter from '../PreFooter' 5 | 6 | it('works', () => { 7 | mount() 8 | }) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .cache/ 5 | # Build directory 6 | public/ 7 | .DS_Store 8 | yarn-error.log 9 | /coverage 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | script: 5 | - yarn run tsc 6 | - yarn run lint 7 | - yarn run prettier:check 8 | - yarn run test 9 | - yarn run build 10 | cache: 11 | yarn: true 12 | directories: 13 | - public 14 | -------------------------------------------------------------------------------- /src/types/group-by.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'group-by' { 2 | interface Dictionary { 3 | [key: string]: T[] 4 | } 5 | 6 | export default function groupBy( 7 | collection: T[], 8 | groupFn: (it: T) => any 9 | ): Dictionary 10 | } 11 | -------------------------------------------------------------------------------- /src/css-legacy-components/index.tsx: -------------------------------------------------------------------------------- 1 | import './css/body-area.css' 2 | import './css/codefund-sponsor.css' 3 | import './css/comments-section.css' 4 | import './css/main-heading.css' 5 | import './css/push-button.css' 6 | import './css/related-posts-section.css' 7 | import './css/top-sheet.css' 8 | -------------------------------------------------------------------------------- /src/uipad/Br.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Br = () => { 4 | return ( 5 |
6 | 11 |
12 | ) 13 | } 14 | 15 | export default Br 16 | -------------------------------------------------------------------------------- /src/rehype-wrapify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rehype-wrapify", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "jest": "^24.0.0" 6 | }, 7 | "license": "MIT", 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "private": true 13 | } 14 | -------------------------------------------------------------------------------- /src/gatsby-plugin-meta-redirect/README.md: -------------------------------------------------------------------------------- 1 | Forked from https://github.com/getchalk/gatsby-plugin-meta-redirect - this plugin has been updated here so: 2 | 3 | - Add support for gatsby v2 by not needing `regeneratorRuntime`. 4 | - Don't add trailing slash to redirects (eg, `/react` instead of `/react/`). 5 | -------------------------------------------------------------------------------- /src/helpers/mdToAst.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import remark from 'remark' 3 | 4 | // @ts-ignore 5 | import toHAST from 'mdast-util-to-hast' 6 | 7 | const mdToAst = (input: string): any => { 8 | const mdAst = remark.parse(input) 9 | return toHAST(mdAst) 10 | } 11 | 12 | export default mdToAst 13 | -------------------------------------------------------------------------------- /src/web-icons/index.d.ts: -------------------------------------------------------------------------------- 1 | export const homeLine: string 2 | export const talkBubblesLine: string 3 | export const talkBubblesSolid: string 4 | export const searchLine: string 5 | export const facebook: string 6 | export const twitter: string 7 | export const github: string 8 | export const mdArrowBack: string 9 | -------------------------------------------------------------------------------- /src/css-markdown/sections/h2-section.css: -------------------------------------------------------------------------------- 1 | /* 2 | * h2 section 3 | */ 4 | 5 | /* Hide the first h2 heading */ 6 | .h2-section:first-child:not(.-no-hide) > h2 { 7 | display: none; 8 | } 9 | 10 | @media (min-width: 481px) { 11 | .h2-section + .h2-section { 12 | margin-top: 48px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/web-search/comps/SearchModal.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | bottom: 0; 6 | right: 0; 7 | padding: 32px; 8 | background: white; 9 | z-index: 1; 10 | } 11 | 12 | .list { 13 | margin: 0; 14 | padding: 0; 15 | list-style-type: none; 16 | } 17 | -------------------------------------------------------------------------------- /src/web/components/PreFooter.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | padding: 32px; 3 | padding-top: 24px; 4 | padding-bottom: 48px; 5 | text-align: center; 6 | } 7 | 8 | .icon::before { 9 | content: ''; 10 | /* TODO pre-footer icon -- @include ion-ios-flash(32px, $base-mute); */ 11 | opacity: 0.25; 12 | } 13 | -------------------------------------------------------------------------------- /src/css-markdown/css/a-em.css: -------------------------------------------------------------------------------- 1 | /* 2 | * For links with sources, eg, 3 | * [Foo](foo.com) _(foo.com)_ 4 | */ 5 | 6 | .MarkdownBody.MarkdownBody { 7 | & a + em { 8 | opacity: 0.5; 9 | } 10 | } 11 | 12 | .MarkdownBody { 13 | & a { 14 | padding-top: 3px; 15 | padding-bottom: 3px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/css-markdown/index.tsx: -------------------------------------------------------------------------------- 1 | import './css/a-em.css' 2 | import './css/code.css' 3 | import './css/headings.css' 4 | import './css/p.css' 5 | import './css/prism.css' 6 | import './css/table.css' 7 | import './css/ul.css' 8 | import './sections/h2-section.css' 9 | import './sections/h3-section-list.css' 10 | import './sections/h3-section.css' 11 | -------------------------------------------------------------------------------- /src/web/components/PreFooter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CSS from './PreFooter.module.css' 3 | 4 | /** 5 | * Pre-footer separator in the cheatsheets page 6 | */ 7 | 8 | export const PreFooter = () => ( 9 |
10 | 11 |
12 | ) 13 | 14 | export default PreFooter 15 | -------------------------------------------------------------------------------- /src/web-search/comps/SearchItem.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | display: block; 3 | margin: 0; 4 | padding: 0; 5 | list-style-type: none; 6 | } 7 | 8 | .link { 9 | text-decoration: none; 10 | } 11 | 12 | .path { 13 | font-weight: bold; 14 | } 15 | 16 | .title { 17 | margin-left: 8px; 18 | } 19 | 20 | .category { 21 | margin-left: 8px; 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/ui/blank.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Canvas, Frame } from '../../uipad' 3 | 4 | const MyCanvas = () => { 5 | return ( 6 | 7 | 8 | Hello, world! 9 | 10 | 11 | ) 12 | } 13 | 14 | export default MyCanvas 15 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/announcements-list.css: -------------------------------------------------------------------------------- 1 | .announcements-list { 2 | & { 3 | position: fixed; 4 | left: 0; 5 | bottom: 0; 6 | max-width: 420px; 7 | padding: 0; 8 | z-index: 10; 9 | } 10 | 11 | @media (min-width: 481px) { 12 | padding: 16px; 13 | } 14 | 15 | @media (min-width: 769px) { 16 | padding: 32px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/search-footer-container.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Section inside the search footer 3 | */ 4 | 5 | .search-footer-section { 6 | & { 7 | display: flex; 8 | } 9 | 10 | & > .search { 11 | flex: 0 1 640px; 12 | } 13 | 14 | & > .links { 15 | @extend %gutter-padding-left; 16 | flex: 0 1 auto; 17 | margin-left: auto; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/web-icons/ionicons/_ionicons_svg_logo-facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/rehype-wrapify/lib/helpers/hast_to_react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import RehypeReact from 'rehype-react' 3 | 4 | /* 5 | * We serialize the test markup as React elements, not hast. 6 | * Just for practicality... Jest has better test output for 7 | * React elements. 8 | */ 9 | 10 | const render = new RehypeReact({ 11 | createElement: React.createElement 12 | }).Compiler 13 | 14 | export default render 15 | -------------------------------------------------------------------------------- /src/web-comments/comps/CommentsSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export interface Props { 4 | thread: React.ReactNode 5 | } 6 | 7 | const CommentsSection = ({ thread }: Props) => { 8 | return ( 9 |
10 | {/* TODO comments-section CSS module */} 11 |
{thread}
12 |
13 | ) 14 | } 15 | 16 | export default CommentsSection 17 | -------------------------------------------------------------------------------- /src/web/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export interface Props { 4 | children: () => React.ReactNode 5 | } 6 | 7 | /** 8 | * Base layout: 9 | * Make this as simple as possible for in preparation for Gatsby v2 10 | * https://next.gatsbyjs.org/docs/migrating-from-v1-to-v2/ 11 | */ 12 | 13 | function TemplateWrapper({ children }: Props) { 14 | return children 15 | } 16 | 17 | export default TemplateWrapper 18 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/notice-box.css: -------------------------------------------------------------------------------- 1 | .notice-box { 2 | & { 3 | margin-bottom: 24px; 4 | color: var(--text-mute); 5 | } 6 | 7 | @media (max-width: 480px) { 8 | margin-bottom: 16px; 9 | } 10 | 11 | &::before { 12 | content: ''; 13 | /* TODO @include ion-md-information-circle(24px, $base-mute3); */ 14 | margin-right: 8px; 15 | } 16 | 17 | & > a { 18 | text-decoration: none; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/web/components/MainHeading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export interface Props { 4 | title: string 5 | suffix: string 6 | } 7 | 8 | /** 9 | * Main heading 10 | */ 11 | 12 | const MainHeading = ({ title, suffix }: Props) => ( 13 |
14 |

15 | {title} {suffix} 16 |

17 | 18 |
19 |
20 | ) 21 | 22 | export default MainHeading 23 | -------------------------------------------------------------------------------- /src/web/components/FeaturedPageLink.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import React from 'react' 3 | import CSS from './FeaturedPagesLink.module.css' 4 | 5 | interface Props { 6 | path: string 7 | title: string 8 | } 9 | 10 | const FeaturedPageLink = (props: Props) => { 11 | const { path, title } = props 12 | 13 | return ( 14 | 15 | {title} 16 | 17 | ) 18 | } 19 | 20 | export default FeaturedPageLink 21 | -------------------------------------------------------------------------------- /src/web/components/HomeButton.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import React from 'react' 3 | import { homeLine } from '../../web-icons' 4 | import CSS from './HomeButton.module.css' 5 | 6 | /** 7 | * Home button 8 | */ 9 | 10 | export const HomeButton = () => ( 11 | 12 | 13 | 14 | ) 15 | 16 | /* 17 | * Export 18 | */ 19 | 20 | export default HomeButton 21 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/comments-section.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Disqus 3 | */ 4 | 5 | .comments-section { 6 | & { 7 | display: flex; 8 | } 9 | 10 | /* Mobile: full width */ 11 | @media (max-width: 768px) { 12 | & > .comments { 13 | flex: 1 0 100%; 14 | width: 100%; 15 | } 16 | } 17 | 18 | /* Desktop: partial width */ 19 | @media (min-width: 769px) { 20 | & > .comments { 21 | flex: 0 1 66%; 22 | min-width: 300px; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/uipad/lib/padify.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert to a padding value. 3 | * 4 | * @example 5 | * padify(24) // => '24px' 6 | */ 7 | 8 | function padify( 9 | value: boolean | string | number | null | undefined, 10 | defaultValue: string = '16px' 11 | ): string { 12 | if (!value) return '0' 13 | if (typeof value === 'string') return value 14 | if (typeof value === 'number') return `${value}px` 15 | if (value) return defaultValue 16 | return '0' 17 | } 18 | 19 | export default padify 20 | -------------------------------------------------------------------------------- /src/css-base/utils/heading-style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * (Mixin) heading-style 3 | * For top-level headings 4 | */ 5 | 6 | %heading-style { 7 | & { 8 | margin: 0; 9 | padding: 0; 10 | margin-bottom: calc(16px + 8px); 11 | margin-top: 64px; 12 | position: relative; 13 | } 14 | 15 | @media (max-width: 768px) { 16 | margin-bottom: 8px; 17 | margin-top: 32px; 18 | } 19 | 20 | @media (max-width: 480px) { 21 | margin-bottom: 8px; 22 | margin-top: 32px; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gatsby-shell/Meta.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import useSiteContent from '../gatsby-hooks/useSiteContent' 3 | import MetaTags from './MetaTags' 4 | 5 | /** 6 | * Default meta tags 7 | */ 8 | 9 | export const HomeMeta = () => { 10 | const { 11 | home: { title, description } 12 | } = useSiteContent() 13 | 14 | const image = 'https://assets.devhints.io/previews/index.jpg?t=20191015093217' 15 | const ogType = 'website' 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /src/web/components/BackButton.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import BackIcon from '-!react-svg-loader!clarity-icons-svg/essential/arrow-outline.svg' 3 | import { Link } from 'gatsby' 4 | import React from 'react' 5 | import CSS from './BackButton.module.css' 6 | 7 | // TODO backbutton 8 | export const BackButton = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | export default BackButton 19 | -------------------------------------------------------------------------------- /src/gatsby-shell/comps/CommonHead.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Helmet from 'react-helmet' 3 | 4 | import '../../css-base' 5 | import '../../css-legacy-components' 6 | import '../../css-markdown' 7 | 8 | /** 9 | * Things that should be in all pages 10 | */ 11 | 12 | export const CommonHead = () => ( 13 | <> 14 | 15 | 19 | 20 | 21 | ) 22 | 23 | export default CommonHead 24 | -------------------------------------------------------------------------------- /support/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # This script will be ran when you do `docker-compose up`. 3 | # 4 | # This entrypoint saves you from the hassle of doing setup (installing npm 5 | # modules, etc) - it will be done for you. All you need to do is 6 | # 'docker-compose up' :) 7 | 8 | # Yarn install as needed 9 | if [ ! -x ./node_modules/.bin/gatsby ]; then 10 | yarn 11 | fi 12 | 13 | # Clear cache as needed 14 | if [ -d .cache ]; then 15 | rm -rf .cache/* 16 | fi 17 | 18 | # Yarn run 19 | yarn run gatsby develop --host 0.0.0.0 --port "${PORT:-19336}" 20 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/hint-mark.css: -------------------------------------------------------------------------------- 1 | .hint-mark { 2 | & { 3 | cursor: help; 4 | } 5 | 6 | & > i::before { 7 | content: '?'; 8 | font-size: 11px; 9 | font-weight: bold; 10 | font-style: normal; 11 | } 12 | 13 | & > i { 14 | display: inline-block; 15 | width: 16px; 16 | height: 16px; 17 | line-height: calc(16px - 4px); 18 | text-align: center; 19 | border-radius: 50%; 20 | background: color-mod(var(--brand-b3) alpha(30%)); 21 | color: var(--text-mute); 22 | margin: 0 0.4em; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/web-icons/icons/home-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/web-comments/CommentsArea.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | margin: 32px 0 16px 0; 3 | } 4 | 5 | .container { 6 | & { 7 | @extend %container; 8 | } 9 | max-width: var(--area-width); 10 | margin: 0 auto; 11 | } 12 | 13 | /* Horizontal line */ 14 | .container::before { 15 | content: ''; 16 | display: block; 17 | background: linear-gradient( 18 | to right, 19 | var(--dark-line-color) 50%, 20 | transparent 21 | ); 22 | height: 1px; 23 | } 24 | 25 | .details { 26 | margin-bottom: -16px; 27 | } 28 | 29 | .details[open] { 30 | margin-bottom: 0; 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | web: 5 | build: 6 | context: . 7 | dockerfile: support/Dockerfile 8 | volumes: 9 | - .:/app 10 | - node_modules:/app/node_modules 11 | - cache:/app/.cache 12 | - public:/app/public 13 | - yarn_cache:/usr/local/share/.cache/yarn/v4 14 | ports: 15 | - '19336:19336' 16 | environment: 17 | CHOKIDAR_USEPOLLING: 'true' 18 | PORT: '19336' 19 | command: 'sh ./support/start.sh' 20 | 21 | volumes: 22 | node_modules: 23 | cache: 24 | public: 25 | yarn_cache: 26 | -------------------------------------------------------------------------------- /src/web-icons/icons/home-line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/web-icons/icons/search-line.svg: -------------------------------------------------------------------------------- 1 | 2 | search-line 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/web/components/SocialList.module.css: -------------------------------------------------------------------------------- 1 | .root, 2 | .root li { 3 | margin: 0; 4 | padding: 0; 5 | list-style-type: none; 6 | } 7 | 8 | .item { 9 | display: inline; 10 | } 11 | 12 | .icon svg { 13 | width: 16px; 14 | height: 16px; 15 | } 16 | 17 | .link { 18 | display: inline-block; 19 | width: 32px; 20 | height: 32px; 21 | line-height: 32px; 22 | text-decoration: none; 23 | } 24 | 25 | .link, 26 | .link:visited { 27 | color: var(--text-mute); 28 | } 29 | 30 | .link:hover, 31 | .link:focus { 32 | color: var(--brand-a); 33 | } 34 | 35 | .text { 36 | display: none; 37 | } 38 | -------------------------------------------------------------------------------- /src/uipad/types.ts: -------------------------------------------------------------------------------- 1 | /** Inheritable options */ 2 | export interface FrameOptions { 3 | size: number | null 4 | width: number | null 5 | 6 | /** Grid size multiplier */ 7 | grid: number 8 | 9 | /** Padding inside frames */ 10 | pad: number | string | boolean 11 | 12 | /** Background of frames */ 13 | background: string 14 | 15 | /** Margin in between */ 16 | margin: number 17 | 18 | /** If true, wrap in an iframe (experimenal) */ 19 | iframe?: boolean 20 | } 21 | 22 | export interface FrameProps extends Partial { 23 | children: React.ReactNode 24 | title?: string 25 | } 26 | -------------------------------------------------------------------------------- /src/web/components/SpanPushButton.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import React from 'react' 3 | import CSS from './SpanPushButton.module.css' 4 | 5 | export interface Props { 6 | className?: string 7 | dark?: boolean 8 | children?: React.ReactNode 9 | } 10 | 11 | const SpanPushButton = (props: Props) => { 12 | const { className, children, dark } = props 13 | 14 | return ( 15 | 21 | {children} 22 | 23 | ) 24 | } 25 | 26 | export default SpanPushButton 27 | -------------------------------------------------------------------------------- /src/web-search/LiveSearchInput.module.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Input 3 | */ 4 | 5 | .input { 6 | font-family: var(--body-font); 7 | @extend %ms-font-size-2; 8 | padding: 16px; 9 | height: 64px; 10 | background: transparent; 11 | border: 0; 12 | flex: 1 1 auto; 13 | padding-left: 0; 14 | font-weight: bold; 15 | color: var(--text-bold); 16 | min-width: 48px; 17 | 18 | &::placeholder { 19 | font-weight: normal; 20 | color: var(--text-mute); 21 | } 22 | 23 | &:focus::placeholder { 24 | color: color-mod(var(--text-mute) alpha(25%)); 25 | } 26 | } 27 | 28 | .input:focus { 29 | outline: 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/web-icons/icons/talk-bubbles-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/gatsby-plugin-meta-redirect/getMetaRedirect.js: -------------------------------------------------------------------------------- 1 | module.exports = function getMetaRedirect(toPath) { 2 | let url = toPath.trim() 3 | 4 | const hasProtocol = url.includes('://') 5 | if (!hasProtocol) { 6 | const hasLeadingSlash = url.startsWith('/') 7 | if (!hasLeadingSlash) { 8 | url = `/${url}` 9 | } 10 | 11 | const resemblesFile = url.includes('.') 12 | if (!resemblesFile) { 13 | url = `${url}/`.replace(/\/\/+/g, '') 14 | } 15 | } 16 | 17 | // Remove trailing slash 18 | url = url.replace(/\/$/, '') 19 | 20 | return `` 21 | } 22 | -------------------------------------------------------------------------------- /src/gatsby-plugin-meta-redirect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devhints/gatsby-plugin-meta-redirect", 3 | "version": "1.1.0", 4 | "repository": "git@github.com:getchalk/gatsby-plugin-meta-redirect.git", 5 | "author": "Danny Wilson ", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "keywords": [ 9 | "gatsby", 10 | "gatsby-plugin" 11 | ], 12 | "scripts": { 13 | "clean": "git clean -xdf ./*.js", 14 | "build": "babel src -d .", 15 | "test": "jest", 16 | "prepublishOnly": "yarn build" 17 | }, 18 | "dependencies": { 19 | "fs-extra": "^7.0.1", 20 | "path": "^0.12.7" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/web-icons/ionicons/_ionicons_svg_logo-twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/web/components/HomeButton.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | & { 3 | display: inline-block; 4 | box-shadow: inset 0 0 0 1px var(--dark-line-color); 5 | border-radius: 50%; 6 | padding: 12px; 7 | } 8 | 9 | &, 10 | &:visited { 11 | color: var(--text-mute); 12 | } 13 | 14 | &:hover, 15 | &:focus { 16 | background-color: var(--brand-a); 17 | color: white; 18 | } 19 | } 20 | 21 | .icon svg { 22 | width: 24px; 23 | height: 24px; 24 | } 25 | 26 | .icon :global(.clr-i-outline) { 27 | fill: var(--dark-line-color); 28 | } 29 | 30 | .root:hover :global(.clr-i-outline), 31 | .root:focus :global(.clr-i-outline) { 32 | fill: white; 33 | } 34 | -------------------------------------------------------------------------------- /src/web/components/SpanPushButton.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | display: inline-block; 3 | text-decoration: none; 4 | padding: 8px 16px; 5 | border-radius: 3px; 6 | 7 | &, 8 | &:visited { 9 | background-color: var(--brand-a); 10 | background: var(--brand-a-gradient); 11 | color: white; 12 | } 13 | 14 | &:hover, 15 | &:focus { 16 | background: var(--brand-a); 17 | box-shadow: none; 18 | color: white; 19 | } 20 | } 21 | 22 | .isDark { 23 | &, 24 | &:visited { 25 | background: var(--brand-a6); 26 | color: white; 27 | } 28 | 29 | &:hover, 30 | &:focus { 31 | background: var(--brand-a7); 32 | color: white; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Unpath helper 3 | * Remove the unnecessary slashes 4 | */ 5 | 6 | export function unpath(path: string): string { 7 | return path.replace(/^\//, '') 8 | } 9 | 10 | /** 11 | * Convert pathname to keywords. 12 | * 13 | * keywordify('/gatsby') => 'gatsby' 14 | * keywordify('/rails/models.html') => 'rails-models' 15 | * keywordify(null) => null 16 | */ 17 | 18 | export function keywordify(str: string | null | void) { 19 | if (!str) { 20 | return null 21 | } 22 | 23 | return str 24 | .slice(1) 25 | .replace(/^cheatsheets-ng\//, '') 26 | .replace(/\//g, '-') 27 | .replace(/ /g, '_') 28 | .replace(/\.html$/, '') 29 | } 30 | -------------------------------------------------------------------------------- /src/web-search/comps/SearchItem.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import React from 'react' 3 | import CSS from './SearchItem.module.css' 4 | 5 | import { SearchPageItem } from '../../types/types' 6 | 7 | export interface Props { 8 | page: SearchPageItem 9 | } 10 | 11 | const SearchItem = ({ page }: Props) => { 12 | return ( 13 |
  • 14 | 15 | {page.nodePath} 16 | {page.title} 17 | {page.category} 18 | 19 |
  • 20 | ) 21 | } 22 | 23 | export default SearchItem 24 | -------------------------------------------------------------------------------- /src/web-search/comps/SearchBox.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import React from 'react' 3 | import { searchLine } from '../../web-icons' 4 | import LiveSearchInput from '../LiveSearchInput' 5 | import CSS from './SearchBox.module.css' 6 | 7 | const SearchBox = () => { 8 | return ( 9 | 20 | ) 21 | } 22 | 23 | export default SearchBox 24 | -------------------------------------------------------------------------------- /src/css-markdown/css/ul.css: -------------------------------------------------------------------------------- 1 | .MarkdownBody ul.-six-column { 2 | & { 3 | display: flex; 4 | flex-wrap: wrap; 5 | } 6 | 7 | & > li { 8 | flex: 0 0 calc(100% / 6); 9 | 10 | @media (max-width: 480px) { 11 | flex: 0 0 calc(100% / 2); 12 | } 13 | 14 | @media (max-width: 768px) { 15 | flex: 0 0 calc(100% / 4); 16 | } 17 | } 18 | } 19 | 20 | .MarkdownBody ul.-four-column { 21 | & { 22 | display: flex; 23 | flex-wrap: wrap; 24 | } 25 | 26 | & > li { 27 | flex: 0 0 calc(100% / 4); 28 | 29 | @media (max-width: 480px) { 30 | flex: 0 0 calc(100% / 2); 31 | } 32 | 33 | @media (max-width: 768px) { 34 | flex: 0 0 calc(100% / 3); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | 4 | # Number of days of inactivity before a stale issue is closed 5 | daysUntilClose: 7 6 | 7 | # Issues with these labels will never be considered stale 8 | exemptLabels: 9 | - pinned 10 | - security 11 | 12 | # Label to use when marking an issue as stale 13 | staleLabel: stale 14 | 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: false 17 | 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: > 20 | This issue has been automatically closed due to inactivity. If the problem persists, feel free to open a new issue. Thanks for all your contributions! 21 | -------------------------------------------------------------------------------- /gatsby-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Node APIs in this file. 3 | * 4 | * See: 5 | * 6 | * - https://www.gatsbyjs.org/docs/node-apis/ 7 | * - https://www.gatsbyjs.org/docs/adding-markdown-pages/#create-static-pages-using-gatsbys-node-api 8 | */ 9 | 10 | // Create pages 11 | import createSitePages from './src/gatsby-node/createPages' 12 | import onCreateNode from './src/gatsby-node/onCreateNode' 13 | import onCreateWebpackConfig from './src/gatsby-node/onCreateWebpackConfig' 14 | 15 | const createPages = async (ctx: any) => { 16 | const SheetTemplate = require.resolve('./src/gatsby-shell/SheetTemplate.tsx') 17 | 18 | await createSitePages(ctx, { SheetTemplate }) 19 | } 20 | 21 | export { onCreateNode, onCreateWebpackConfig, createPages } 22 | -------------------------------------------------------------------------------- /src/web/components/IntroContent.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import React from 'react' 3 | import css from 'styled-jsx/css' 4 | 5 | interface Props { 6 | children: React.ReactNode 7 | className?: string 8 | } 9 | 10 | /** 11 | * Intro content just below the cheatsheet title 12 | */ 13 | 14 | export const IntroContent = ({ children, className }: Props) => ( 15 |
    16 | {children} 17 | 18 |
    19 | ) 20 | 21 | const CSS = css` 22 | @media (min-width: 481px) { 23 | .IntroContent { 24 | max-width: 480px; 25 | text-align: center; 26 | margin-left: auto; 27 | margin-right: auto; 28 | } 29 | } 30 | ` 31 | 32 | export default IntroContent 33 | -------------------------------------------------------------------------------- /src/web/components/PageLink.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import React from 'react' 3 | import { unpath } from '../../helpers' 4 | import { SiteLink } from '../../types/types' 5 | import AttributePeg from './AttributePeg' 6 | import CSS from './PagesList.module.css' 7 | 8 | const PageLink = ({ 9 | link, 10 | updatedLabel 11 | }: { 12 | link: SiteLink 13 | updatedLabel: string 14 | }) => ( 15 | 16 | 17 | {unpath(link.path)}{' '} 18 | 19 | {link.title} 20 | 21 | 22 | ) 23 | 24 | export default PageLink 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended", 5 | "tslint-config-prettier" 6 | ], 7 | "jsRules": { 8 | // Don't be so strict about how object literal keys are sorted. 9 | "object-literal-sort-keys": false 10 | }, 11 | "rules": { 12 | // Allow interfaces even if they're not prefixed with 'I' (eg, 'IUser'). 13 | "interface-name": false, 14 | // Don't be so strict about how object literal keys are sorted. 15 | "object-literal-sort-keys": false, 16 | // Don't require the 'public' keyword. 17 | "member-access": [true, "no-public"], 18 | // Don't enforce curlies 19 | "curly": false 20 | }, 21 | "rulesDirectory": [] 22 | } 23 | -------------------------------------------------------------------------------- /src/gatsby-node/onCreateWebpackConfig.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | actions: { 3 | setWebpackConfig: (config: any) => void 4 | } 5 | loaders: any 6 | } 7 | 8 | /** 9 | * Modify Webpack configuration. 10 | * 11 | * This makes it so that jQuery isn't part of the final JS bundle, saving up 12 | * some space. 13 | */ 14 | 15 | const onCreateWebpackConfig = ({ actions }: Props) => { 16 | const noop = require.resolve('../helpers/noop.js') 17 | 18 | actions.setWebpackConfig({ 19 | // isotope-layout tries to require('jquery'), but let's let that 20 | // fail silently. We don't want it to load jQuery. 21 | resolve: { 22 | alias: { 23 | jquery: noop, 24 | jsdom: noop 25 | } 26 | } 27 | }) 28 | } 29 | 30 | export default onCreateWebpackConfig 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Target latest version of ECMAScript. 4 | "target": "esnext", 5 | // Search under node_modules for non-relative imports. 6 | "moduleResolution": "node", 7 | // Process & infer types from .js files. 8 | "allowJs": true, 9 | // Don't emit; allow Babel to transform files. 10 | "noEmit": true, 11 | // Enable strictest settings like strictNullChecks & noImplicitAny. 12 | "strict": true, 13 | // Disallow features that require cross-file information for emit. 14 | // "isolatedModules": true, 15 | // Import non-ES modules as default imports. 16 | "esModuleInterop": true, 17 | "jsx": "react", 18 | "typeRoots": ["./src/types", "./node_modules/@types"] 19 | }, 20 | "include": ["src"] 21 | } 22 | -------------------------------------------------------------------------------- /src/gatsby-node/onCreateNode.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Add extra node fields. 3 | * 4 | * This allows us to use $node_id and $category in sheet template queries. 5 | */ 6 | 7 | interface Props { 8 | node: any 9 | getNode: any 10 | actions: any 11 | } 12 | 13 | const onCreateNode = (props: Props) => { 14 | createNodeFields(props) 15 | } 16 | 17 | const createNodeFields = ({ node, actions }: Props) => { 18 | const { createNodeField } = actions 19 | 20 | if (node.internal.type === `MarkdownRemark`) { 21 | createNodeField({ 22 | node, 23 | name: 'node_id', 24 | value: node.id 25 | }) 26 | 27 | createNodeField({ 28 | node, 29 | name: 'category', 30 | value: node.frontmatter.category || 'Default' 31 | }) 32 | } 33 | } 34 | 35 | export default onCreateNode 36 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // See https://www.gatsbyjs.org/docs/unit-testing/ 2 | module.exports = { 3 | transform: { 4 | '^.+\\.[tj]sx?$': `/support/jest.preprocess.js` 5 | }, 6 | setupFiles: ['./tests/jest.setup.js'], 7 | snapshotSerializers: ['enzyme-to-json/serializer'], 8 | testPathIgnorePatterns: ['/node_modules/', '/.cache/'], 9 | testURL: 'http://localhost', 10 | // transformIgnorePatterns: [ 11 | // 'xxxx/node_modules/(?!(devhints|@devhints|rehype-decorate/|gatsby/)).+$' 12 | // ], 13 | moduleNameMapper: { 14 | '.*raw-loader.*': '/__mocks__/filemock.js', 15 | '.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`, 16 | '.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `/__mocks__/filemock.js` 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/web-icons/icons/talk-bubbles-line.svg: -------------------------------------------------------------------------------- 1 | 2 | talk-bubbles-line 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/web/components/SearchFooter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import css from 'styled-jsx/css' 3 | import SearchFooterSection from './SearchFooterSection' 4 | 5 | /** 6 | * Search footer in the cheatsheets page. 7 | */ 8 | 9 | const SearchFooter = () => ( 10 | 17 | ) 18 | 19 | const style = css` 20 | .SearchFooter { 21 | padding-top: 16px; 22 | padding-bottom: 16px; 23 | background: var(--bg-gray); 24 | border-top: solid 1px var(--dark-line-color); 25 | border-bottom: solid 1px var(--dark-line-color); 26 | } 27 | 28 | .container { 29 | @extend %container; 30 | } 31 | ` 32 | 33 | export default SearchFooter 34 | -------------------------------------------------------------------------------- /src/web/components/AttributePeg.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import React from 'react' 3 | import css from 'styled-jsx/css' 4 | 5 | interface Props { 6 | hint: string 7 | } 8 | 9 | /** 10 | * A dot thing 11 | */ 12 | 13 | export const AttributePeg = ({ hint }: Props) => ( 14 | 15 | 16 | 17 | 18 | ) 19 | 20 | const CSS = css` 21 | .root { 22 | display: inline-block; 23 | height: 12px; 24 | width: 20px; 25 | text-align: center; 26 | } 27 | 28 | .dot { 29 | display: inline-block; 30 | width: 8px; 31 | height: 8px; 32 | background: #77dab2; /* saturate(lighten(#5a8, 16%), 24%); */ 33 | border-radius: 50%; 34 | } 35 | ` 36 | 37 | export default AttributePeg 38 | -------------------------------------------------------------------------------- /static/admin/config.yml: -------------------------------------------------------------------------------- 1 | # See: https://www.netlifycms.org/docs/add-to-your-site/#configuration 2 | backend: 3 | name: git-gateway 4 | branch: master 5 | 6 | # Location in the repo 7 | media_folder: static/images 8 | 9 | # Location in devhints.io/ 10 | public_folder: /images 11 | 12 | collections: 13 | - name: 'sheet' 14 | label: 'Sheet' 15 | folder: 'sheets' 16 | create: true 17 | fields: 18 | - label: 'Title' 19 | name: 'title' 20 | widget: 'string' 21 | 22 | - label: 'Path' 23 | name: 'path' 24 | widget: 'string' 25 | 26 | - label: 'Category' 27 | name: 'category' 28 | widget: 'string' 29 | 30 | - label: 'Body' 31 | name: 'body' 32 | widget: 'markdown' 33 | 34 | - label: 'Intro' 35 | name: 'intro' 36 | widget: 'string' 37 | -------------------------------------------------------------------------------- /src/web-icons/ionicons/_ionicons_svg_logo-github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/web/components/PageActions.module.css: -------------------------------------------------------------------------------- 1 | .root, 2 | .root > li { 3 | margin: 0; 4 | padding: 0; 5 | list-style-type: none; 6 | } 7 | 8 | .root { 9 | margin-left: 8px; 10 | } 11 | 12 | .item { 13 | display: inline; 14 | } 15 | 16 | .icon svg { 17 | width: 16px; 18 | height: 16px; 19 | } 20 | 21 | .icon { 22 | margin-right: 8px; 23 | } 24 | 25 | .link { 26 | @extend %ms-font-size--1; 27 | display: inline-block; 28 | height: 32px; 29 | line-height: calc(32px - 2px); 30 | text-decoration: none; 31 | white-space: nowrap; 32 | padding: 0 16px; 33 | border: solid 1px var(--dark-line-color); 34 | border-radius: 24px; 35 | } 36 | 37 | .link, 38 | .link:visited { 39 | color: var(--text-mute); 40 | } 41 | 42 | .link:hover, 43 | .link:focus { 44 | background: var(--brand-a-gradient), var(--brand-a); 45 | box-shadow: var(--shadow3); 46 | color: white; 47 | } 48 | -------------------------------------------------------------------------------- /src/web/components/SearchFooterSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import css from 'styled-jsx/css' 3 | import SearchBox from '../../web-search/comps/SearchBox' 4 | import HomeButton from './HomeButton' 5 | 6 | const SearchFooterSection = () => ( 7 |
    8 |
    9 |
    10 | 11 | 12 |
    13 | 14 |
    15 | 16 |
    17 | 18 | 19 |
    20 | ) 21 | 22 | const CSS = css` 23 | .SearchFooterSection { 24 | display: flex; 25 | } 26 | 27 | .search { 28 | flex: 0 1 640px; 29 | } 30 | 31 | .links { 32 | @extend %gutter-padding-left; 33 | flex: 0 1 auto; 34 | margin-left: auto; 35 | } 36 | ` 37 | 38 | export default SearchFooterSection 39 | -------------------------------------------------------------------------------- /src/web-icons/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-webpack-loader-syntax */ 2 | 3 | export { default as homeLine } from '!raw-loader!./icons/home-line.svg' 4 | export { default as talkBubblesLine } from '!raw-loader!./icons/talk-bubbles-line.svg' // prettier-ignore 5 | export { default as talkBubblesSolid } from '!raw-loader!./icons/talk-bubbles-solid.svg' // prettier-ignore 6 | export { default as searchLine } from '!raw-loader!./icons/search-line.svg' // prettier-ignore 7 | export { default as facebook } from '!raw-loader!./ionicons/_ionicons_svg_logo-facebook.svg' // prettier-ignore 8 | export { default as twitter } from '!raw-loader!./ionicons/_ionicons_svg_logo-twitter.svg' // prettier-ignore 9 | export { default as github } from '!raw-loader!./ionicons/_ionicons_svg_logo-github.svg' // prettier-ignore 10 | export { default as mdArrowBack } from '!raw-loader!./ionicons/_ionicons_svg_md-arrow-back.svg' // prettier-ignore 11 | -------------------------------------------------------------------------------- /src/css-base/utils/gutter-padding.css: -------------------------------------------------------------------------------- 1 | /* 2 | * (Mixin) gutter-padding-* 3 | */ 4 | 5 | %gutter-padding { 6 | & { 7 | padding: var(--gut); 8 | } 9 | 10 | @media (max-width: 480px) { 11 | padding: var(--gut-small); 12 | } 13 | } 14 | 15 | %gutter-padding-left { 16 | & { 17 | padding-left: var(--gut); 18 | } 19 | 20 | @media (max-width: 480px) { 21 | padding-left: var(--gut-small); 22 | } 23 | } 24 | 25 | %gutter-padding-right { 26 | & { 27 | padding-right: var(--gut); 28 | } 29 | 30 | @media (max-width: 480px) { 31 | padding-right: var(--gut-small); 32 | } 33 | } 34 | 35 | %gutter-right { 36 | & { 37 | right: var(--gut); 38 | } 39 | 40 | @media (max-width: 480px) { 41 | right: var(--gut-small); 42 | } 43 | } 44 | 45 | %gutter-left { 46 | & { 47 | left: var(--gut); 48 | } 49 | 50 | @media (max-width: 480px) { 51 | left: var(--gut-small); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/css-markdown/css/headings.css: -------------------------------------------------------------------------------- 1 | /* 2 | * MarkdownBody context 3 | */ 4 | 5 | .MarkdownBody h2 { 6 | & { 7 | @extend %heading-style; 8 | } 9 | & { 10 | @extend %ms-font-size-6; 11 | } 12 | line-height: 1.2; 13 | font-weight: 200; 14 | font-family: var(--heading-font); 15 | margin-top: 0; 16 | } 17 | 18 | .MarkdownBody h3 { 19 | @extend %ms-font-size-2; 20 | margin: 0; 21 | padding: 0; 22 | margin-bottom: 16px; 23 | font-family: var(--heading-font); 24 | font-weight: 400; 25 | color: var(--brand-a); 26 | } 27 | 28 | .MarkdownBody { 29 | & a, 30 | & a:visited { 31 | color: var(--brand-b); 32 | text-decoration: none; 33 | } 34 | 35 | & a:hover { 36 | text-decoration: underline; 37 | } 38 | 39 | & em { 40 | font-style: normal; 41 | color: var(--text-mute); 42 | } 43 | 44 | & iframe { 45 | border: 0; 46 | margin: 0; 47 | width: 100%; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sheets/devhints.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Devhints styles 3 | updated: 2019-08-22 4 | intro: > 5 | This is a reference of styles that you can use on Devhints cheatsheets. How 6 | meta! 7 | 8 | You can refer to this when contributing your own cheatsheets to the [GitHub 9 | repo](https://github.com/rstacruz/cheatsheets/). 10 | --- 11 | 12 | ## Intro 13 | 14 | 15 | 16 | ### Classes 17 | 18 | ``` 19 | ## This h2 has a class 20 | 21 | 22 | 23 | This paragraph has a class 24 | 25 | 26 | ``` 27 | 28 | See: [CSS classes](/devhints/classes) 29 | 30 | ### Tables 31 | 32 | ``` 33 | . 34 | | Example | Output | 35 | | --------------- | ---------------------- | 36 | | `%m/%d/%Y` | `06/05/2013` | 37 | | `%A, %B %e, %Y` | `Sunday, June 5, 2013` | 38 | | `%b %e %a` | `Jun 5 Sun` | 39 | . 40 | ``` 41 | 42 | See: [Tables](/devhints/tables) 43 | -------------------------------------------------------------------------------- /src/web/components/RelatedPostsCallout.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import React from 'react' 3 | import useSiteContent from '../../gatsby-hooks/useSiteContent' 4 | import CSS from './RelatedPostsCallout.module.css' 5 | import SpanPushButton from './SpanPushButton' 6 | 7 | interface Props { 8 | pageCount: number 9 | } 10 | 11 | const RelatedPostsCallout = ({ pageCount }: Props) => { 12 | const { relatedPostsCallout: content } = useSiteContent() 13 | 14 | return ( 15 | 16 | 17 |
    18 | 19 | 20 | {content.description.replace('SIZE', pageCount.toString())} 21 | 22 | {content.link} 23 |
    24 | 25 |
    26 | ) 27 | } 28 | 29 | export default RelatedPostsCallout 30 | -------------------------------------------------------------------------------- /src/uipad/Canvas.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import css from 'styled-jsx/css' 3 | import { OptionsProvider } from './OptionsContext' 4 | import { FrameOptions } from './types' 5 | 6 | interface Props { 7 | children: React.ReactNode 8 | background?: string 9 | frame?: Partial 10 | } 11 | 12 | /** 13 | * A canvas that contains groups and frames. 14 | */ 15 | 16 | const Canvas = (props: Props) => { 17 | const { children, frame } = props 18 | const { background } = props 19 | 20 | return ( 21 | 22 |
    23 | {children} 24 | 25 | 26 |
    27 |
    28 | ) 29 | } 30 | 31 | const CSS = css` 32 | .Canvas { 33 | display: flex; 34 | flex-wrap: wrap; 35 | align-items: flex-start; 36 | background: #fafafa; 37 | } 38 | ` 39 | 40 | export default Canvas 41 | -------------------------------------------------------------------------------- /src/web-comments/comps/CommentsAreaSummary.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import React from 'react' 3 | import { talkBubblesLine, talkBubblesSolid } from '../../web-icons' 4 | import CSS from './CommentsAreaSummary.module.css' 5 | 6 | interface Props { 7 | count: React.ReactNode 8 | } 9 | 10 | /** 11 | * Summary in the comments area 12 | */ 13 | 14 | const CommentsAreaSummary = ({ count }: Props) => ( 15 | 16 | 20 | 24 | {count}{' '} 25 | for this cheatsheet.{' '} 26 | Write yours! 27 | 28 | ) 29 | 30 | export default CommentsAreaSummary 31 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/missing-message.css: -------------------------------------------------------------------------------- 1 | .missing-message.missing-message { 2 | text-align: center; 3 | margin: 32px 0; 4 | display: flex; 5 | align-items: center; 6 | border-top: solid 1px var(--dark-line-color); 7 | padding-top: 16px; 8 | 9 | @media (min-width: 769px) { 10 | padding-top: 32px; 11 | } 12 | 13 | & > h3, 14 | & > p { 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | & > h3 { 20 | @extend %ms-font-size-1; 21 | font-weight: normal; 22 | color: var(--text-body); 23 | flex: 1 0 auto; 24 | text-align: left; 25 | } 26 | 27 | & > h3::before { 28 | content: ''; 29 | /* TODO @include ion-md-arrow-forward(24px, $base-a); */ 30 | margin-right: 16px; 31 | } 32 | 33 | & > p { 34 | color: var(--text-mute); 35 | flex: 0 0 auto; 36 | } 37 | 38 | @media (max-width: 480px) { 39 | flex-wrap: wrap; 40 | 41 | & > p { 42 | margin-top: 16px; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/web/components/RelatedPostsSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Props } from './RelatedPostsArea' 3 | import RelatedPostsCallout from './RelatedPostsCallout' 4 | import RelatedPostsGroup from './RelatedPostsGroup' 5 | 6 | /** 7 | * Related posts section 8 | */ 9 | 10 | export const RelatedPostsSection = ({ 11 | category, 12 | pageCount, 13 | relatedPages, 14 | topPages 15 | }: Props) => ( 16 |
    17 |
    18 | 19 |
    20 |
    21 | 27 |
    28 |
    29 | 30 |
    31 |
    32 | ) 33 | 34 | export default RelatedPostsSection 35 | -------------------------------------------------------------------------------- /docs/writing_cheatsheets.md: -------------------------------------------------------------------------------- 1 | # Writing cheatsheets 2 | 3 | ## Frontmatter 4 | 5 | Each sheet supports these metadata: 6 | 7 | ~~~ yml 8 | --- 9 | title: React.js 10 | layout: 2017/sheet # 'default' | '2017/sheet' 11 | 12 | # Optional: 13 | category: React 14 | updated: 2017-08-30 # To show in the updated list 15 | ads: false # Add this to disable ads 16 | weight: -5 # lower number = higher in related posts list 17 | deprecated: true # Don't show in related posts 18 | deprecated_by: /enzyme # Point to latest version 19 | prism_languages: [vim] # Extra syntax highlighting 20 | intro: | 21 | This is some *Markdown* at the beginning of the article. 22 | tags: 23 | - WIP 24 | - Featured 25 | --- 26 | ~~~ 27 | 28 | ## Prism languages 29 | 30 | For supported prism languages, see: 31 | 32 | 33 | ## CSS classes 34 | 35 | See for a reference on styling. 36 | -------------------------------------------------------------------------------- /src/css-markdown/css/prism.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Syntax highlighting via Prism.js 3 | */ 4 | 5 | :root { 6 | --syntax-a: var(--brand-b); 7 | --syntax-a3: color-mod(var(--syntax-a) lightness(+8%) hue(-10deg)); 8 | --syntax-a7: color-mod(var(--syntax-a) lightness(-8%) hue(+10deg)); 9 | 10 | --syntax-b: var(--brand-c); 11 | --syntax-b3: color-mod(var(--syntax-b) lightness(+8%) hue(-10deg)); 12 | --syntax-b7: color-mod(var(--syntax-b) lightness(-8%) hue(+10deg)); 13 | 14 | --syntax-m: #aaaaaa; 15 | } 16 | 17 | .token { 18 | &.tag, 19 | &.keyword { 20 | color: var(--syntax-a); 21 | } 22 | 23 | &.tag { 24 | color: var(--syntax-a7); 25 | } 26 | 27 | &.value, 28 | &.string, 29 | &.number, 30 | &.attr-value, 31 | &.boolean, 32 | &.regex { 33 | color: var(--syntax-b); 34 | } 35 | 36 | &.function, 37 | &.attr-name { 38 | color: var(--syntax-a3); 39 | } 40 | 41 | &.comment, 42 | &.punctuation, 43 | &.operator { 44 | color: var(--syntax-m); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/main-heading.css: -------------------------------------------------------------------------------- 1 | /* 2 | * The top-level heading 3 | */ 4 | 5 | .main-heading { 6 | @extend %heading-style; 7 | 8 | & { 9 | margin-top: 0; 10 | margin-bottom: 0; 11 | } 12 | 13 | & > h1 { 14 | @extend %ms-font-size-8; 15 | line-height: 1.2; 16 | font-weight: 200; 17 | font-family: var(--body-font); 18 | margin: 0; 19 | } 20 | 21 | & > h1 > em { 22 | font-style: normal; 23 | color: color-mod(var(--text-mute) alpha(20%)); 24 | } 25 | } 26 | 27 | /* 28 | * Center 29 | */ 30 | 31 | .main-heading.-center { 32 | & > h1 { 33 | text-align: center; 34 | } 35 | 36 | & > .adbox { 37 | margin-top: 16px; 38 | text-align: center; 39 | } 40 | 41 | & > .adbox > .ad { 42 | display: inline-block; 43 | } 44 | 45 | & > .adbox > .ad.-carbon { 46 | margin-top: 16px; 47 | } 48 | } 49 | 50 | /** 51 | * Add some space in preview mode 52 | */ 53 | 54 | .PreviewMode .main-heading { 55 | margin-top: 16px; 56 | } 57 | -------------------------------------------------------------------------------- /src/gatsby-node/helpers.tsx: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | 3 | /** 4 | * Get an absolute path from the project root. 5 | * 6 | * root('src/index.js') 7 | * // => '/path/to/src/index.js' 8 | */ 9 | 10 | const root = (...args: string[]) => resolve(__dirname, '..', '..', ...args) 11 | 12 | /** 13 | * Strip path of its parent. 14 | * 15 | * stripPath('/path/to/react.md', '/path/to') 16 | * // => 'react.md' 17 | * 18 | * stripPath('/path/to/react.md', '/path/to', '.md') 19 | * // => 'react' 20 | */ 21 | 22 | function stripPath(fullPath: string, parent: string, extension: string) { 23 | if (!fullPath.startsWith(parent)) { 24 | throw new Error(`${fullPath} doesnt start with ${parent}`) 25 | } 26 | let result = fullPath.substr(parent.length) 27 | 28 | if (extension && result.endsWith(extension)) { 29 | result = result.substr(0, result.length - extension.length) 30 | } 31 | 32 | return result 33 | } 34 | 35 | /* 36 | * Export 37 | */ 38 | 39 | export { root, stripPath } 40 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import React from 'react' 3 | import { keywordify } from '../helpers' 4 | 5 | import useSiteContent from '../gatsby-hooks/useSiteContent' 6 | import { LiveSearchInput } from '../web-search' 7 | import ExternalSearchLinks from '../web/components/ExternalSearchLinks' 8 | 9 | /** 10 | * The 404 page. 11 | */ 12 | 13 | export const NotFoundPage = () => { 14 | const CONTENT = useSiteContent() 15 | 16 | const pathname = typeof location !== 'undefined' ? location.pathname : null 17 | const keyword = keywordify(pathname) 18 | 19 | const labels = { 20 | title: CONTENT.notFound.title, 21 | description: CONTENT.notFound.description, 22 | home: CONTENT.notFound.home 23 | } 24 | 25 | return ( 26 |
    27 |

    {labels.title}

    28 |

    {labels.description}

    29 | {keyword && } 30 | 31 | {labels.home} 32 |
    33 | ) 34 | } 35 | 36 | export default NotFoundPage 37 | -------------------------------------------------------------------------------- /src/web/components/BackButton.module.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | width: 48px; 4 | height: 48px; 5 | line-height: calc(48px - 2px); 6 | text-align: center; 7 | display: inline-block; 8 | border-radius: 50%; 9 | transition: all 100ms linear; 10 | 11 | /* Smaller on mobile */ 12 | @media (max-width: 480px) { 13 | width: 32px; 14 | height: 32px; 15 | line-height: calc(32px - 2); 16 | } 17 | 18 | /* Colors */ 19 | &, 20 | &:visited { 21 | color: var(--text-mute); 22 | } 23 | 24 | /* Active */ 25 | &:hover, 26 | &:focus { 27 | color: white; 28 | background: color-mod(var(--brand-b) alpha(40%)); 29 | opacity: 1; 30 | } 31 | 32 | &:hover .icon, 33 | &:focus .icon { 34 | color: white; 35 | } 36 | 37 | /* Icon: smaller on mobile */ 38 | @media (max-width: 480px) { 39 | &::before { 40 | font-size: 16px; 41 | } 42 | } 43 | } 44 | 45 | .icon { 46 | height: 24px; 47 | width: 24px; 48 | transform: rotate(-90deg); 49 | color: var(--brand-a); 50 | } 51 | -------------------------------------------------------------------------------- /src/uipad/OptionsContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { FrameOptions } from './types' 3 | 4 | const defaults: FrameOptions = { 5 | margin: 24, 6 | grid: 128, 7 | pad: 0, 8 | background: '#fff', 9 | width: null, 10 | size: null 11 | } 12 | 13 | interface Options { 14 | frame: Partial | null | undefined 15 | } 16 | 17 | const Context = React.createContext>({}) 18 | 19 | /** 20 | * Fetches options from the parent Canvas 21 | */ 22 | 23 | export const useOptions = (overrides?: Partial): FrameOptions => { 24 | return { ...defaults, ...useContext(Context), ...overrides } 25 | } 26 | 27 | /** 28 | * Provider used by the canvas. 29 | * Used to propagate options to Frames. 30 | */ 31 | 32 | export const OptionsProvider = ({ 33 | children, 34 | frame 35 | }: Options & { children: React.ReactNode }) => { 36 | const thisValue = frame || {} 37 | const mergedValue = useOptions(thisValue) 38 | return {children} 39 | } 40 | -------------------------------------------------------------------------------- /src/web/components/RelatedPostItem.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import { Link } from 'gatsby' 3 | import React from 'react' 4 | import useSiteContent from '../../gatsby-hooks/useSiteContent' 5 | import CSS from './RelatedPostItem.module.css' 6 | 7 | /* 8 | * Types 9 | */ 10 | 11 | interface Props { 12 | /** Extra classnames to be appended to the root element */ 13 | className?: string 14 | 15 | /** Path to be linked to */ 16 | path: string 17 | 18 | /** Title to be shown */ 19 | title: string 20 | 21 | isPrimary?: boolean 22 | } 23 | 24 | const RelatedPostItem = (props: Props) => { 25 | const { className, path, title, isPrimary } = props 26 | const suffix = useSiteContent().sheet.suffix 27 | 28 | return ( 29 |
    30 | 31 | {title} 32 | {suffix} 33 | 34 |
    35 | ) 36 | } 37 | 38 | export default RelatedPostItem 39 | -------------------------------------------------------------------------------- /sheets/emacs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Emacs 3 | category: Apps 4 | updated: 2019-01-04 5 | tags: [WIP] 6 | --- 7 | 8 | ## Shortcuts 9 | 10 | 11 | 12 | ### Movements 13 | 14 | | Description | Key | 15 | | ------------------------- | --------------- | 16 | | Up/down | `C-n` _/_ `C-p` | 17 | | left/right | `C-f` _/_ `C-b` | 18 | | | | 19 | | Page up/page down | `C-v` _/_ `M-v` | 20 | | | | 21 | | Beginning/end of line | `C-a` _/_ `C-e` | 22 | | Beginning/end of sentence | `M-a` _/_ `M-e` | 23 | 24 | 25 | 26 | ### Basic 27 | 28 | | Description | Key | 29 | | ----------- | ----------- | 30 | | Find file | `C-x` `C-f` | 31 | | Save file | `C-x` `C-s` | 32 | 33 | 34 | 35 | ### Command line 36 | 37 | | Description | Key | 38 | | ----------------- | ----- | 39 | | Open command line | `M-x` | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/web/components/PagesList.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import React from 'react' 3 | import useSiteContent from '../../gatsby-hooks/useSiteContent' 4 | import { SiteLink } from '../../types/types' 5 | import PageLink from './PageLink' 6 | import CSS from './PagesList.module.css' 7 | 8 | /** 9 | * Types 10 | */ 11 | 12 | export interface Props { 13 | links: SiteLink[] 14 | title?: string 15 | } 16 | 17 | export type ViewProps = Props & { 18 | updatedLabel: string 19 | } 20 | 21 | /** 22 | * List of pages 23 | */ 24 | 25 | export const PagesList = (props: Props) => { 26 | const content = useSiteContent() 27 | const { updatedLabel } = content.home 28 | const { title, links } = props 29 | 30 | return ( 31 |
    32 |

    33 | {title} 34 |

    35 | 36 | {links.map(link => ( 37 | 38 | ))} 39 |
    40 | ) 41 | } 42 | 43 | export default PagesList 44 | -------------------------------------------------------------------------------- /src/css-markdown/css/p.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Crosslink (eg, phoenix.md) 3 | */ 4 | 5 | .MarkdownBody.MarkdownBody { 6 | & img { 7 | max-width: 100%; 8 | } 9 | } 10 | 11 | .MarkdownBody.MarkdownBody p.-crosslink { 12 | & > a { 13 | display: block; 14 | text-decoration: none; 15 | color: var(--brand-a); 16 | border-bottom: 0; 17 | box-shadow: none; 18 | margin: -16px; 19 | padding: 16px; 20 | } 21 | 22 | & > a:visited { 23 | color: var(--brand-a); 24 | } 25 | 26 | & > a::before { 27 | content: ''; 28 | /* TODO @include ion-md-arrow-forward(16px, white); */ 29 | margin-right: 16px; 30 | width: 32px; 31 | height: 32px; 32 | line-height: 32px; 33 | border-radius: 50%; 34 | } 35 | 36 | & > a, 37 | & > a:visited { 38 | &::before { 39 | background-color: var(--brand-a); 40 | color: white; 41 | } 42 | } 43 | 44 | & > a:hover, 45 | & > a:focus { 46 | & { 47 | color: var(--brand-a7); 48 | } 49 | 50 | &::before { 51 | background-color: var(--brand-a7); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/web/components/RelatedPostsArea.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import css from 'styled-jsx/css' 3 | import { SiteLink } from '../../types/types' 4 | import RelatedPostsSection from './RelatedPostsSection' 5 | 6 | /* 7 | * Props 8 | */ 9 | 10 | export interface Props { 11 | relatedPages: SiteLink[] 12 | topPages: SiteLink[] 13 | pageCount: number 14 | category?: string 15 | } 16 | 17 | /** 18 | * Related posts area 19 | */ 20 | 21 | export const RelatedPostsArea = (props: Props) => ( 22 |
    23 |
    24 | 25 |
    26 | 27 | 28 |
    29 | ) 30 | 31 | const CSS = css` 32 | .RelatedPostsArea { 33 | padding-top: 16px; 34 | padding-bottom: 16px; 35 | background: var(--bg-gray); 36 | 37 | @media (min-width: 481px) { 38 | padding-top: 64px; 39 | padding-bottom: 64px; 40 | } 41 | } 42 | 43 | .container { 44 | @extend %container; 45 | } 46 | ` 47 | 48 | export default RelatedPostsArea 49 | -------------------------------------------------------------------------------- /src/helpers/site_page.tsx: -------------------------------------------------------------------------------- 1 | import groupBy from 'group-by' 2 | import { 3 | AllSitePage, 4 | GroupedSiteLinks, 5 | PageEdge, 6 | SiteLink 7 | } from '../types/types' 8 | 9 | /** 10 | * Converts AllSitePage to SiteLinks 11 | */ 12 | 13 | export function toSiteLinks(pages?: AllSitePage): SiteLink[] { 14 | if (!pages || !pages.edges) return [] 15 | 16 | return pages.edges.map(toSiteLink) 17 | } 18 | 19 | /** 20 | * Convert an edge to a site link 21 | */ 22 | 23 | export function toSiteLink(edge: PageEdge): SiteLink { 24 | return { 25 | path: edge.node.context.nodePath, 26 | title: edge.node.context.title 27 | } 28 | } 29 | 30 | /** 31 | * Groups by category, returns sitelinks 32 | */ 33 | 34 | export function groupByCategory(allPages: AllSitePage): GroupedSiteLinks { 35 | const groups: { [key: string]: PageEdge[] } = groupBy( 36 | allPages.edges, 37 | (edge: PageEdge) => edge.node.context.category 38 | ) 39 | 40 | return Object.keys(groups).reduce((result, group: string) => { 41 | const edges = groups[group] 42 | return { ...result, [group]: edges.map(toSiteLink) } 43 | }, {}) 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [12.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | 16 | - name: Cache Yarn package cache 17 | uses: actions/cache@v1 18 | with: 19 | path: ~/.cache/yarn 20 | key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }} 21 | restore-keys: | 22 | ${{ runner.os }}-yarn- 23 | 24 | - name: Use Node.js 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | 29 | - name: Install dependencies 30 | run: | 31 | yarn install --frozen-lockfile 32 | 33 | - name: Check TypeScript types 34 | run: yarn tsc 35 | 36 | - name: Check for lint warnings 37 | run: yarn lint 38 | 39 | - name: Check Prettier formatting 40 | run: yarn prettier:check 41 | 42 | - name: Run tests 43 | run: yarn test 44 | 45 | - name: Simulate build 46 | run: yarn build 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018, Rico Sta. Cruz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/gatsby-plugin-meta-redirect/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Get Chalk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/web-post-content/lib/doPostTransform.tsx: -------------------------------------------------------------------------------- 1 | import debugjs from 'debug' 2 | import isotopify from './isotopify' 3 | import { loadPrism, PrismType } from './prism' 4 | 5 | const debug = debugjs('app:doPostTransform') 6 | 7 | /** 8 | * Asynchronously performs transformations needed after rendering. 9 | * @returns a Promise that resolves to nothing. 10 | * 11 | * @example 12 | * const el = // ...HTML element 13 | * await doPostTransform(el) 14 | */ 15 | 16 | export function doPostTransform( 17 | element: HTMLElement | null | void 18 | ): Promise { 19 | debug('working on', element) 20 | 21 | return Promise.resolve() 22 | .then(() => { 23 | if (!element) { 24 | return (global as any).Prism 25 | } 26 | debug('Invoking Isotope') 27 | isotopify(element) 28 | return loadPrism(element) 29 | }) 30 | .then((Prism: PrismType) => { 31 | if (element) { 32 | debug('Highlighting via Prism') 33 | Prism.highlightAllUnder(element) 34 | } 35 | }) 36 | .catch((error: Error) => { 37 | debug('Prism/isotope error:', error) 38 | }) 39 | } 40 | 41 | export default doPostTransform 42 | -------------------------------------------------------------------------------- /src/web-post-content/lib/transform.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | // @ts-ignore TODO types for rehype-decorate 3 | import decorate from 'rehype-decorate' 4 | // @ts-ignore TODO types for rehype-react 5 | import RehypeReact from 'rehype-react' 6 | // @ts-ignore TODO types for rehype-table-separators 7 | import separatify from 'rehype-table-separators' 8 | // @ts-ignore TODO types for rehype-wrapify 9 | import wrapify from '../../rehype-wrapify' 10 | import { HastNode } from '../../types/types' 11 | 12 | /** 13 | * Converts a HAST node (generated from Markdown) into a React element. 14 | * Does the appropriate transformations. 15 | * 16 | * @returns a React Element. 17 | */ 18 | 19 | export default function transform(htmlAst: HastNode): React.ReactNode { 20 | // Perform transformations 21 | htmlAst = decorate(htmlAst) 22 | htmlAst = wrapify(htmlAst) 23 | htmlAst = separatify(htmlAst) 24 | 25 | // Convert to React 26 | const element = renderAst(htmlAst) 27 | return element 28 | } 29 | 30 | /** 31 | * Renders HAST into React using `rehype-react`. 32 | */ 33 | 34 | export const renderAst = new RehypeReact({ 35 | createElement: React.createElement 36 | }).Compiler 37 | -------------------------------------------------------------------------------- /src/web/components/RelatedPostsCallout.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | & { 3 | display: flex; 4 | text-decoration: none; 5 | background: var(--brand-a-gradient); 6 | padding: 32px; 7 | align-items: center; 8 | justify-content: center; 9 | color: white; 10 | border-radius: 2px; 11 | box-shadow: var(--shadow2); 12 | text-shadow: 0 1px 1px #0003; 13 | transition: filter 300ms linear; 14 | } 15 | 16 | &, 17 | &:visited { 18 | color: white; 19 | } 20 | 21 | &:hover, 22 | &:focus { 23 | filter: brightness(110%); 24 | transition: filter 50ms linear; 25 | } 26 | } 27 | 28 | .text { 29 | margin: auto; 30 | text-align: center; 31 | } 32 | 33 | .icon { 34 | margin-bottom: 16px; 35 | display: block; 36 | } 37 | 38 | .icon::before { 39 | content: ''; 40 | display: inline-block; 41 | height: 64px; 42 | width: 64px; 43 | border: solid 2px color-mod(var(--brand-a) lightness(+16%) hue(+20deg)); 44 | border-radius: 50%; 45 | text-indent: -2px; 46 | text-shadow: none; 47 | } 48 | 49 | .description { 50 | @extend %ms-font-size-1; 51 | line-height: 1.4; 52 | font-weight: 300; 53 | display: block; 54 | margin-bottom: 16px; 55 | } 56 | -------------------------------------------------------------------------------- /src/web/components/SiteHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import useSiteContent from '../../gatsby-hooks/useSiteContent' 3 | 4 | /** Site header view component */ 5 | export const SiteHeader = () => { 6 | const { 7 | siteHeader: { title, tagline } 8 | } = useSiteContent() 9 | 10 | return ( 11 |
    12 |

    {title}

    13 |

    14 | 15 | 42 |

    43 | ) 44 | } 45 | 46 | /* Export */ 47 | export default SiteHeader 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const DEBUG = process.env.NODE_ENV !== 'production' 2 | 3 | module.exports = ctx => { 4 | return { 5 | plugins: [ 6 | require('postcss-prepend-imports')({ 7 | files: [ 8 | require.resolve( 9 | 'responsive-modular-scale.css/modularscale.extend.css' 10 | ), 11 | require.resolve('./src/css-base/utils/gutter-padding.css'), 12 | require.resolve('./src/css-base/utils/heading-style.css'), 13 | require.resolve('./src/css-base/utils/section-gutter.css'), 14 | require.resolve('./src/css-base/utils/has-container.css') 15 | ] 16 | }), 17 | require('postcss-import')(), 18 | require('postcss-preset-env')({ 19 | stage: 0, 20 | preserve: false, 21 | importFrom: [require.resolve('./src/css-base/variables.css')], 22 | insertBefore: { 23 | 'all-property': [ 24 | require('postcss-extend-rule')({ 25 | onUnusedExtend: 'warn' 26 | }), 27 | require('postcss-color-mod-function') 28 | ] 29 | } 30 | }), 31 | 32 | require('postcss-browser-reporter')(), 33 | require('postcss-reporter')() 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/gatsby-plugin-meta-redirect/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { exists, writeFile, ensureDir } = require('fs-extra') 3 | 4 | const getMetaRedirect = require('./getMetaRedirect') 5 | 6 | async function writeRedirectsFile(redirects, folder, pathPrefix) { 7 | if (!redirects.length) { 8 | return 9 | } 10 | 11 | for (const redirect of redirects) { 12 | const { fromPath, toPath } = redirect 13 | 14 | const FILE_PATH = path.join( 15 | folder, 16 | fromPath.replace(pathPrefix, ''), 17 | 'index.html' 18 | ) 19 | 20 | const fileExists = await exists(FILE_PATH) 21 | if (!fileExists) { 22 | try { 23 | await ensureDir(path.dirname(FILE_PATH)) 24 | } catch (err) { 25 | // ignore if the directory already exists; 26 | } 27 | 28 | const data = getMetaRedirect(toPath) 29 | await writeFile(FILE_PATH, data) 30 | } 31 | } 32 | } 33 | 34 | exports.onPostBuild = ({ store }) => { 35 | const { redirects, program, config } = store.getState() 36 | 37 | let pathPrefix = '' 38 | if (program.prefixPaths) { 39 | pathPrefix = config.pathPrefix 40 | } 41 | 42 | const folder = path.join(program.directory, 'public') 43 | return writeRedirectsFile(redirects, folder, pathPrefix) 44 | } 45 | -------------------------------------------------------------------------------- /src/web-comments/comps/CommentsAreaSummary.module.css: -------------------------------------------------------------------------------- 1 | /* Root component */ 2 | .root { 3 | @extend %ms-font-size-1; 4 | color: var(--brand-a); 5 | padding: 24px 0; 6 | white-space: nowrap; 7 | cursor: pointer; 8 | } 9 | 10 | .root:hover, 11 | .root:focus { 12 | &, 13 | & > .suffix { 14 | color: var(--brand-a7); 15 | } 16 | 17 | & > .fauxlink { 18 | border-bottom: solid 1px var(--brand-a7); 19 | } 20 | } 21 | 22 | .count { 23 | font-weight: bold; 24 | } 25 | 26 | .count::before { 27 | content: ''; 28 | vertical-align: middle; 29 | color: var(-bbrand-a); 30 | margin: 0 8px 0 0; 31 | } 32 | 33 | .suffix { 34 | color: $text-mute; 35 | } 36 | 37 | .fauxlink { 38 | margin-left: 4px; 39 | border-bottom: solid 1px color-mod(var(--brand-a) alpha(25%)); 40 | } 41 | 42 | /* Icon */ 43 | .icon svg { 44 | width: 24px; 45 | height: 24px; 46 | } 47 | 48 | .icon :global(.clr-i-outline) { 49 | fill: var(--brand-a); 50 | } 51 | 52 | /* Icon on non-hover */ 53 | .icon.isLine { 54 | display: inline; 55 | } 56 | 57 | .icon.isSolid { 58 | display: none; 59 | margin-right: -4px; 60 | } 61 | 62 | /* Icon on hover */ 63 | .root:hover, 64 | .root:focus { 65 | & .icon.isSolid { 66 | display: inline; 67 | } 68 | 69 | & .icon.isLine { 70 | display: none; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/web-comments/comps/DisqusDiscussion.tsx: -------------------------------------------------------------------------------- 1 | import { graphql, useStaticQuery } from 'gatsby' 2 | import React from 'react' 3 | import { DisqusData } from '../../types/types' 4 | import DisqusScript from './DisqusScript' 5 | import { RenderProps } from './DisqusScript' 6 | 7 | export const DisqusDiscussion = () => { 8 | const { 9 | site: { 10 | siteMetadata: { disqus } 11 | } 12 | } = useStaticQuery(QUERY) 13 | 14 | // TODO: get this somehow, maybe through a context? 15 | const prefix = 'https://devhints.io/' 16 | const identifier = 'react' 17 | 18 | // Disqus configuration 19 | const disqusProps: DisqusData = { 20 | host: disqus.host, // 'devhints.disqus.com', 21 | url: prefix + identifier, 22 | identifier 23 | } 24 | 25 | return ( 26 | 27 | {({ thread, count }: RenderProps) => { 28 | return ( 29 | 30 | {count} 31 | {thread} 32 | 33 | ) 34 | }} 35 | 36 | ) 37 | } 38 | 39 | const QUERY = graphql` 40 | { 41 | site { 42 | siteMetadata { 43 | disqus { 44 | enabled 45 | host 46 | } 47 | } 48 | } 49 | } 50 | ` 51 | export default DisqusDiscussion 52 | -------------------------------------------------------------------------------- /src/web-post-content/Elements.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames' 2 | import React from 'react' 3 | import redent from 'redent' 4 | 5 | export const PreCode = (props: { 6 | lang?: string 7 | children: string 8 | className?: string 9 | }) => { 10 | const { lang, children, className } = props 11 | return ( 12 |
    13 |       
    14 |         {redent(children).trim()}
    15 |       
    16 |     
    17 | ) 18 | } 19 | 20 | export const H2Section = (props: { 21 | className?: string 22 | title: React.ReactNode 23 | children: React.ReactNode 24 | }) => { 25 | const { title, className, children } = props 26 | return ( 27 |
    28 |

    {title}

    29 |
    {children}
    30 |
    31 | ) 32 | } 33 | 34 | export const H3Section = (props: { 35 | className?: string 36 | title: React.ReactNode 37 | children: React.ReactNode 38 | }) => { 39 | const { title, className, children } = props 40 | return ( 41 |
    42 |

    {title}

    43 |
    {children}
    44 |
    45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/uipad/lib/FrameWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Frame from 'react-frame-component' 3 | import HarvestHeadStyles from './HarvestHeadStyles' 4 | import { useIFrameSize } from './useIFrameSize' 5 | 6 | /** 7 | * Wraps the `children` in an iframe. 8 | * Injects styles. 9 | * 10 | * See: 11 | * https://github.com/ryanseddon/react-frame-component 12 | */ 13 | 14 | const FrameWrapper = ({ children, className, style }: Props) => { 15 | const head = ( 16 | <> 17 | 18 | 27 | 28 | ) 29 | 30 | const frameSize = useIFrameSize({ children }) 31 | 32 | return ( 33 | frameSize.update()} 39 | data-testid='framewrapper-iframe' 40 | > 41 | {children} 42 | 43 | ) 44 | } 45 | 46 | interface Props { 47 | children: React.ReactNode 48 | className?: string 49 | style?: React.CSSProperties 50 | } 51 | 52 | export default FrameWrapper 53 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | @echo 3 | @echo "Makefile targets:" 4 | @grep -E '^[a-zA-Z_-].*?: .*?## .*$$' Makefile | sed 's#\\:#:#g' | awk 'BEGIN {FS = ": .*?## "}; {printf "\033[36m %-20s\033[0m %s\n", $$1, $$2}' 5 | @echo 6 | 7 | run ?= docker-compose run --rm web 8 | 9 | yarn: ## Installs packages [alias: i] 10 | $(run) yarn 11 | 12 | start: ## Starts the server [alias: s] 13 | $(run) yarn start 14 | 15 | up: ## Starts the server as a daemon 16 | docker-compose up -d 17 | 18 | down: ## Stops the server 19 | docker-compose down 20 | 21 | bash: ## Runs a shell inside a Docker container [alias: sh] 22 | $(run) bash 23 | 24 | test: ## Jest tests 25 | $(run) yarn test 26 | 27 | tsc: ## Runs the TypeScript compiler 28 | $(run) yarn tsc 29 | 30 | tsc\:watch: ## Runs the TypeScript compiler (watch mode) [alias: t] 31 | $(run) yarn tsc --watch 32 | 33 | css_modules\:update: ## Update CSS modules [alias: c] 34 | $(run) yarn css_modules:update 35 | 36 | lint: ## Run tslint 37 | $(run) yarn lint 38 | 39 | fix: css_modules\:update ## Run tslint-fix and prettier-fix 40 | $(run) yarn run tslint --project . --fix 41 | $(run) yarn run prettier\:fix 42 | 43 | prettier\:check: ## Run prettier 44 | $(run) yarn prettier\:check 45 | 46 | # Aliases 47 | i: yarn 48 | s: start 49 | sh: bash 50 | c: css_modules\:update 51 | ci: test tsc prettier\:check lint 52 | t: tsc\:watch 53 | -------------------------------------------------------------------------------- /src/web-post-content/PostContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useEffect, useMemo, useRef } from 'react' 2 | import doPostTransform from './lib/doPostTransform' 3 | import transform from './lib/transform' 4 | 5 | interface Props { 6 | /** Markdown HAST syntax tree */ 7 | htmlAst?: any 8 | 9 | /** Class name of the
    */ 10 | className?: string 11 | 12 | children?: React.ReactNode 13 | } 14 | 15 | /** 16 | * Post content with transform magic. 17 | * @param {any} props.htmlAst The markdown AST to render. 18 | * @param {any} props.children Stuff to render. If given, htmlAst is ignored. 19 | * @param {any} props.className The classname of the parent div. 20 | */ 21 | 22 | const PostContent = memo((props: Props) => { 23 | const { htmlAst, className, children } = props 24 | const root = useRef(null) 25 | 26 | // Render HTML AST as React nodes 27 | const content = useMemo(() => { 28 | if (children) { 29 | return children 30 | } else if (htmlAst) { 31 | return transform(htmlAst) 32 | } 33 | }, [htmlAst, children]) 34 | 35 | // Perform syntax highlighting and Isotope'ing 36 | useEffect(() => { 37 | if (root.current) doPostTransform(root.current) 38 | }, []) 39 | 40 | return ( 41 |
    42 | {content} 43 |
    44 | ) 45 | }) 46 | 47 | export default PostContent 48 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/related-posts-section.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Section 3 | * has callout and group 4 | */ 5 | 6 | .related-posts-section { 7 | & { 8 | display: flex; 9 | } 10 | & { 11 | @extend %section-gutter-margin-left--1; 12 | } 13 | & { 14 | @extend %section-gutter-margin-right--1; 15 | } 16 | 17 | & > .callout, 18 | & > .group { 19 | margin: 0; 20 | & { 21 | @extend %section-gutter-margin-left; 22 | } 23 | & { 24 | @extend %section-gutter-margin-right; 25 | } 26 | } 27 | 28 | & > .callout { 29 | flex: 1 1 33%; 30 | } 31 | 32 | & > .group { 33 | flex: 1 1 50%; 34 | } 35 | 36 | & > .callout { 37 | display: flex; 38 | 39 | & > * { 40 | flex: 1 0 100%; 41 | } 42 | } 43 | 44 | /* Mobile */ 45 | @media (max-width: 480px) { 46 | & { 47 | flex-wrap: wrap; 48 | } 49 | 50 | & > .callout, 51 | & > .group { 52 | margin-top: 16px; 53 | margin-bottom: 16px; 54 | flex: 1 1 100%; 55 | } 56 | } 57 | 58 | /* Tablet */ 59 | @media (min-width: 481px) and (max-width: 768px) { 60 | & { 61 | flex-wrap: wrap; 62 | } 63 | 64 | & > .callout, 65 | & > .group { 66 | margin-top: 16px; 67 | margin-bottom: 16px; 68 | flex: 1 1 100%; 69 | } 70 | 71 | & > .group { 72 | flex: 1 1 40%; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/css-legacy-components/css/future/announcements-item.css: -------------------------------------------------------------------------------- 1 | .announcements-item { 2 | & { 3 | position: relative; 4 | padding: 16px; 5 | box-shadow: var(--shadow6); 6 | border-radius: 1px; 7 | background: white; 8 | padding-right: 48px; 9 | animation: announcements-item-flyin 500ms ease-out; 10 | transition: opacity 500ms linear, transform 500ms ease-out; 11 | } 12 | 13 | &.-hide { 14 | display: none; 15 | } 16 | 17 | & > .title { 18 | @extend %ms-font-size-1; 19 | font-weight: normal; 20 | color: var(--brand-a); 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | & > .body > p { 26 | margin: 0; 27 | padding: 0; 28 | 29 | & + p { 30 | margin-top: 1em; 31 | } 32 | } 33 | 34 | & > .close { 35 | position: absolute; 36 | right: 0; 37 | top: 0; 38 | width: 40px; 39 | height: 40px; 40 | line-height: 40px; 41 | text-align: center; 42 | border: 0; 43 | margin: 0; 44 | padding: 0; 45 | cursor: pointer; 46 | 47 | &:hover, 48 | &:focus { 49 | color: var(--brand-a); 50 | } 51 | } 52 | 53 | & > .close::before { 54 | content: '\00D7'; 55 | font-size: 14px; 56 | } 57 | } 58 | 59 | @keyframes announcements-item-flyin { 60 | 0% { 61 | transform: translate3d(0, 32px, 0); 62 | opacity: 0; 63 | } 64 | 65 | 100% { 66 | transform: translate3d(0, 0, 0); 67 | opacity: 1; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/web/components/PageActions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { github as githubIcon } from '../../web-icons' 3 | 4 | import cn from 'classnames' 5 | import useSiteContent from '../../gatsby-hooks/useSiteContent' 6 | import CSS from './PageActions.module.css' 7 | 8 | /* 9 | * Types 10 | */ 11 | 12 | export interface Props { 13 | // eg, '/vim' 14 | path?: string 15 | } 16 | 17 | export interface ViewProps extends Props { 18 | labels: { 19 | edit: string // => 'Edit' 20 | editOnGithub: string // => 'Edit on GitHub' 21 | } 22 | editURL: string 23 | } 24 | 25 | /** 26 | * Connector 27 | */ 28 | 29 | export const PageActions = (props: Props) => { 30 | const repo = 'https://github.com/rstacruz/cheatsheets' 31 | const branch = 'master' 32 | const editURL = `${repo}/blob/${branch}${props.path || ''}.md` 33 | const content = useSiteContent() 34 | const { editOnGithub, edit } = content.topNav 35 | const { path } = props 36 | 37 | if (!path) return null 38 | 39 | return ( 40 | 51 | ) 52 | } 53 | 54 | export default PageActions 55 | -------------------------------------------------------------------------------- /src/web/components/PagesList.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | & { 3 | display: flex; 4 | flex-wrap: wrap; 5 | } 6 | 7 | & > .item { 8 | flex: 0 0 100%; 9 | } 10 | 11 | & > .item.article { 12 | flex: 0 0 50%; 13 | } 14 | 15 | @media (min-width: 581px) { 16 | & > .item.isTopSheet { 17 | flex: 0 0 25%; 18 | } 19 | } 20 | } 21 | 22 | /* Article */ 23 | 24 | .root > .article { 25 | text-decoration: none; 26 | display: block; 27 | white-space: nowrap; 28 | padding: 4px 0; 29 | 30 | &, 31 | &:visited { 32 | color: var(--text-mute3); 33 | } 34 | 35 | & > .info > .slug { 36 | color: var(--text-bold); 37 | } 38 | 39 | &:visited > .info > .slug { 40 | color: var(--text-body); 41 | } 42 | 43 | & > .info > .title::before { 44 | content: ''; 45 | margin: 0 4px; 46 | } 47 | 48 | & > .info > .title { 49 | opacity: 0; 50 | } 51 | 52 | @media (max-width: 768px) { 53 | & > .info > .title { 54 | display: none; 55 | } 56 | } 57 | 58 | &:hover, 59 | &:focus { 60 | & { 61 | color: var(--text-mute); 62 | } 63 | 64 | & > .info > .title { 65 | opacity: 1; 66 | color: var(--brand-a); 67 | } 68 | } 69 | } 70 | 71 | .root > .category { 72 | @extend %ms-font-size-1; 73 | border-bottom: solid 1px var(--dark-line-color); 74 | margin: 16px 0; 75 | padding: 0 0 16px 0; 76 | font-weight: normal; 77 | color: var(--brand-a); 78 | } 79 | -------------------------------------------------------------------------------- /src/web-post-content/lib/isotopify.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Lays out each h3-section using Isotope. 3 | */ 4 | 5 | export default function isotopify(el: HTMLElement | null | void) { 6 | if (!el || !el.children) return 7 | 8 | // If we're running on the server, don't bother with this 9 | if (typeof window === 'undefined') return 10 | 11 | // There's a wrapping
    from renderAst, meh 12 | const div = el.children[0] 13 | if (!div) return 14 | 15 | // isotope()'ify all lists 16 | const lists = div.querySelectorAll('.h3-section-list') 17 | Array.from(lists).forEach(isotopifyItem) 18 | } 19 | 20 | /** 21 | * Applies an Isotope layout to the given HTML element `el`'s H3 sections. 22 | */ 23 | 24 | function isotopifyItem(el: HTMLElement) { 25 | // Load this lazily, so that it doesn't happen on the server 26 | const Isotope = require('isotope-layout/dist/isotope.pkgd.js') 27 | 28 | const iso = new Isotope(el, { 29 | itemSelector: '.h3-section', 30 | transitionDuration: 0 31 | }) 32 | 33 | const images = el.querySelectorAll('img') 34 | 35 | Array.from(images).forEach(image => { 36 | image.addEventListener('load', () => { 37 | iso.layout() 38 | }) 39 | }) 40 | 41 | // Insurance against weirdness on pages like devhints.io/vim, where the 42 | // critical path CSS may look different from the final CSS (because of the 43 | // tables). 44 | window.addEventListener('load', () => { 45 | iso.layout() 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/web-comments/CommentsArea.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { DisqusData } from '../types/types' 3 | import CSS from './CommentsArea.module.css' 4 | import CommentsAreaSummary from './comps/CommentsAreaSummary' 5 | import CommentsSection from './comps/CommentsSection' 6 | import DisqusScript, { RenderProps } from './comps/DisqusScript' 7 | 8 | interface ViewProps { 9 | thread: React.ReactNode 10 | count: React.ReactNode 11 | } 12 | 13 | /** 14 | * Comments area 15 | */ 16 | 17 | const CommentsAreaView = (props: ViewProps) => { 18 | const { count } = props 19 | 20 | return ( 21 |
    22 |
    23 |
    24 | 25 | 26 |
    27 |
    28 |
    29 | ) 30 | } 31 | 32 | /* 33 | * Connector 34 | */ 35 | 36 | const CommentsArea = () => { 37 | // Disqus configuration 38 | const disqus: DisqusData = { 39 | host: 'devhints.disqus.com', 40 | url: 'https://devhints.io/react', 41 | identifier: 'react' 42 | } 43 | 44 | return ( 45 | 46 | {({ thread, count }: RenderProps) => { 47 | return 48 | }} 49 | 50 | ) 51 | } 52 | 53 | /* 54 | * Export 55 | */ 56 | 57 | export default CommentsArea 58 | -------------------------------------------------------------------------------- /src/web/components/RelatedPostItem.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | text-align: left; 4 | line-height: 1.4; 5 | } 6 | 7 | /* Layout */ 8 | .link { 9 | flex: 1 1 100%; 10 | display: block; 11 | border-radius: 2px; 12 | box-shadow: var(--shadow2); 13 | padding: 16px; 14 | text-decoration: none; 15 | } 16 | 17 | /* Color */ 18 | .link, 19 | .link:visited { 20 | background: white; 21 | color: var(--text-mute); 22 | 23 | & > .title { 24 | color: var(--brand-a); 25 | } 26 | 27 | &:hover, 28 | &:focus { 29 | color: var(--brand-a); 30 | } 31 | 32 | &:hover > .title, 33 | &:focus > .title { 34 | /* TODO color: darken(var(--brand-a), 16%); */ 35 | } 36 | } 37 | 38 | .root.isPrimary > .link, 39 | .root.isPrimary > .link:visited { 40 | background: var(--brand-a); 41 | color: color-mod(white alpha(50%)); 42 | 43 | & > .title { 44 | color: white; 45 | } 46 | 47 | &:hover, 48 | &:focus { 49 | color: white; 50 | } 51 | 52 | &:hover > .title, 53 | &:focus > .title { 54 | color: white; 55 | } 56 | 57 | &:hover, 58 | &:focus { 59 | background: color-mod(var(--brand-a) lightness(-9%)); 60 | } 61 | } 62 | 63 | /* Two lines when bigger */ 64 | @media (min-width: 481px) { 65 | .link > .title, 66 | .link > .suffix { 67 | display: block; 68 | } 69 | 70 | .link > .title { 71 | font-size: calc(var(--ms-base-md) * var(--ms-ratio-md)); 72 | font-weight: normal; 73 | } 74 | 75 | .link > .suffix { 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/web/components/RootPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import useSiteContent from '../../gatsby-hooks/useSiteContent' 4 | import CommonHead from '../../gatsby-shell/comps/CommonHead' 5 | import { HomeMeta } from '../../gatsby-shell/Meta' 6 | import { GroupedSiteLinks, SiteLink } from '../../types/types' 7 | import { LiveSearchInput } from '../../web-search' 8 | import PagesList from './PagesList' 9 | import SiteHeader from './SiteHeader' 10 | import TopNav from './TopNav' 11 | 12 | export interface Props { 13 | recentlyUpdated: SiteLink[] 14 | groups: GroupedSiteLinks 15 | } 16 | 17 | /** 18 | * The home page. 19 | * 20 | * @example 21 | * 25 | */ 26 | 27 | export const RootPage = (props: Props) => { 28 | const content = useSiteContent() 29 | const recentlyUpdatedLabel = content.home.recentlyUpdated 30 | const { groups, recentlyUpdated } = props 31 | 32 | return ( 33 |
    34 | 35 | 36 | 37 |
    38 | 39 | 40 | 41 | 42 | 43 | 44 | {Object.keys(groups).map((group: string) => ( 45 | 46 | ))} 47 |
    48 |
    49 | ) 50 | } 51 | 52 | export default RootPage 53 | -------------------------------------------------------------------------------- /src/uipad/lib/useIFrameSize.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export interface IFrame { 4 | node: HTMLIFrameElement 5 | } 6 | 7 | /** 8 | * React hook to dynamically adjust iframe size. 9 | * Returns the following things: 10 | * 11 | * - `ref` - set this ref to the `