mirror of
https://github.com/onkelbeh/cheatsheets.git
synced 2025-06-15 14:47:53 +02:00
Major rewrite (!) (#2130)
This commit is contained in:
parent
bf059536c6
commit
44bdd413fb
11
.babelrc
11
.babelrc
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
"useBuiltIns": "entry",
|
|
||||||
"targets": "> 2%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
node_modules
|
|
37
.eslintrc.cjs
Normal file
37
.eslintrc.cjs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:astro/recommended',
|
||||||
|
'prettier'
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
browser: true // enables window, document, etc
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
root: true,
|
||||||
|
ignorePatterns: ['dist/**'],
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.test.ts'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.astro'],
|
||||||
|
parser: 'astro-eslint-parser',
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extraFileExtensions: ['.astro']
|
||||||
|
}
|
||||||
|
// rules: {
|
||||||
|
// override/add rules settings here, such as:
|
||||||
|
// "astro/no-set-html-directive": "error"
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +0,0 @@
|
|||||||
yarn.lock binary
|
|
Before Width: | Height: | Size: 504 KiB After Width: | Height: | Size: 504 KiB |
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@ -1,22 +1,35 @@
|
|||||||
name: Build and test
|
name: Run tests
|
||||||
on: [push, pull_request]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ruby/setup-ruby@v1
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
|
|
||||||
- name: Use Ruby
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
with:
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: .node-version
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
with: { run_install: false }
|
||||||
|
|
||||||
- run: yarn install --frozen-lockfile
|
# https://github.com/pnpm/action-setup?tab=readme-ov-file#use-cache-to-reduce-installation-time
|
||||||
- run: yarn build
|
- name: Get pnpm store directory
|
||||||
- run: yarn test
|
shell: bash
|
||||||
- run: yarn test:smoke
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
name: Setup pnpm cache
|
||||||
|
with:
|
||||||
|
path: ${{ env.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
- name: Install playwright browsers
|
||||||
|
run: pnpm playwright install --with-deps chromium
|
||||||
|
- name: Run tests
|
||||||
|
run: pnpm run ci
|
||||||
|
35
.gitignore
vendored
35
.gitignore
vendored
@ -1,11 +1,26 @@
|
|||||||
_output
|
# build output
|
||||||
_site
|
dist/
|
||||||
.jekyll-metadata
|
# generated types
|
||||||
/node_modules
|
.astro/
|
||||||
/vendor
|
|
||||||
.idea/
|
|
||||||
.cache/
|
|
||||||
|
|
||||||
# Generated by 'yarn dev'
|
# dependencies
|
||||||
/_includes/2017/critical/*
|
node_modules/
|
||||||
/assets/packed/*
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# playwright
|
||||||
|
test-results
|
||||||
|
15
.gitpod.yml
15
.gitpod.yml
@ -1,15 +0,0 @@
|
|||||||
image: gitpod/workspace-full
|
|
||||||
|
|
||||||
ports:
|
|
||||||
- port: 4001
|
|
||||||
onOpen: open-preview
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- init: yarn install && bundle install
|
|
||||||
command: env PORT=4001 yarn run dev
|
|
||||||
|
|
||||||
github:
|
|
||||||
# Prebuild the docker image for gitpod - https://www.gitpod.io/docs/prebuilds/
|
|
||||||
prebuilds:
|
|
||||||
# enable for the master/default branch
|
|
||||||
master: true
|
|
@ -1 +1 @@
|
|||||||
18.19.1
|
20.11.1
|
6
.prettierignore
Normal file
6
.prettierignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
src/sass/vendor
|
||||||
|
vendor
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
*.md
|
||||||
|
pnpm-lock.yaml
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"jsxSingleQuote": true,
|
"trailingComma": "none",
|
||||||
"trailingComma": "none"
|
"plugins": ["prettier-plugin-astro"]
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"*.md": {
|
|
||||||
"type": "cheat",
|
|
||||||
"template": [
|
|
||||||
"---",
|
|
||||||
"title: {basename|capitalize}",
|
|
||||||
"category: Ruby",
|
|
||||||
"layout: 2017/sheet",
|
|
||||||
"updated: DATE",
|
|
||||||
"---"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["astro-build.astro-vscode"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "./node_modules/.bin/astro dev",
|
||||||
|
"name": "Development server",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
8
.vscode/markdown.code-snippets
vendored
Normal file
8
.vscode/markdown.code-snippets
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Test snip": {
|
||||||
|
"prefix": ["about"],
|
||||||
|
"body": "Copyright. Foo Corp 2028",
|
||||||
|
"description": "Adds copyright...",
|
||||||
|
"scope": "markdown"
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ Or using a button:<br>
|
|||||||
|
|
||||||
To preview the website you need to first build it then you can navigate to file that you are trying to contribute and preview directly.
|
To preview the website you need to first build it then you can navigate to file that you are trying to contribute and preview directly.
|
||||||
|
|
||||||
<img src='_docs/images/gitpod_preview_tut.png' width=828 height=459/>
|
<img src='.github/images/gitpod_preview_tut.png' width=828 height=459/>
|
||||||
|
|
||||||
## Starting a local instance
|
## Starting a local instance
|
||||||
|
|
||||||
|
11
Dockerfile
11
Dockerfile
@ -1,11 +0,0 @@
|
|||||||
FROM ruby:2.7.1
|
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
|
|
||||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
|
||||||
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
|
|
||||||
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
|
|
||||||
nodejs \
|
|
||||||
yarn \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
RUN mkdir -p /app
|
|
||||||
WORKDIR /app
|
|
9
Gemfile
9
Gemfile
@ -1,5 +1,4 @@
|
|||||||
source 'https://rubygems.org'
|
source "https://rubygems.org"
|
||||||
gem 'webrick'
|
gem "minitest"
|
||||||
gem 'github-pages', group: :jekyll_plugins
|
gem "kramdown"
|
||||||
gem 'json'
|
gem "kramdown-parser-gfm"
|
||||||
gem 'csv'
|
|
||||||
|
288
Gemfile.lock
288
Gemfile.lock
@ -1,293 +1,21 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (7.1.3)
|
kramdown (2.4.0)
|
||||||
base64
|
|
||||||
bigdecimal
|
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
||||||
connection_pool (>= 2.2.5)
|
|
||||||
drb
|
|
||||||
i18n (>= 1.6, < 2)
|
|
||||||
minitest (>= 5.1)
|
|
||||||
mutex_m
|
|
||||||
tzinfo (~> 2.0)
|
|
||||||
addressable (2.8.6)
|
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
|
||||||
base64 (0.2.0)
|
|
||||||
bigdecimal (3.1.6)
|
|
||||||
coffee-script (2.4.1)
|
|
||||||
coffee-script-source
|
|
||||||
execjs
|
|
||||||
coffee-script-source (1.11.1)
|
|
||||||
colorator (1.1.0)
|
|
||||||
commonmarker (0.23.10)
|
|
||||||
concurrent-ruby (1.2.3)
|
|
||||||
connection_pool (2.4.1)
|
|
||||||
csv (3.2.8)
|
|
||||||
dnsruby (1.70.0)
|
|
||||||
simpleidn (~> 0.2.1)
|
|
||||||
drb (2.2.0)
|
|
||||||
ruby2_keywords
|
|
||||||
em-websocket (0.5.3)
|
|
||||||
eventmachine (>= 0.12.9)
|
|
||||||
http_parser.rb (~> 0)
|
|
||||||
ethon (0.16.0)
|
|
||||||
ffi (>= 1.15.0)
|
|
||||||
eventmachine (1.2.7)
|
|
||||||
execjs (2.9.1)
|
|
||||||
faraday (2.9.0)
|
|
||||||
faraday-net_http (>= 2.0, < 3.2)
|
|
||||||
faraday-net_http (3.1.0)
|
|
||||||
net-http
|
|
||||||
ffi (1.16.3)
|
|
||||||
forwardable-extended (2.6.0)
|
|
||||||
gemoji (3.0.1)
|
|
||||||
github-pages (228)
|
|
||||||
github-pages-health-check (= 1.17.9)
|
|
||||||
jekyll (= 3.9.3)
|
|
||||||
jekyll-avatar (= 0.7.0)
|
|
||||||
jekyll-coffeescript (= 1.1.1)
|
|
||||||
jekyll-commonmark-ghpages (= 0.4.0)
|
|
||||||
jekyll-default-layout (= 0.1.4)
|
|
||||||
jekyll-feed (= 0.15.1)
|
|
||||||
jekyll-gist (= 1.5.0)
|
|
||||||
jekyll-github-metadata (= 2.13.0)
|
|
||||||
jekyll-include-cache (= 0.2.1)
|
|
||||||
jekyll-mentions (= 1.6.0)
|
|
||||||
jekyll-optional-front-matter (= 0.3.2)
|
|
||||||
jekyll-paginate (= 1.1.0)
|
|
||||||
jekyll-readme-index (= 0.3.0)
|
|
||||||
jekyll-redirect-from (= 0.16.0)
|
|
||||||
jekyll-relative-links (= 0.6.1)
|
|
||||||
jekyll-remote-theme (= 0.4.3)
|
|
||||||
jekyll-sass-converter (= 1.5.2)
|
|
||||||
jekyll-seo-tag (= 2.8.0)
|
|
||||||
jekyll-sitemap (= 1.4.0)
|
|
||||||
jekyll-swiss (= 1.0.0)
|
|
||||||
jekyll-theme-architect (= 0.2.0)
|
|
||||||
jekyll-theme-cayman (= 0.2.0)
|
|
||||||
jekyll-theme-dinky (= 0.2.0)
|
|
||||||
jekyll-theme-hacker (= 0.2.0)
|
|
||||||
jekyll-theme-leap-day (= 0.2.0)
|
|
||||||
jekyll-theme-merlot (= 0.2.0)
|
|
||||||
jekyll-theme-midnight (= 0.2.0)
|
|
||||||
jekyll-theme-minimal (= 0.2.0)
|
|
||||||
jekyll-theme-modernist (= 0.2.0)
|
|
||||||
jekyll-theme-primer (= 0.6.0)
|
|
||||||
jekyll-theme-slate (= 0.2.0)
|
|
||||||
jekyll-theme-tactile (= 0.2.0)
|
|
||||||
jekyll-theme-time-machine (= 0.2.0)
|
|
||||||
jekyll-titles-from-headings (= 0.5.3)
|
|
||||||
jemoji (= 0.12.0)
|
|
||||||
kramdown (= 2.3.2)
|
|
||||||
kramdown-parser-gfm (= 1.1.0)
|
|
||||||
liquid (= 4.0.4)
|
|
||||||
mercenary (~> 0.3)
|
|
||||||
minima (= 2.5.1)
|
|
||||||
nokogiri (>= 1.13.6, < 2.0)
|
|
||||||
rouge (= 3.26.0)
|
|
||||||
terminal-table (~> 1.4)
|
|
||||||
github-pages-health-check (1.17.9)
|
|
||||||
addressable (~> 2.3)
|
|
||||||
dnsruby (~> 1.60)
|
|
||||||
octokit (~> 4.0)
|
|
||||||
public_suffix (>= 3.0, < 5.0)
|
|
||||||
typhoeus (~> 1.3)
|
|
||||||
html-pipeline (2.14.3)
|
|
||||||
activesupport (>= 2)
|
|
||||||
nokogiri (>= 1.4)
|
|
||||||
http_parser.rb (0.8.0)
|
|
||||||
i18n (1.14.1)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
jekyll (3.9.3)
|
|
||||||
addressable (~> 2.4)
|
|
||||||
colorator (~> 1.0)
|
|
||||||
em-websocket (~> 0.5)
|
|
||||||
i18n (>= 0.7, < 2)
|
|
||||||
jekyll-sass-converter (~> 1.0)
|
|
||||||
jekyll-watch (~> 2.0)
|
|
||||||
kramdown (>= 1.17, < 3)
|
|
||||||
liquid (~> 4.0)
|
|
||||||
mercenary (~> 0.3.3)
|
|
||||||
pathutil (~> 0.9)
|
|
||||||
rouge (>= 1.7, < 4)
|
|
||||||
safe_yaml (~> 1.0)
|
|
||||||
jekyll-avatar (0.7.0)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
jekyll-coffeescript (1.1.1)
|
|
||||||
coffee-script (~> 2.2)
|
|
||||||
coffee-script-source (~> 1.11.1)
|
|
||||||
jekyll-commonmark (1.4.0)
|
|
||||||
commonmarker (~> 0.22)
|
|
||||||
jekyll-commonmark-ghpages (0.4.0)
|
|
||||||
commonmarker (~> 0.23.7)
|
|
||||||
jekyll (~> 3.9.0)
|
|
||||||
jekyll-commonmark (~> 1.4.0)
|
|
||||||
rouge (>= 2.0, < 5.0)
|
|
||||||
jekyll-default-layout (0.1.4)
|
|
||||||
jekyll (~> 3.0)
|
|
||||||
jekyll-feed (0.15.1)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-gist (1.5.0)
|
|
||||||
octokit (~> 4.2)
|
|
||||||
jekyll-github-metadata (2.13.0)
|
|
||||||
jekyll (>= 3.4, < 5.0)
|
|
||||||
octokit (~> 4.0, != 4.4.0)
|
|
||||||
jekyll-include-cache (0.2.1)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-mentions (1.6.0)
|
|
||||||
html-pipeline (~> 2.3)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-optional-front-matter (0.3.2)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
jekyll-paginate (1.1.0)
|
|
||||||
jekyll-readme-index (0.3.0)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
jekyll-redirect-from (0.16.0)
|
|
||||||
jekyll (>= 3.3, < 5.0)
|
|
||||||
jekyll-relative-links (0.6.1)
|
|
||||||
jekyll (>= 3.3, < 5.0)
|
|
||||||
jekyll-remote-theme (0.4.3)
|
|
||||||
addressable (~> 2.0)
|
|
||||||
jekyll (>= 3.5, < 5.0)
|
|
||||||
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
|
||||||
rubyzip (>= 1.3.0, < 3.0)
|
|
||||||
jekyll-sass-converter (1.5.2)
|
|
||||||
sass (~> 3.4)
|
|
||||||
jekyll-seo-tag (2.8.0)
|
|
||||||
jekyll (>= 3.8, < 5.0)
|
|
||||||
jekyll-sitemap (1.4.0)
|
|
||||||
jekyll (>= 3.7, < 5.0)
|
|
||||||
jekyll-swiss (1.0.0)
|
|
||||||
jekyll-theme-architect (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-cayman (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-dinky (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-hacker (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-leap-day (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-merlot (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-midnight (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-minimal (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-modernist (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-primer (0.6.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-github-metadata (~> 2.9)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-slate (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-tactile (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-theme-time-machine (0.2.0)
|
|
||||||
jekyll (> 3.5, < 5.0)
|
|
||||||
jekyll-seo-tag (~> 2.0)
|
|
||||||
jekyll-titles-from-headings (0.5.3)
|
|
||||||
jekyll (>= 3.3, < 5.0)
|
|
||||||
jekyll-watch (2.2.1)
|
|
||||||
listen (~> 3.0)
|
|
||||||
jemoji (0.12.0)
|
|
||||||
gemoji (~> 3.0)
|
|
||||||
html-pipeline (~> 2.2)
|
|
||||||
jekyll (>= 3.0, < 5.0)
|
|
||||||
json (2.7.1)
|
|
||||||
kramdown (2.3.2)
|
|
||||||
rexml
|
rexml
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (~> 2.0)
|
||||||
liquid (4.0.4)
|
minitest (5.18.0)
|
||||||
listen (3.8.0)
|
rexml (3.2.5)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
|
||||||
mercenary (0.3.6)
|
|
||||||
minima (2.5.1)
|
|
||||||
jekyll (>= 3.5, < 5.0)
|
|
||||||
jekyll-feed (~> 0.9)
|
|
||||||
jekyll-seo-tag (~> 2.1)
|
|
||||||
minitest (5.21.2)
|
|
||||||
mutex_m (0.2.0)
|
|
||||||
net-http (0.4.1)
|
|
||||||
uri
|
|
||||||
nokogiri (1.16.1-aarch64-linux)
|
|
||||||
racc (~> 1.4)
|
|
||||||
nokogiri (1.16.1-arm-linux)
|
|
||||||
racc (~> 1.4)
|
|
||||||
nokogiri (1.16.1-arm64-darwin)
|
|
||||||
racc (~> 1.4)
|
|
||||||
nokogiri (1.16.1-x86-linux)
|
|
||||||
racc (~> 1.4)
|
|
||||||
nokogiri (1.16.1-x86_64-darwin)
|
|
||||||
racc (~> 1.4)
|
|
||||||
nokogiri (1.16.1-x86_64-linux)
|
|
||||||
racc (~> 1.4)
|
|
||||||
octokit (4.25.1)
|
|
||||||
faraday (>= 1, < 3)
|
|
||||||
sawyer (~> 0.9)
|
|
||||||
pathutil (0.16.2)
|
|
||||||
forwardable-extended (~> 2.6)
|
|
||||||
public_suffix (4.0.7)
|
|
||||||
racc (1.7.3)
|
|
||||||
rb-fsevent (0.11.2)
|
|
||||||
rb-inotify (0.10.1)
|
|
||||||
ffi (~> 1.0)
|
|
||||||
rexml (3.2.6)
|
|
||||||
rouge (3.26.0)
|
|
||||||
ruby2_keywords (0.0.5)
|
|
||||||
rubyzip (2.3.2)
|
|
||||||
safe_yaml (1.0.5)
|
|
||||||
sass (3.7.4)
|
|
||||||
sass-listen (~> 4.0.0)
|
|
||||||
sass-listen (4.0.0)
|
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
|
||||||
sawyer (0.9.2)
|
|
||||||
addressable (>= 2.3.5)
|
|
||||||
faraday (>= 0.17.3, < 3)
|
|
||||||
simpleidn (0.2.1)
|
|
||||||
unf (~> 0.1.4)
|
|
||||||
terminal-table (1.8.0)
|
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
|
||||||
typhoeus (1.4.1)
|
|
||||||
ethon (>= 0.9.0)
|
|
||||||
tzinfo (2.0.6)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
unf (0.1.4)
|
|
||||||
unf_ext
|
|
||||||
unf_ext (0.0.9.1)
|
|
||||||
unicode-display_width (1.8.0)
|
|
||||||
uri (0.13.0)
|
|
||||||
webrick (1.8.1)
|
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux-android
|
||||||
arm-linux
|
|
||||||
arm64-darwin
|
|
||||||
x86-linux
|
|
||||||
x86_64-darwin
|
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
csv
|
kramdown
|
||||||
github-pages
|
kramdown-parser-gfm
|
||||||
json
|
minitest
|
||||||
webrick
|
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.3
|
2.4.1
|
||||||
|
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021 Rico Sta. Cruz and contributors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
19
Makefile
19
Makefile
@ -1,19 +0,0 @@
|
|||||||
npmbin := ./node_modules/.bin
|
|
||||||
PORT ?= 3000
|
|
||||||
HOST ?= 127.0.0.1
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo
|
|
||||||
@echo Makefile targets
|
|
||||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
|
||||||
@echo
|
|
||||||
|
|
||||||
# Builds intermediate files. Needs a _site built first though
|
|
||||||
update: _site
|
|
||||||
|
|
||||||
# Builds _site
|
|
||||||
_site:
|
|
||||||
yarn build
|
|
||||||
|
|
||||||
dev:
|
|
||||||
yarn dev
|
|
@ -4,16 +4,10 @@
|
|||||||
TL;DR for developer documentation - a ridiculous collection of cheatsheets
|
TL;DR for developer documentation - a ridiculous collection of cheatsheets
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
<p align='center'>
|
|
||||||
<a href='https://travis-ci.org/rstacruz/cheatsheets'><img src='https://travis-ci.org/rstacruz/cheatsheets.svg?branch=master' alt='See test builds'></a>
|
|
||||||
<a href='https://github.com/rstacruz/cheatsheets/actions?query=workflow%3ADeploy'><img src='https://github.com/rstacruz/cheatsheets/workflows/Deploy/badge.svg' alt='GitHub pages deploy status'></a>
|
|
||||||
<a href='https://app.netlify.com/sites/devhints-cheatsheets/deploys'><img src='https://api.netlify.com/api/v1/badges/c66b2a8b-5147-4243-9bf6-e2143126f6c8/deploy-status' alt='Netlify deploy status'></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<p align='center'>
|
<p align='center'>
|
||||||
<a href='https://devhints.io/'><img src='_docs/images/screenshot.png' width=600></a>
|
<a href='https://devhints.io/'><img src='.github/images/screenshot.png' width=600></a>
|
||||||
<br>
|
<br>
|
||||||
✨ <b><a href='https://devhints.io/'>devhints.io</a></b> ✨
|
✨ <b><a href='https://devhints.io/'>devhints.io</a></b> ✨
|
||||||
</p>
|
</p>
|
||||||
|
67
_config.yml
67
_config.yml
@ -1,67 +0,0 @@
|
|||||||
# Jekyll configuration
|
|
||||||
|
|
||||||
whitelist:
|
|
||||||
- jekyll-redirect-from
|
|
||||||
- jekyll-github-metadata
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- jekyll-redirect-from
|
|
||||||
- jekyll-github-metadata
|
|
||||||
|
|
||||||
exclude:
|
|
||||||
- .babelrc
|
|
||||||
- .cache
|
|
||||||
- CNAME
|
|
||||||
- CONTRIBUTING.md
|
|
||||||
- cssnano.config.js
|
|
||||||
- docker_compose.yml
|
|
||||||
- Dockerfile
|
|
||||||
- Gemfile
|
|
||||||
- Gemfile.lock
|
|
||||||
- Makefile
|
|
||||||
- node_modules
|
|
||||||
- package.json
|
|
||||||
- package-lock.json
|
|
||||||
- postcss.config.js
|
|
||||||
- README.md
|
|
||||||
- vendor
|
|
||||||
- webpack.config.js
|
|
||||||
- yarn-error.log
|
|
||||||
- yarn.lock
|
|
||||||
|
|
||||||
# Markdown
|
|
||||||
|
|
||||||
highlighter: false
|
|
||||||
markdown: kramdown
|
|
||||||
kramdown:
|
|
||||||
input: GFM
|
|
||||||
hard_wrap: false
|
|
||||||
parse_block_html: true
|
|
||||||
syntax_highlighter_opts:
|
|
||||||
disable: true
|
|
||||||
|
|
||||||
# Defaults
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
- scope:
|
|
||||||
path: ''
|
|
||||||
type: pages
|
|
||||||
values:
|
|
||||||
layout: 'default'
|
|
||||||
og_type: article
|
|
||||||
type: article
|
|
||||||
category: 'Others'
|
|
||||||
excerpt_separator: '<!--more-->'
|
|
||||||
prism_languages: []
|
|
||||||
|
|
||||||
# Site info
|
|
||||||
|
|
||||||
url: https://devhints.io
|
|
||||||
title: Devhints.io cheatsheets
|
|
||||||
|
|
||||||
# GitHub metadata
|
|
||||||
# https://help.github.com/articles/repository-metadata-on-github-pages/
|
|
||||||
|
|
||||||
repository: rstacruz/cheatsheets
|
|
||||||
include:
|
|
||||||
- _redirects
|
|
@ -1,2 +0,0 @@
|
|||||||
# No trailing slash
|
|
||||||
preview_host: https://assets.devhints.io/previews
|
|
@ -1,2 +0,0 @@
|
|||||||
enabled: true
|
|
||||||
src: https://cdn.carbonads.com/carbon.js?serve=CE7IK5QM&placement=devhintsio
|
|
@ -1,30 +0,0 @@
|
|||||||
enabled: true
|
|
||||||
names:
|
|
||||||
- Analytics
|
|
||||||
- Ansible
|
|
||||||
- Apps
|
|
||||||
- C-like
|
|
||||||
- CLI
|
|
||||||
- CSS
|
|
||||||
- Databases
|
|
||||||
- Devops
|
|
||||||
- Elixir
|
|
||||||
- Git
|
|
||||||
- HTML
|
|
||||||
- Java & JVM
|
|
||||||
- JavaScript
|
|
||||||
- JavaScript libraries
|
|
||||||
- Jekyll
|
|
||||||
- Ledger
|
|
||||||
- Markup
|
|
||||||
- macOS
|
|
||||||
- Node.js
|
|
||||||
- PHP
|
|
||||||
- Python
|
|
||||||
- Rails
|
|
||||||
- React
|
|
||||||
- Ruby
|
|
||||||
- Ruby libraries
|
|
||||||
- Vim
|
|
||||||
- Fitness
|
|
||||||
- Others
|
|
@ -1,2 +0,0 @@
|
|||||||
enabled: true
|
|
||||||
# token: "c2c8bc62-c275-4c7a-a304-74335c5a1cd0"
|
|
@ -1,51 +0,0 @@
|
|||||||
home:
|
|
||||||
title: "Rico's cheatsheets"
|
|
||||||
tagline: |
|
|
||||||
Hey! I'm <a href='https://ricostacruz.com'>@rstacruz</a> and this is a modest collection of cheatsheets I've written.
|
|
||||||
|
|
||||||
top_nav:
|
|
||||||
title: Devhints.io
|
|
||||||
edit: Edit
|
|
||||||
edit_on_github: Edit this page on GitHub
|
|
||||||
|
|
||||||
sheet:
|
|
||||||
suffix: cheatsheet
|
|
||||||
|
|
||||||
social_list:
|
|
||||||
default_description: 'Ridiculous collection of web development cheatsheets'
|
|
||||||
description: 'The ultimate cheatsheet for TITLE.'
|
|
||||||
facebook_share: Share on Facebook
|
|
||||||
twitter_share: Share on Twitter
|
|
||||||
|
|
||||||
related_posts_callout:
|
|
||||||
description: Over SIZE curated cheatsheets, by developers for developers.
|
|
||||||
link: Devhints home
|
|
||||||
|
|
||||||
related_posts_group:
|
|
||||||
top: Top cheatsheets
|
|
||||||
other: Other cheatsheets
|
|
||||||
category: Other CATEGORY cheatsheets
|
|
||||||
|
|
||||||
search_form:
|
|
||||||
default_placeholder: Search SIZE+ cheatsheets
|
|
||||||
home_placeholder: Search...
|
|
||||||
prefix: devhints.io
|
|
||||||
|
|
||||||
comments_area:
|
|
||||||
suffix: for this cheatsheet.
|
|
||||||
link: 'Write yours!'
|
|
||||||
|
|
||||||
not_found:
|
|
||||||
title: Not found
|
|
||||||
description: Sorry, we don't have a cheatsheet for this yet. Try searching!
|
|
||||||
home: Back to home
|
|
||||||
|
|
||||||
announcement:
|
|
||||||
# id: 2017-10-26-twitter
|
|
||||||
id: 2023-12-14
|
|
||||||
title: |
|
|
||||||
We're on Twitter ♥️
|
|
||||||
body: |
|
|
||||||
Follow [@devhints](https://twitter.com/devhints) on X/Twitter for daily "today I learned" snippets.
|
|
||||||
|
|
||||||
Also: I've started a new blog with some insights on web development. Have a look! [**ricostacruz.com/posts**](https://ricostacruz.com/posts?utm_source=devhints)
|
|
@ -1,2 +0,0 @@
|
|||||||
enabled: true
|
|
||||||
host: devhints.disqus.com
|
|
@ -1,4 +0,0 @@
|
|||||||
enabled: true
|
|
||||||
hostname: devhints.io
|
|
||||||
id: "G-N7TC6B227L"
|
|
||||||
# id: "UA-106902774-1"
|
|
@ -1,10 +0,0 @@
|
|||||||
{% if site.data.content.announcement %}
|
|
||||||
<div class='announcements-list'>
|
|
||||||
<div class='announcements-item item -hide' data-js-dismissable='{"id":"{{ site.data.content.announcement.id }}"}'>
|
|
||||||
<h3 class='title'>{{ site.data.content.announcement.title }}</h3>
|
|
||||||
<div class='body'>{{ site.data.content.announcement.body | markdownify }}</div>
|
|
||||||
<button data-js-dismiss class='close'></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
|||||||
<script type='application/ld+json'>
|
|
||||||
{
|
|
||||||
"@context": "http://schema.org",
|
|
||||||
"@type": "NewsArticle",
|
|
||||||
"mainEntityOfPage": {
|
|
||||||
"@type": "WebPage",
|
|
||||||
"@id": "https://google.com/article"
|
|
||||||
},
|
|
||||||
"headline": {{ meta_title | jsonify }},
|
|
||||||
"image": [ {{ meta_image | jsonify }} ],
|
|
||||||
"description": {{ meta_description | jsonify }}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script type='application/ld+json'>
|
|
||||||
{
|
|
||||||
"@context": "http://schema.org",
|
|
||||||
"@type": "BreadcrumbList",
|
|
||||||
"itemListElement": [{
|
|
||||||
"@type": "ListItem",
|
|
||||||
"position": 1,
|
|
||||||
"item": {
|
|
||||||
"@id": "{{ site.url }}/#{{ page.category | downcase | replace: ' ', '-' }}",
|
|
||||||
"name": "{{ page.category }}"
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
"@type": "ListItem",
|
|
||||||
"position": 2,
|
|
||||||
"item": {
|
|
||||||
"@id": {{ page_url | jsonify }},
|
|
||||||
"name": {{ page.title | jsonify }}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,22 +0,0 @@
|
|||||||
{% assign identifier = include.page.url | remove: '.html' | remove_first: '/' %}
|
|
||||||
{% if site.data.disqus.enabled %}
|
|
||||||
<section class='comments-area' id='comments' data-js-no-preview>
|
|
||||||
<div class='container'>
|
|
||||||
<details class='comments-details'>
|
|
||||||
<summary>
|
|
||||||
<strong class='count'>
|
|
||||||
<span class='disqus-comment-count' data-disqus-identifier="{{ identifier }}" data-disqus-url='{{ site.url }}/{{ identifier }}'>0 Comments</span>
|
|
||||||
</strong>
|
|
||||||
<span class='suffix'>{{ site.data.content.comments_area.suffix }}</span>
|
|
||||||
<span class='fauxlink'>{{ site.data.content.comments_area.link }}</span>
|
|
||||||
</summary>
|
|
||||||
<div class='comments-section'>
|
|
||||||
<div class='comments'>
|
|
||||||
<div id='disqus_thread'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
<noscript data-js-disqus='{"host":"{{ site.data.disqus.host }}","url":"{{ site.url }}/{{ identifier }}","identifier":"{{ identifier }}"}'></noscript>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
@ -1,3 +0,0 @@
|
|||||||
<script>{% include 2017/critical/critical.js %}</script>
|
|
||||||
<script src='{{base}}/assets/packed/app.js?t={{ timestamp }}'></script>
|
|
||||||
{% for lang in page.prism_languages %}<script src='https://cdn.jsdelivr.net/npm/prismjs@1.6.0/components/prism-{{lang}}.min.js'></script>{% endfor %}
|
|
@ -1,24 +0,0 @@
|
|||||||
{% include meta.html %}
|
|
||||||
{% include polyfills.html %}
|
|
||||||
|
|
||||||
<!-- critical css -->
|
|
||||||
{% if include.critical == 'home'
|
|
||||||
%}<style id='critical-css'>{% include 2017/critical/critical-home.css %}</style>{%
|
|
||||||
endif
|
|
||||||
%}{%
|
|
||||||
if include.critical == 'sheet'
|
|
||||||
%}<style id='critical-css'>{% include 2017/critical/critical-sheet.css %}</style>{%
|
|
||||||
endif %}
|
|
||||||
|
|
||||||
<!-- allow disabling critical CSS optimization by passing ?nocrit=1 -->
|
|
||||||
<script id='critical-css-disable'>if (~window.location.search.indexOf('nocrit')){;[].slice.call(document.querySelectorAll('#critical-css')).map(function(e){e.parentNode.removeChild(e)})}</script>
|
|
||||||
|
|
||||||
<!-- deferred css -->
|
|
||||||
<script id='deferred-css'>;(function(links){(requestAnimationFrame||mozRequestAnimationFrame||webkitRequestAnimationFrame||msRequestAnimationFrame||(function(fn){window.addEventListener('load',fn)}))(function(){var h=document.getElementsByTagName('head')[0],l,i;for (i=0;i<links.length;i++){l=document.createElement('link');l.rel='stylesheet';l.href=links[i];h.appendChild(l);}})})([
|
|
||||||
'https://fonts.googleapis.com/css?family=Cousine',
|
|
||||||
'{{base}}/assets/2017/style.css?t={{ timestamp }}'
|
|
||||||
])</script>
|
|
||||||
<noscript id='deferred-css-fallback'>
|
|
||||||
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Cousine'>
|
|
||||||
<link rel='stylesheet' href='{{base}}/assets/2017/style.css?t={{ timestamp }}'>
|
|
||||||
</noscript>
|
|
@ -1,7 +0,0 @@
|
|||||||
<div class='HeadlinePub' role='complementary'>
|
|
||||||
<script async src='{{ site.data.carbon.src }}' id="_carbonads_js"></script>
|
|
||||||
<span class='placeholder -one'></span>
|
|
||||||
<span class='placeholder -two'></span>
|
|
||||||
<span class='placeholder -three'></span>
|
|
||||||
<span class='placeholder -four'></span>
|
|
||||||
</div>
|
|
@ -1,41 +0,0 @@
|
|||||||
{% comment %}
|
|
||||||
|
|
||||||
This partial assigns these variables:
|
|
||||||
|
|
||||||
meta_image: "https://assets.devhints.io/previews/react.jpg"
|
|
||||||
meta_description: "A comprehensive cheatsheet for React."
|
|
||||||
meta_title: "React cheatsheet"
|
|
||||||
depth: "1"
|
|
||||||
base: "./"
|
|
||||||
timestamp: "293048189123"
|
|
||||||
page_url: "https://devhints.io/react"
|
|
||||||
|
|
||||||
It emits some blank lines because Jekyll, lol.
|
|
||||||
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% assign depth = page.url | split: '/' | size | minus: 1 %}
|
|
||||||
|
|
||||||
{% assign base = '' %}
|
|
||||||
{% if depth <= 1 %}{% assign base = '.' %}
|
|
||||||
{% elsif depth == 2 %}{% assign base = '..' %}
|
|
||||||
{% elsif depth == 3 %}{% assign base = '../..' %}
|
|
||||||
{% elsif depth == 4 %}{% assign base = '../../..' %}{% endif %}
|
|
||||||
|
|
||||||
{% assign timestamp = site.time | date: "%Y%m%d%H%M%S" %}
|
|
||||||
|
|
||||||
{% if site.data.assets.preview_host %}{% capture meta_image %}{%
|
|
||||||
if page.url == '/'
|
|
||||||
%}{{ site.data.assets.preview_host }}/index.jpg?t={{ timestamp }}{%
|
|
||||||
else
|
|
||||||
%}{{ site.data.assets.preview_host }}{{ include.page.url | remove: '.html' }}.jpg?t={{ timestamp }}{%
|
|
||||||
endif
|
|
||||||
%}{% endcapture %}{% endif %}
|
|
||||||
|
|
||||||
{% capture meta_title %}{% include values/title.html page=include.page %}{% endcapture %}
|
|
||||||
{% assign meta_title = meta_title | strip_newlines %}
|
|
||||||
|
|
||||||
{% capture meta_description %}{% include values/description.html page=include.page %}{% endcapture %}
|
|
||||||
{% assign meta_description = meta_description | strip_newlines %}
|
|
||||||
|
|
||||||
{% capture page_url %}{{ site.url }}{{ page.url | remove: '.html' }}{% endcapture %}
|
|
@ -1,14 +0,0 @@
|
|||||||
{% assign slug = include.page.url | remove: '.html' | remove_first: '/' %}
|
|
||||||
<a class='{{ include.class }} -item-{{ slug }}'
|
|
||||||
href="{{base}}{{ include.page.url | remove: '.html' }}"
|
|
||||||
data-js-searchable-item='{"slug":"{{ slug }}","category":"{{ include.page.category }}"}'>
|
|
||||||
<span class='info'>
|
|
||||||
<code class='slug'>{{ slug }}</code>
|
|
||||||
|
|
||||||
{% if include.page.layout == '2017/sheet' %}
|
|
||||||
<abbr class='attribute-peg -new-layout hint--bottom' data-hint='New layout!'><span></span></abbr>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<span class='title'>{{ include.page.title }} {{ include.page.redirect_to }}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
@ -1,12 +0,0 @@
|
|||||||
<li class='{{ include.class }}'>
|
|
||||||
<a href='{{ base }}{{ include.page.url | remove: '.html' }}'>
|
|
||||||
<strong>{{ include.page.title }}</strong>
|
|
||||||
<span>
|
|
||||||
cheatsheet
|
|
||||||
|
|
||||||
{% if include.page.layout == '2017/sheet' %}
|
|
||||||
<abbr class='attribute-peg -new-layout hint--bottom' data-hint='New layout!'><span></span></abbr>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
@ -1,62 +0,0 @@
|
|||||||
{% assign category_pages = site.pages
|
|
||||||
| where: "category", include.page.category
|
|
||||||
| where_exp: "page", "page.url != include.page.url"
|
|
||||||
| where_exp: "page", "page.deprecated != true"
|
|
||||||
| where_exp: "page", "page.redirect_to == null"
|
|
||||||
| sort: "weight", "last"
|
|
||||||
%}
|
|
||||||
{% assign top_pages = site.pages
|
|
||||||
| where_exp: "page", "page.url != include.page.url"
|
|
||||||
| where_exp: "page", "page.deprecated != true"
|
|
||||||
| sort: "weight", "last"
|
|
||||||
%}
|
|
||||||
{% assign size = site.pages | size %}
|
|
||||||
<footer class='related-posts-area' id='related' data-js-no-preview>
|
|
||||||
<div class='container'>
|
|
||||||
<div class='related-posts-section'>
|
|
||||||
<div class='callout'>
|
|
||||||
<a class='related-posts-callout' href='{{ base }}'>
|
|
||||||
<div class='text'>
|
|
||||||
<i class='icon'></i>
|
|
||||||
|
|
||||||
<span class='description'>
|
|
||||||
{{ site.data.content.related_posts_callout.description | replace: "SIZE", size }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class='push-button -dark'>
|
|
||||||
{{ site.data.content.related_posts_callout.link }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='group'>
|
|
||||||
<div class='related-posts-group'>
|
|
||||||
{% if include.page.category == 'Others' %}
|
|
||||||
<h3>{{ site.data.content.related_posts_group.other }}</h3>
|
|
||||||
{% else %}
|
|
||||||
<h3>{{ site.data.content.related_posts_group.category | replace: "CATEGORY", include.page.category }}</h3>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<ul class='related-post-list'>
|
|
||||||
{% for page in category_pages limit: 6 %}
|
|
||||||
{% include 2017/related-posts-item.html page=page class='item related-post-item' %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='group'>
|
|
||||||
<div class='related-posts-group'>
|
|
||||||
<h3>{{ site.data.content.related_posts_group.top }}</h3>
|
|
||||||
|
|
||||||
<ul class='related-post-list'>
|
|
||||||
{% for page in top_pages limit: 6 %}
|
|
||||||
{% include 2017/related-posts-item.html page=page class='item related-post-item' %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
@ -1,12 +0,0 @@
|
|||||||
<footer class='search-footer' data-js-no-preview>
|
|
||||||
<div class='container'>
|
|
||||||
<div class='search-footer-section'>
|
|
||||||
<div class='search'>
|
|
||||||
{% include 2017/search-form.html class="-small" %}
|
|
||||||
</div>
|
|
||||||
<div class='links'>
|
|
||||||
<a class='home-button' href='{{ base }}'><i></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
@ -1,19 +0,0 @@
|
|||||||
<form
|
|
||||||
{% if include.live %}data-js-search-form{% endif %}
|
|
||||||
class='search' action='{{ base }}' method='get'>
|
|
||||||
<label class='search-box {{ include.class }}'>
|
|
||||||
<span class='prefix'>{{ site.data.content.search_form.prefix }}</span>
|
|
||||||
<span class='sep'>/</span>
|
|
||||||
<input name='q'
|
|
||||||
type='text'
|
|
||||||
{% if include.live %}
|
|
||||||
{% assign placeholder = site.data.content.search_form.home_placeholder | replace: "%{size}", size %}
|
|
||||||
autofocus data-js-search-input
|
|
||||||
placeholder='{{ placeholder }}'
|
|
||||||
{% else %}
|
|
||||||
{% assign size = site.pages | size %}
|
|
||||||
{% assign placeholder = site.data.content.search_form.default_placeholder | replace: "SIZE", size %}
|
|
||||||
placeholder='{{ placeholder }}'
|
|
||||||
{% endif %}>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
@ -1,35 +0,0 @@
|
|||||||
{% comment %}
|
|
||||||
Params:
|
|
||||||
- noshare
|
|
||||||
- noedit
|
|
||||||
- noback
|
|
||||||
{% endcomment %}
|
|
||||||
<nav class='top-nav' data-js-no-preview role='navigation'>
|
|
||||||
<div class='container'>
|
|
||||||
{% unless include.noback %}
|
|
||||||
<div class='left'>
|
|
||||||
<a class='home back-button' href='{{base}}'></a>
|
|
||||||
</div>
|
|
||||||
{% endunless %}
|
|
||||||
|
|
||||||
<a class='brand' href='{{base}}'>
|
|
||||||
{{ site.data.content.top_nav.title }}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{% unless include.noshare %}
|
|
||||||
<div class='actions'>
|
|
||||||
{% include social-list.html class="social page-actions" page=include.page %}
|
|
||||||
|
|
||||||
{% unless include.noedit %}
|
|
||||||
<ul class='page-actions'>
|
|
||||||
<li class='link github -button hint--bottom' data-hint='{{ site.data.content.top_nav.edit_on_github }}'>
|
|
||||||
<a href='{{ site.github.repository_url }}/blob/master/{{ page.path | remove: '.html' }}'>
|
|
||||||
<span class='text -visible'>{{ site.data.content.top_nav.edit }}</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{% endunless %}
|
|
||||||
</div>
|
|
||||||
{% endunless %}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
@ -1,20 +0,0 @@
|
|||||||
<div class="about-the-site">
|
|
||||||
<div class="container">
|
|
||||||
<p class='blurb'>
|
|
||||||
<strong><a href="{{ base }}">{{ site.title }}</a></strong> is a collection of cheatsheets I've written over the years.
|
|
||||||
Suggestions and corrections? <a href='https://github.com/rstacruz/cheatsheets/issues/907'>Send them in</a>.
|
|
||||||
<i class='fleuron'></i>
|
|
||||||
I'm <a href="http://ricostacruz.com">Rico Sta. Cruz</a>.
|
|
||||||
Check out my <a href="http://ricostacruz.com/posts">Today I learned blog</a> for more.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if page.url != '/index.html' %}
|
|
||||||
<p class='back'>
|
|
||||||
<a class='big-button -back -slim' href='.#toc'></a>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,14 +0,0 @@
|
|||||||
{% include about-the-site.html %}
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/highlight.min.js"></script>
|
|
||||||
|
|
||||||
{% comment %}<!-- https://github.com/highlightjs/cdn-release/tree/master/build/languages -->{% endcomment %}
|
|
||||||
{% for lang in page.hljs_languages %}
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.5/languages/{{lang}}.min.js"></script>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<script src="https://cdn.rawgit.com/rstacruz/unorphan/v1.0.1/index.js"></script>
|
|
||||||
<script>hljs.initHighlightingOnLoad()</script>
|
|
||||||
<script>unorphan('h1, h2, h3, p, li, .unorphan')</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang='en' class='no-js {{ page.html_class }}'>
|
|
||||||
<head>
|
|
||||||
{% include meta.html %}
|
|
||||||
{% include polyfills.html %}
|
|
||||||
<style>html{opacity:0}</style>
|
|
||||||
<link rel="stylesheet" href="{{base}}/assets/2015/style.css?t={{ timestamp }}">
|
|
||||||
<link href="{{base}}/assets/style.css?t={{ timestamp }}" rel="stylesheet" />
|
|
||||||
<link href="{{base}}/assets/print.css?t={{ timestamp }}" rel="stylesheet" media="print" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class='all'>
|
|
@ -1,85 +0,0 @@
|
|||||||
{% include 2017/meta-vars.html page=page %}
|
|
||||||
<meta charset='utf-8'>
|
|
||||||
<meta content='width=device-width, initial-scale=1.0' name='viewport'>
|
|
||||||
<link href='{{ base }}/assets/favicon.png' rel='shortcut icon'>
|
|
||||||
<meta content='{{ page.url | escape }}' name='app:pageurl'>
|
|
||||||
|
|
||||||
{% if meta_title %}
|
|
||||||
<title>{{ meta_title | escape }}</title>
|
|
||||||
<meta content='{{ meta_title | escape }}' property='og:title'>
|
|
||||||
<meta content='{{ meta_title | escape }}' property='twitter:title'>
|
|
||||||
<meta content='{{ page.og_type | default: "article" | escape }}' property='og:type'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if meta_image %}
|
|
||||||
<meta content='{{ meta_image | escape }}' property='og:image'>
|
|
||||||
<meta content='{{ meta_image | escape }}' property='twitter:image'>
|
|
||||||
<meta content='900' property='og:image:width'>
|
|
||||||
<meta content='471' property='og:image:height'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if meta_description %}
|
|
||||||
<meta content="{{ meta_description | escape }}" name="description">
|
|
||||||
<meta content="{{ meta_description | escape }}" property="og:description">
|
|
||||||
<meta content="{{ meta_description | escape }}" property="twitter:description">
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<link rel="canonical" href="{{ page_url | escape }}">
|
|
||||||
<meta name="og:url" content="{{ page_url | escape }}">
|
|
||||||
{% if page.url == '/' %}
|
|
||||||
<link rel="prefetch" href="{{ site.url | escape }}">
|
|
||||||
<link rel="prerender" href="{{ site.url | escape }}">
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if page.author %}
|
|
||||||
{% for author in site.authors | where: "name", page.author %}
|
|
||||||
<meta content='{{ author.name }}' name='author'>
|
|
||||||
{% if author.ogp %}
|
|
||||||
<meta content='{{ author.ogp }}' property='article:author'>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if site.title %}
|
|
||||||
<meta content='{{ site.title | escape }}' property='og:site_name'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if site.facebook.app_id %}
|
|
||||||
<meta content='{{ site.facebook.app_id | escape }}' property='fb:app_id'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if site.facebook.admin %}
|
|
||||||
<meta content='{{ site.facebook.admin | escape }}' property='fb:admins'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if page.date %}
|
|
||||||
<meta content='{{ page.date | date: "%Y-%m-%d" }}' property='article:published_date'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if page.category %}
|
|
||||||
<meta content='{{ page.category | escape }}' property='article:section'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if page.tags %}
|
|
||||||
{% for tag in page.tags %}
|
|
||||||
<meta content='{{ tag | escape }}' property='article:tag'>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if site.data.google_analytics.enabled %}
|
|
||||||
<script async src='https://www.googletagmanager.com/gtag/js?id={{ site.data.google_analytics.id }}'></script>
|
|
||||||
<script>
|
|
||||||
{% comment %} if(~location.hostname.indexOf('{{site.data.google_analytics.hostname}}')){ {% endcomment %}
|
|
||||||
window.dataLayer=window.dataLayer||[];
|
|
||||||
function gtag(){dataLayer.push(arguments)};
|
|
||||||
gtag('js',new Date());
|
|
||||||
gtag('config','{{ site.data.google_analytics.id }}');
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if depth %}
|
|
||||||
<meta property='page:depth' content='{{depth}}'>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>
|
|
||||||
<script>(function(H){H.className=H.className.replace(/\bNoJs\b/,'WithJs')})(document.documentElement)</script>
|
|
@ -1,9 +0,0 @@
|
|||||||
<script>(function(d,s){if(window.Promise&&[].includes&&Object.assign&&window.Map)return;var js,sc=d.getElementsByTagName(s)[0];js=d.createElement(s);js.src='https://cdn.polyfill.io/v2/polyfill.min.js';sc.parentNode.insertBefore(js, sc);}(document,'script'))</script>
|
|
||||||
|
|
||||||
<!--[if lt IE 9]>{%comment%}
|
|
||||||
{%endcomment%}<script src='https://cdnjs.cloudflare.com/ajax/libs/nwmatcher/1.2.5/nwmatcher.min.js'></script>{%comment%}
|
|
||||||
{%endcomment%}<script src='https://cdnjs.cloudflare.com/ajax/libs/json2/20140204/json2.js'></script>{%comment%}
|
|
||||||
{%endcomment%}<script src='https://cdn.rawgit.com/gisu/selectivizr/1.0.3/selectivizr.js'></script>{%comment%}
|
|
||||||
{%endcomment%}<script src='https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js'></script>{%comment%}
|
|
||||||
{%endcomment%}<script src='https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.js'></script>{%comment%}
|
|
||||||
{%endcomment%}<![endif]-->
|
|
@ -1,6 +0,0 @@
|
|||||||
<div class='site-header'>
|
|
||||||
<div class='container'>
|
|
||||||
This is <a href="."><em>{{ site.title }}</em></a> — a collection of cheatsheets I've written.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
{% if include.page.type == 'home' %}
|
|
||||||
{% assign description = site.data.content.social_list.default_description %}
|
|
||||||
{% else %}
|
|
||||||
{% assign description = site.data.content.social_list.description | replace: "TITLE", include.page.title %}
|
|
||||||
{% endif %}
|
|
||||||
<ul class="social-list {{ include.class }}">
|
|
||||||
<li class="facebook link hint--bottom" data-hint="{{ site.data.content.social_list.facebook_share }}"><a href="https://www.facebook.com/sharer/sharer.php?u={{ site.url | uri_escape }}{{ include.page.url | uri_escape }}" target="share"><span class="text"></span></a></li>
|
|
||||||
<li class="twitter link hint--bottom" data-hint="{{ site.data.content.social_list.twitter_share }}"><a href="https://twitter.com/intent/tweet?text={{ description | uri_escape }}%20{{ site.url | uri_escape }}{{ include.page.url | uri_escape }}" target="share"><span class="text"></span></a></li>
|
|
||||||
{% comment %}
|
|
||||||
<li class="googleplus link hint--bottom" data-hint="Share on Google Plus"><a href="https://plus.google.com/share?url={{ site.url }}{{ include.page.url }}" target="share"><span class="text">+1</span></a></li>-->
|
|
||||||
{% endcomment %}
|
|
||||||
</ul>
|
|
@ -1,17 +0,0 @@
|
|||||||
{%
|
|
||||||
if page.description and page.intro
|
|
||||||
%}{{ page.description }} {{ page.intro | markdownify | strip_html }}{%
|
|
||||||
elsif page.description
|
|
||||||
%}{{ page.description }} · One-page guide to {{ page.title }}{%
|
|
||||||
elsif page.keywords and page.intro
|
|
||||||
%}{{ page.keywords | join: ' · ' }} · {{ page.intro | markdownify | strip_html }}{%
|
|
||||||
elsif page.keywords
|
|
||||||
%}{{ page.keywords | join: ' · ' }} · One-page guide to {{ page.title }}{%
|
|
||||||
elsif page.intro
|
|
||||||
%}One-page guide to {{ page.title }}: usage, examples, and more. {{ page.intro | markdownify | strip_html }}{%
|
|
||||||
elsif page.type == 'article'
|
|
||||||
%}The one-page guide to {{ page.title }}: usage, examples, links, snippets, and more.{%
|
|
||||||
else
|
|
||||||
%}The one-page guide to {{ page.title }}: usage, examples, links, snippets, and more.{%
|
|
||||||
endif
|
|
||||||
%}
|
|
@ -1,11 +0,0 @@
|
|||||||
{%
|
|
||||||
if page.full_title
|
|
||||||
%}{{ page.full_title }}{%
|
|
||||||
elsif page.type == 'article'
|
|
||||||
%}{{ page.title }} cheatsheet{%
|
|
||||||
elsif page.title
|
|
||||||
%}{{ page.title }}{%
|
|
||||||
else
|
|
||||||
%}{{ site.title }}{%
|
|
||||||
endif
|
|
||||||
%}
|
|
@ -1,78 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html class='NoJs' lang='en'><head>
|
|
||||||
|
|
||||||
{% assign featured_pages = site.pages
|
|
||||||
| where_exp: "page", "page.tags contains 'Featured'"
|
|
||||||
%}
|
|
||||||
{% assign recent_pages = site.pages
|
|
||||||
| where_exp: "page", "page.updated"
|
|
||||||
| sort: "updated", "first"
|
|
||||||
%}
|
|
||||||
{% include 2017/head.html critical='home' %}
|
|
||||||
|
|
||||||
</head><body class='UseCompactHeader HighlightPubFirstLine'>
|
|
||||||
|
|
||||||
{% include 2017/top-nav.html page=page is_home=true noedit=true noback=true %}
|
|
||||||
|
|
||||||
<div class='body-area -slim'>
|
|
||||||
<div class='site-header' role='banner'>
|
|
||||||
<h1>
|
|
||||||
{{ site.data.content.home.title }}
|
|
||||||
</h1>
|
|
||||||
<p>
|
|
||||||
{{ site.data.content.home.tagline }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% include 2017/search-form.html live=true %}
|
|
||||||
|
|
||||||
{% if site.data.carbon.enabled %}
|
|
||||||
<div class='pubbox'>
|
|
||||||
{% include 2017/headline-pub.html %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='pages-list' role='main'>
|
|
||||||
{% for page in featured_pages %}
|
|
||||||
{% include 2017/pages-list-item.html page=page class='item top-sheet' %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<h2 class='category item' data-js-searchable-header>
|
|
||||||
<span>Recently updated</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{% for page in recent_pages reversed %}
|
|
||||||
{% if forloop.index <= 18 %}
|
|
||||||
{% include 2017/pages-list-item.html page=page class='article item' %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
{% for category in site.data.categories.names %}
|
|
||||||
<h2 class='category item' id='{{ category | downcase | replace: " ", "-" }}' data-js-searchable-header>
|
|
||||||
<span>{{ category }}</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{% for page in site.pages %}
|
|
||||||
{% if page.category == category %}
|
|
||||||
{% if page.title %}
|
|
||||||
{% include 2017/pages-list-item.html page=page class='article item' %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class='message item missing-message'>
|
|
||||||
<h3>See something missing?</h3>
|
|
||||||
<p>
|
|
||||||
<a class='push-button' href='{{ site.github.repository_url }}/issues/907'>Request cheatsheet</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include 2017/announcements-list.html %}
|
|
||||||
|
|
||||||
{% include 2017/foot.html %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,26 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang='en'><head>
|
|
||||||
|
|
||||||
{% include 2017/head.html %}
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
{% include 2017/top-nav.html page=page noshare=true %}
|
|
||||||
|
|
||||||
<div class='body-area -slim'>
|
|
||||||
<div class='site-header'>
|
|
||||||
<h1>{{ site.data.content.not_found.title }}</h1>
|
|
||||||
<p>{{ site.data.content.not_found.description }}</p>
|
|
||||||
|
|
||||||
{% include 2017/search-form.html %}
|
|
||||||
|
|
||||||
<p class='action'>
|
|
||||||
<a class='push-button' href='./'>{{ site.data.content.not_found.home }}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include 2017/foot.html %}
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,54 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html class='NoJs' lang='en'><head>
|
|
||||||
|
|
||||||
{% include 2017/head.html critical='sheet' %}
|
|
||||||
{% include 2017/article-schema.html page=page %}
|
|
||||||
|
|
||||||
</head><body class='UseCompactHeader HighlightPubFirstLine'>
|
|
||||||
|
|
||||||
{% include 2017/top-nav.html page=page %}
|
|
||||||
|
|
||||||
<div class='body-area'>
|
|
||||||
<header class='main-heading -center' role='banner'>
|
|
||||||
<h1 class='h1'>{{ page.title }} <em>{{ site.data.content.sheet.suffix }}</em></h1>
|
|
||||||
|
|
||||||
<div class='pubbox' data-js-no-preview>
|
|
||||||
{% if site.data.carbon.enabled %}
|
|
||||||
{% include 2017/headline-pub.html %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{% if page.tags contains 'WIP' %}
|
|
||||||
<aside class='notice-box'>
|
|
||||||
This page is a work in progress. You can help by <a href='{{ site.github.repository_url }}/blob/master/{{ page.path | remove: '.html' }}'>suggesting edits</a>!
|
|
||||||
</aside>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if page.deprecated_by %}
|
|
||||||
<aside class='notice-box'>
|
|
||||||
<strong>Deprecated:</strong> This guide covers an older version.
|
|
||||||
<a href='{{ base }}{{ page.deprecated_by }}'>A newer version is available here.</a>
|
|
||||||
</aside>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if page.intro %}
|
|
||||||
<div class='intro-content MarkdownBody'>
|
|
||||||
{{ page.intro | markdownify }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<main class='post-content MarkdownBody' data-js-main-body data-js-anchors role='main'>
|
|
||||||
{{ content }}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='pre-footer' data-js-no-preview><i class='icon'></i></div>
|
|
||||||
|
|
||||||
{% include 2017/comments-area.html page=page %}
|
|
||||||
{% include 2017/search-footer.html %}
|
|
||||||
{% include 2017/related-posts.html page=page %}
|
|
||||||
{% include 2017/foot.html %}
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1 +0,0 @@
|
|||||||
{{content}}
|
|
@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
type: article
|
|
||||||
---
|
|
||||||
{% include head.html %}
|
|
||||||
{% include site-header.html %}
|
|
||||||
|
|
||||||
<div class='post-list -single -cheatsheet'>
|
|
||||||
<div class='post-item'>
|
|
||||||
{% include social-list.html page=page class="-collapse" %}
|
|
||||||
|
|
||||||
<div class='post-headline -cheatsheet'>
|
|
||||||
<p class='prelude'><span></span></p>
|
|
||||||
<h1><span>{{ page.title }}</span></h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if site.data.carbon.enabled %}
|
|
||||||
<div class='headline-pub'>
|
|
||||||
<script async src='{{ site.data.carbon.src }}' id="_carbonads_js"></script>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class='post-content -cheatsheet'>
|
|
||||||
{{ content }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include social-list.html page=page %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include foot.html %}
|
|
@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
type: article
|
|
||||||
---
|
|
||||||
{% include head.html %}
|
|
||||||
{% include site-header.html %}
|
|
||||||
|
|
||||||
{% include 2017/article-schema.html page=page %}
|
|
||||||
|
|
||||||
<div class='post-list -single -cheatsheet'>
|
|
||||||
<div class='post-item'>
|
|
||||||
<div class='post-headline -cheatsheet'>
|
|
||||||
<p class='prelude'><span></span></p>
|
|
||||||
<h1><span>{{ page.title }}</span></h1>
|
|
||||||
|
|
||||||
<div class='pubbox'>
|
|
||||||
{% include 2017/headline-pub.html %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='post-content -cheatsheet'>
|
|
||||||
{{ content }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include social-list.html page=page %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include foot.html %}
|
|
@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
html_class: home
|
|
||||||
type: home
|
|
||||||
---
|
|
||||||
{% include head.html %}
|
|
||||||
{% include site-header.html %}
|
|
||||||
|
|
||||||
{% for category in site.data.categories.names %}
|
|
||||||
<div class='pages-header'>
|
|
||||||
<h2 id='{{ category | downcase | replace: " ", "-" }}'>{{ category }}</h2>
|
|
||||||
</div>
|
|
||||||
<div class='pages-list'>
|
|
||||||
{% for page in site.pages %}
|
|
||||||
{% if page.category == category %}
|
|
||||||
<a href="{{base}}{{ page.url }}">
|
|
||||||
<span class='title'>{{ page.title }}</span>
|
|
||||||
<span class='date'>{{ page.url | remove: '.html' | remove: '/' }}</span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if site.data.carbon.enabled %}
|
|
||||||
<div class='side-ad'>
|
|
||||||
<script async src='{{ site.data.carbon.src }}' id="_carbonads_js"></script>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% include foot.html %}
|
|
@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
{% assign target = page.redirect.to | remove: '.html' | replace: 'cheatsheets/cheatsheets', 'cheatsheets' %}
|
|
||||||
<html lang="en-US">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Redirecting…</title>
|
|
||||||
<link rel="canonical" href="{{ target }}">
|
|
||||||
<meta http-equiv="refresh" content="0; url={{ target }}">
|
|
||||||
<h1>Redirecting...</h1>
|
|
||||||
<a href="{{ target }}">Click here if you are not redirected.</a>
|
|
||||||
<script>location="{{ target }}"</script>
|
|
||||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||||||
// Base
|
|
||||||
@import '../_sass/2017/base/base.scss';
|
|
||||||
@import '../_sass/2017/base/fade.scss';
|
|
@ -1,30 +0,0 @@
|
|||||||
// Prismjs
|
|
||||||
import 'prismjs'
|
|
||||||
import 'prismjs/plugins/line-highlight/prism-line-highlight.min.js'
|
|
||||||
import 'prismjs/components/prism-jsx.min.js'
|
|
||||||
import 'prismjs/components/prism-bash.min.js'
|
|
||||||
import 'prismjs/components/prism-scss.min.js'
|
|
||||||
import 'prismjs/components/prism-css.min.js'
|
|
||||||
import 'prismjs/components/prism-elixir.min.js'
|
|
||||||
import 'prismjs/components/prism-ruby.min.js'
|
|
||||||
|
|
||||||
// Initializers
|
|
||||||
import './initializers/prism'
|
|
||||||
import './initializers/onmount'
|
|
||||||
|
|
||||||
// Behaviors
|
|
||||||
import './behaviors/anchors'
|
|
||||||
import './behaviors/dismissable'
|
|
||||||
import './behaviors/dismiss'
|
|
||||||
import './behaviors/disqus'
|
|
||||||
import './behaviors/h3-section-list'
|
|
||||||
import './behaviors/main-body'
|
|
||||||
import './behaviors/no-preview'
|
|
||||||
import './behaviors/searchable-header'
|
|
||||||
import './behaviors/searchable-item'
|
|
||||||
import './behaviors/search-form'
|
|
||||||
import './behaviors/search-input'
|
|
||||||
|
|
||||||
// CSS
|
|
||||||
import 'prismjs/plugins/line-highlight/prism-line-highlight.css'
|
|
||||||
import 'hint.css/hint.min.css'
|
|
@ -1,46 +0,0 @@
|
|||||||
import onmount from 'onmount'
|
|
||||||
import prepend from 'dom101/prepend'
|
|
||||||
|
|
||||||
const DEFAULTS = {
|
|
||||||
// select elements to put anchor on
|
|
||||||
rule: 'h2[id]',
|
|
||||||
// class name for anchor
|
|
||||||
className: 'local-anchor anchor',
|
|
||||||
// text of anchor
|
|
||||||
text: '#',
|
|
||||||
// append before or after innerText?
|
|
||||||
shouldAppend: false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Behavior: Add local anchors
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-anchors]', function () {
|
|
||||||
const data = JSON.parse(this.getAttribute('data-js-anchors') || '{}')
|
|
||||||
const rules = Array.isArray(data)
|
|
||||||
? data.length
|
|
||||||
? data
|
|
||||||
: [DEFAULTS]
|
|
||||||
: [Object.assign({}, DEFAULTS, data)]
|
|
||||||
|
|
||||||
for (const { rule, className, text, shouldAppend } of rules) {
|
|
||||||
for (const el of this.querySelectorAll(rule)) {
|
|
||||||
if (!el.hasAttribute('id')) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = el.getAttribute('id')
|
|
||||||
const anchor = document.createElement('a')
|
|
||||||
anchor.setAttribute('href', `#${id}`)
|
|
||||||
anchor.setAttribute('class', className)
|
|
||||||
anchor.innerText = String(text || DEFAULTS.text)
|
|
||||||
|
|
||||||
if (shouldAppend) {
|
|
||||||
el.appendChild(anchor)
|
|
||||||
} else {
|
|
||||||
prepend(el, anchor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,22 +0,0 @@
|
|||||||
import closest from 'dom101/closest'
|
|
||||||
import remove from 'dom101/remove'
|
|
||||||
import on from 'dom101/on'
|
|
||||||
import { getData } from '../helpers/data'
|
|
||||||
import onmount from 'onmount'
|
|
||||||
import * as Dismiss from '../helpers/dismiss'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismiss button
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-dismiss]', function () {
|
|
||||||
const parent = closest(this, '[data-js-dismissable]')
|
|
||||||
const dismissable = getData(parent, 'js-dismissable')
|
|
||||||
const id = (dismissable && dismissable.id) || ''
|
|
||||||
|
|
||||||
on(this, 'click', (e) => {
|
|
||||||
Dismiss.setDismissed(id)
|
|
||||||
e.preventDefault()
|
|
||||||
if (parent) remove(parent)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,17 +0,0 @@
|
|||||||
import onmount from 'onmount'
|
|
||||||
import remove from 'dom101/remove'
|
|
||||||
import removeClass from 'dom101/remove-class'
|
|
||||||
|
|
||||||
import { getData } from '../helpers/data'
|
|
||||||
import { isDismissed } from '../helpers/dismiss'
|
|
||||||
import { isPreview } from '../helpers/preview'
|
|
||||||
|
|
||||||
onmount('[data-js-dismissable]', function () {
|
|
||||||
const id = getData(this, 'js-dismissable').id || ''
|
|
||||||
|
|
||||||
if (isPreview() || isDismissed(id)) {
|
|
||||||
remove(this)
|
|
||||||
} else {
|
|
||||||
removeClass(this, '-hide')
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,32 +0,0 @@
|
|||||||
import onmount from 'onmount'
|
|
||||||
import injectDisqus from '../helpers/inject_disqus'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delay disqus by some time. It's at the bottom of the page, there's no need
|
|
||||||
* for it to load fast. This will give more time to load more critical assets.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DISQUS_DELAY = 100
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injects Disqus onto the page.
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-disqus]', function () {
|
|
||||||
const data = JSON.parse(this.getAttribute('data-js-disqus'))
|
|
||||||
const $parent = this.parentNode
|
|
||||||
$parent.setAttribute('hidden', true)
|
|
||||||
|
|
||||||
window.disqus_config = function () {
|
|
||||||
this.page.url = data.url
|
|
||||||
this.page.identifier = data.identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disqus takes a while to load, don't do it so eagerly.
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
injectDisqus(data.host)
|
|
||||||
$parent.removeAttribute('hidden')
|
|
||||||
}, DISQUS_DELAY)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,32 +0,0 @@
|
|||||||
/* eslint-disable no-new */
|
|
||||||
|
|
||||||
import Isotope from 'isotope-layout'
|
|
||||||
import onmount from 'onmount'
|
|
||||||
import on from 'dom101/on'
|
|
||||||
import qsa from 'dom101/query-selector-all'
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Behavior: Isotope
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-h3-section-list]', function () {
|
|
||||||
const iso = new Isotope(this, {
|
|
||||||
itemSelector: '.h3-section',
|
|
||||||
transitionDuration: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const images = qsa('img', this)
|
|
||||||
|
|
||||||
images.forEach((image) => {
|
|
||||||
on(image, 'load', () => {
|
|
||||||
iso.layout()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Insurance against weirdness on pages like devhints.io/vim, where the
|
|
||||||
// critical path CSS may look different from the final CSS (because of the
|
|
||||||
// tables).
|
|
||||||
on(window, 'load', () => {
|
|
||||||
iso.layout()
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,16 +0,0 @@
|
|||||||
import remove from 'dom101/remove'
|
|
||||||
import onmount from 'onmount'
|
|
||||||
import addClass from 'dom101/add-class'
|
|
||||||
|
|
||||||
import { isPreview } from '../helpers/preview'
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Behavior: Things to remove when preview mode is on
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-no-preview]', function (b) {
|
|
||||||
if (isPreview()) {
|
|
||||||
remove(this)
|
|
||||||
addClass(document.documentElement, 'PreviewMode')
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,17 +0,0 @@
|
|||||||
import onmount from 'onmount'
|
|
||||||
import on from 'dom101/on'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submitting the search form
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-search-form]', function () {
|
|
||||||
on(this, 'submit', (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
const link = document.querySelector('a[data-search-index]:not([hidden])')
|
|
||||||
const href = link && link.getAttribute('href')
|
|
||||||
|
|
||||||
if (href) window.location = href
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,24 +0,0 @@
|
|||||||
import onmount from 'onmount'
|
|
||||||
import * as Search from '../helpers/search'
|
|
||||||
import qs from '../helpers/qs'
|
|
||||||
import on from 'dom101/on'
|
|
||||||
|
|
||||||
onmount('[data-js-search-input]', function () {
|
|
||||||
on(this, 'input', () => {
|
|
||||||
const val = this.value
|
|
||||||
|
|
||||||
if (val === '') {
|
|
||||||
Search.showAll()
|
|
||||||
} else {
|
|
||||||
Search.show(val)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const query = (qs(window.location.search) || {}).q
|
|
||||||
if (query && query.length) {
|
|
||||||
this.value = query
|
|
||||||
setTimeout(() => {
|
|
||||||
Search.show(query)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,23 +0,0 @@
|
|||||||
import onmount from 'onmount'
|
|
||||||
import { nextUntil } from '../helpers/dom'
|
|
||||||
import matches from 'dom101/matches'
|
|
||||||
|
|
||||||
// Ensure that search-index is set first
|
|
||||||
import './searchable-item'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Propagate item search indices to headers
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-searchable-header]', function () {
|
|
||||||
const els = nextUntil(this, '[data-js-searchable-header]').filter((el) =>
|
|
||||||
matches(el, '[data-search-index]')
|
|
||||||
)
|
|
||||||
|
|
||||||
const keywords = els
|
|
||||||
.map((n) => n.getAttribute('data-search-index'))
|
|
||||||
.join(' ')
|
|
||||||
.split(' ')
|
|
||||||
|
|
||||||
this.setAttribute('data-search-index', keywords.join(' '))
|
|
||||||
})
|
|
@ -1,13 +0,0 @@
|
|||||||
import onmount from 'onmount'
|
|
||||||
import permutate from '../helpers/permutate'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets search indices (`data-search-index` attribute)
|
|
||||||
*/
|
|
||||||
|
|
||||||
onmount('[data-js-searchable-item]', function () {
|
|
||||||
const data = JSON.parse(this.getAttribute('data-js-searchable-item') || '{}')
|
|
||||||
const words = permutate(data)
|
|
||||||
|
|
||||||
this.setAttribute('data-search-index', words.join(' '))
|
|
||||||
})
|
|
@ -1,2 +0,0 @@
|
|||||||
import 'sanitize.css'
|
|
||||||
import './critical-home.scss'
|
|
@ -1,16 +0,0 @@
|
|||||||
@import './_utils.scss';
|
|
||||||
@import './_base.scss';
|
|
||||||
|
|
||||||
// Components
|
|
||||||
@import '../_sass/2017/components/attribute-peg.scss';
|
|
||||||
@import '../_sass/2017/components/announcements-item.scss';
|
|
||||||
@import '../_sass/2017/components/announcements-list.scss';
|
|
||||||
@import '../_sass/2017/components/back-button.scss';
|
|
||||||
@import '../_sass/2017/components/body-area.scss';
|
|
||||||
@import '../_sass/2017/components/headline-pub.scss';
|
|
||||||
@import '../_sass/2017/components/page-actions.scss';
|
|
||||||
@import '../_sass/2017/components/pages-list.scss';
|
|
||||||
@import '../_sass/2017/components/search-box.scss';
|
|
||||||
@import '../_sass/2017/components/site-header.scss';
|
|
||||||
@import '../_sass/2017/components/top-nav.scss';
|
|
||||||
@import '../_sass/2017/components/top-sheet.scss';
|
|
@ -1,2 +0,0 @@
|
|||||||
import 'sanitize.css'
|
|
||||||
import './critical-sheet.scss'
|
|
@ -1,21 +0,0 @@
|
|||||||
@import './_utils.scss';
|
|
||||||
@import './_base.scss';
|
|
||||||
|
|
||||||
// Markdown
|
|
||||||
@import '../_sass/2017/markdown/a-em.scss';
|
|
||||||
@import '../_sass/2017/markdown/code.scss';
|
|
||||||
@import '../_sass/2017/markdown/headings.scss';
|
|
||||||
@import '../_sass/2017/markdown/local-anchor.scss';
|
|
||||||
@import '../_sass/2017/markdown/p.scss';
|
|
||||||
@import '../_sass/2017/markdown/table.scss';
|
|
||||||
@import '../_sass/2017/markdown/ul.scss';
|
|
||||||
|
|
||||||
// Components
|
|
||||||
@import '../_sass/2017/components/back-button.scss';
|
|
||||||
@import '../_sass/2017/components/body-area.scss';
|
|
||||||
@import '../_sass/2017/components/h3-section.scss';
|
|
||||||
@import '../_sass/2017/components/h3-section-list.scss';
|
|
||||||
@import '../_sass/2017/components/headline-pub.scss';
|
|
||||||
@import '../_sass/2017/components/main-heading.scss';
|
|
||||||
@import '../_sass/2017/components/page-actions.scss';
|
|
||||||
@import '../_sass/2017/components/top-nav.scss';
|
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* This is the "critical path" JavaScript that will be included INLINE on every
|
|
||||||
* page. Keep this as small as possible!
|
|
||||||
*/
|
|
||||||
|
|
||||||
import wrapify from './wrapify'
|
|
||||||
import addClass from 'dom101/add-class'
|
|
||||||
import on from 'dom101/on'
|
|
||||||
|
|
||||||
// Transform the main body markup to make it readable.
|
|
||||||
const body = document.querySelector('[data-js-main-body]')
|
|
||||||
|
|
||||||
if (body) {
|
|
||||||
wrapify(body)
|
|
||||||
addClass(body, '-wrapified')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Be "done" when we're done, or after a certain timeout.
|
|
||||||
on(window, 'load', done)
|
|
||||||
setTimeout(done, 5000)
|
|
||||||
|
|
||||||
let isDone
|
|
||||||
function done() {
|
|
||||||
if (isDone) return
|
|
||||||
addClass(document.documentElement, 'LoadDone')
|
|
||||||
isDone = true
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
import matches from 'dom101/matches'
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Just like jQuery.append
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function appendMany(el, children) {
|
|
||||||
children.forEach((child) => {
|
|
||||||
el.appendChild(child)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Just like jQuery.nextUntil
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function nextUntil(el, selector) {
|
|
||||||
const nextEl = el.nextSibling
|
|
||||||
return nextUntilTick(nextEl, selector, [])
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextUntilTick(el, selector, acc) {
|
|
||||||
if (!el) return acc
|
|
||||||
|
|
||||||
const isMatch = matches(el, selector)
|
|
||||||
if (isMatch) return acc
|
|
||||||
|
|
||||||
return nextUntilTick(el.nextSibling, selector, [...acc, el])
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Just like jQuery.before
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function before(reference, newNode) {
|
|
||||||
reference.parentNode.insertBefore(newNode, reference)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Like jQuery.children('selector')
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function findChildren(el, selector) {
|
|
||||||
return [].slice.call(el.children).filter((child) => matches(child, selector))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a div
|
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* createDiv({ class: 'foo' })
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function createDiv(props) {
|
|
||||||
const d = document.createElement('div')
|
|
||||||
Object.keys(props).forEach((key) => {
|
|
||||||
d.setAttribute(key, props[key])
|
|
||||||
})
|
|
||||||
return d
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
/* blank */
|
|
@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* Permutates a searcheable item.
|
|
||||||
*
|
|
||||||
* permutate({
|
|
||||||
* slug: 'hello-world',
|
|
||||||
* category: 'greetings'
|
|
||||||
* })
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function permutate(data) {
|
|
||||||
let words = []
|
|
||||||
if (data.slug) {
|
|
||||||
words = words.concat(permutateString(data.slug))
|
|
||||||
}
|
|
||||||
if (data.category) {
|
|
||||||
words = words.concat(permutateString(data.category))
|
|
||||||
}
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permutates strings.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* permutateString('hi joe')
|
|
||||||
* => ['h', 'hi', 'j', 'jo', 'joe']
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function permutateString(str) {
|
|
||||||
let words = []
|
|
||||||
let inputs = splitwords(str)
|
|
||||||
|
|
||||||
inputs.forEach((word) => {
|
|
||||||
words = words.concat(permutateWord(word))
|
|
||||||
})
|
|
||||||
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permutates a word.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* permutateWord('hello')
|
|
||||||
* => ['h', 'he', 'hel', 'hell', 'hello']
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function permutateWord(str) {
|
|
||||||
let words = []
|
|
||||||
const len = str.length
|
|
||||||
for (var i = 1; i <= len; ++i) {
|
|
||||||
words.push(str.substr(0, i))
|
|
||||||
}
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for splitting to words.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* splitWords('Hello, world!')
|
|
||||||
* => ['hello', 'world']
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function splitwords(str) {
|
|
||||||
const words = str
|
|
||||||
.toLowerCase()
|
|
||||||
.split(/[ /\-_]/)
|
|
||||||
.filter((k) => k && k.length !== 0)
|
|
||||||
|
|
||||||
return words
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
import { splitwords } from './permutate'
|
|
||||||
import qsa from 'dom101/query-selector-all'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show everything.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* Search.showAll()
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function showAll() {
|
|
||||||
qsa('[data-search-index]').forEach((el) => {
|
|
||||||
el.removeAttribute('hidden')
|
|
||||||
el.style.removeProperty('display')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for a given keyword.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* Search.show('hello')
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function show(val) {
|
|
||||||
const keywords = splitwords(val)
|
|
||||||
|
|
||||||
if (!keywords.length) return showAll()
|
|
||||||
|
|
||||||
const selectors = keywords
|
|
||||||
.map((k) => `[data-search-index~=${JSON.stringify(k)}]`)
|
|
||||||
.join('')
|
|
||||||
|
|
||||||
qsa('[data-search-index]').forEach((el) => {
|
|
||||||
el.setAttribute('hidden', true)
|
|
||||||
el.style.setProperty('display', "none")
|
|
||||||
})
|
|
||||||
|
|
||||||
qsa(selectors).forEach((el) => {
|
|
||||||
el.removeAttribute('hidden')
|
|
||||||
el.style.removeProperty('display')
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import ready from 'dom101/ready'
|
|
||||||
import onmount from 'onmount'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Behavior: Wrapping
|
|
||||||
*/
|
|
||||||
|
|
||||||
ready(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
onmount()
|
|
||||||
})
|
|
||||||
})
|
|
@ -1 +0,0 @@
|
|||||||
window.Prism = require('prismjs')
|
|
@ -1,274 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`h2 + pre 1`] = `
|
|
||||||
<div>
|
|
||||||
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="h2-section"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
heading
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="h3-section language-markdown"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="body language-markdown"
|
|
||||||
>
|
|
||||||
<pre
|
|
||||||
class="language-markdown"
|
|
||||||
>
|
|
||||||
(code)
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`h3 with class 1`] = `
|
|
||||||
<div>
|
|
||||||
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="h2-section -hello"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list -hello"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="h3-section -hello"
|
|
||||||
>
|
|
||||||
<h3
|
|
||||||
class="-hello"
|
|
||||||
>
|
|
||||||
install
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="body -hello"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
(install)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`multiple h2s 1`] = `
|
|
||||||
<div>
|
|
||||||
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="h2-section"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
multiple h2
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h2-section"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="h3-section"
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
install
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="body"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
(install)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h3-section"
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
usage
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="body"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
(usage)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h2-section"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
getting started
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h2-section"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="h3-section"
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
first
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="body"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
(first)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h3-section"
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
second
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="body"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
(second)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`simple usage 1`] = `
|
|
||||||
<div>
|
|
||||||
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="h2-section"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
simple usage
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h2-section"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="body h3-section-list"
|
|
||||||
data-js-h3-section-list=""
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="h3-section"
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
install
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="body"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
(install)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h3-section"
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
usage
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
class="body"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
(usage)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
@ -1,88 +0,0 @@
|
|||||||
/* eslint-env jest */
|
|
||||||
import wrapify from '../index'
|
|
||||||
|
|
||||||
it(
|
|
||||||
'simple usage',
|
|
||||||
run(
|
|
||||||
`
|
|
||||||
<div>
|
|
||||||
<h2>simple usage<h2>
|
|
||||||
|
|
||||||
<h3>install</h3>
|
|
||||||
<p>(install)</p>
|
|
||||||
|
|
||||||
<h3>usage</h3>
|
|
||||||
<p>(usage)</p>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
(root) => {
|
|
||||||
expect(
|
|
||||||
root.querySelectorAll('.h2-section .h3-section-list .h3-section').length
|
|
||||||
).toEqual(2)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'h3 with class',
|
|
||||||
run(
|
|
||||||
`
|
|
||||||
<div>
|
|
||||||
<h3 class='-hello'>install</h3>
|
|
||||||
<p>(install)</p>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
(root) => {
|
|
||||||
expect(root.querySelectorAll('div.h3-section.-hello').length).toEqual(1)
|
|
||||||
expect(
|
|
||||||
root.querySelectorAll('div.h3-section-list.-hello').length
|
|
||||||
).toEqual(1)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'multiple h2s',
|
|
||||||
run(`
|
|
||||||
<div>
|
|
||||||
<h2>multiple h2<h2>
|
|
||||||
|
|
||||||
<h3>install</h3>
|
|
||||||
<p>(install)</p>
|
|
||||||
|
|
||||||
<h3>usage</h3>
|
|
||||||
<p>(usage)</p>
|
|
||||||
|
|
||||||
<h2>getting started<h2>
|
|
||||||
|
|
||||||
<h3>first</h3>
|
|
||||||
<p>(first)</p>
|
|
||||||
|
|
||||||
<h3>second</h3>
|
|
||||||
<p>(second)</p>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
)
|
|
||||||
|
|
||||||
function run(input, fn) {
|
|
||||||
return function () {
|
|
||||||
const div = document.createElement('div')
|
|
||||||
div.innerHTML = input
|
|
||||||
|
|
||||||
const root = div.children[0]
|
|
||||||
wrapify(root)
|
|
||||||
expect(root).toMatchSnapshot()
|
|
||||||
|
|
||||||
if (fn) fn(root)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it(
|
|
||||||
'h2 + pre',
|
|
||||||
run(`
|
|
||||||
<div>
|
|
||||||
<h2>heading</h2>
|
|
||||||
<pre class='language-markdown'>(code)</pre>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
)
|
|
@ -1,119 +0,0 @@
|
|||||||
import matches from 'dom101/matches'
|
|
||||||
import addClass from 'dom101/add-class'
|
|
||||||
import {
|
|
||||||
appendMany,
|
|
||||||
nextUntil,
|
|
||||||
before,
|
|
||||||
findChildren,
|
|
||||||
createDiv
|
|
||||||
} from '../helpers/dom'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps h2 sections into h2-section.
|
|
||||||
* Wraps h3 sections into h3-section.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function wrapify(root) {
|
|
||||||
// These are your H2 sections. Returns a list of .h2-section nodes.
|
|
||||||
const sections = wrapifyH2(root)
|
|
||||||
|
|
||||||
// For each h2 section, wrap the H3's in them
|
|
||||||
sections.forEach((section) => {
|
|
||||||
const bodies = findChildren(section, '[data-js-h3-section-list]')
|
|
||||||
bodies.forEach((body) => {
|
|
||||||
wrapifyH3(body)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps h2 sections into h2-section.
|
|
||||||
* Creates and HTML structure like so:
|
|
||||||
*
|
|
||||||
* .h2-section
|
|
||||||
* h2.
|
|
||||||
* (title)
|
|
||||||
* .body.h3-section-list.
|
|
||||||
* (body goes here)
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function wrapifyH2(root) {
|
|
||||||
return groupify(root, {
|
|
||||||
tag: 'h2',
|
|
||||||
wrapperFn: () => createDiv({ class: 'h2-section' }),
|
|
||||||
bodyFn: () =>
|
|
||||||
createDiv({
|
|
||||||
class: 'body h3-section-list',
|
|
||||||
'data-js-h3-section-list': ''
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps h3 sections into h3-section.
|
|
||||||
* Creates and HTML structure like so:
|
|
||||||
*
|
|
||||||
* .h3-section
|
|
||||||
* h3.
|
|
||||||
* (title)
|
|
||||||
* .body.
|
|
||||||
* (body goes here)
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function wrapifyH3(root) {
|
|
||||||
return groupify(root, {
|
|
||||||
tag: 'h3',
|
|
||||||
wrapperFn: () => createDiv({ class: 'h3-section' }),
|
|
||||||
bodyFn: () => createDiv({ class: 'body' })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Groups all headings (a `tag` selector) under wrappers like `.h2-section`
|
|
||||||
* (build by `wrapperFn()`).
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function groupify(el, { tag, wrapperFn, bodyFn }) {
|
|
||||||
const first = el.children[0]
|
|
||||||
let result = []
|
|
||||||
|
|
||||||
// Handle the markup before the first h2
|
|
||||||
if (first && !matches(first, tag)) {
|
|
||||||
const sibs = nextUntil(first, tag)
|
|
||||||
result.push(wrap(first, null, [first, ...sibs]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all h3's inside it
|
|
||||||
const children = findChildren(el, tag)
|
|
||||||
|
|
||||||
children.forEach((child) => {
|
|
||||||
const sibs = nextUntil(child, tag)
|
|
||||||
result.push(wrap(child, child, sibs))
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
function wrap(pivot, first, sibs) {
|
|
||||||
const wrap = wrapperFn()
|
|
||||||
|
|
||||||
const pivotClass = pivot.className
|
|
||||||
if (pivotClass) addClass(wrap, pivotClass)
|
|
||||||
before(pivot, wrap)
|
|
||||||
|
|
||||||
const body = bodyFn()
|
|
||||||
if (pivotClass) addClass(body, pivotClass)
|
|
||||||
appendMany(body, sibs)
|
|
||||||
|
|
||||||
if (first) wrap.appendChild(first)
|
|
||||||
wrap.appendChild(body)
|
|
||||||
|
|
||||||
return wrap
|
|
||||||
}
|
|
||||||
}
|
|
0
_sass/.gitignore
vendored
0
_sass/.gitignore
vendored
427
_sass/2015/base/normalize.scss
vendored
427
_sass/2015/base/normalize.scss
vendored
@ -1,427 +0,0 @@
|
|||||||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Set default font family to sans-serif.
|
|
||||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
|
||||||
* user zoom.
|
|
||||||
*/
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: sans-serif; /* 1 */
|
|
||||||
-ms-text-size-adjust: 100%; /* 2 */
|
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove default margin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HTML5 display definitions
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
|
||||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
|
||||||
* and Firefox.
|
|
||||||
* Correct `block` display not defined for `main` in IE 11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
article,
|
|
||||||
aside,
|
|
||||||
details,
|
|
||||||
figcaption,
|
|
||||||
figure,
|
|
||||||
footer,
|
|
||||||
header,
|
|
||||||
hgroup,
|
|
||||||
main,
|
|
||||||
menu,
|
|
||||||
nav,
|
|
||||||
section,
|
|
||||||
summary {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
|
||||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
|
||||||
*/
|
|
||||||
|
|
||||||
audio,
|
|
||||||
canvas,
|
|
||||||
progress,
|
|
||||||
video {
|
|
||||||
display: inline-block; /* 1 */
|
|
||||||
vertical-align: baseline; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent modern browsers from displaying `audio` without controls.
|
|
||||||
* Remove excess height in iOS 5 devices.
|
|
||||||
*/
|
|
||||||
|
|
||||||
audio:not([controls]) {
|
|
||||||
display: none;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
|
||||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[hidden],
|
|
||||||
template {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Links
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the gray background color from active links in IE 10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Improve readability when focused and also mouse hovered in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a:active,
|
|
||||||
a:hover {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Text-level semantics
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr[title] {
|
|
||||||
border-bottom: 1px dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address styling not present in Safari and Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
dfn {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address variable `h1` font-size and margin within `section` and `article`
|
|
||||||
* contexts in Firefox 4+, Safari, and Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
margin: 0.67em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address styling not present in IE 8/9.
|
|
||||||
*/
|
|
||||||
|
|
||||||
mark {
|
|
||||||
background: #ff0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address inconsistent and variable font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Embedded content
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove border when inside `a` element in IE 8/9/10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct overflow not hidden in IE 9/10/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
svg:not(:root) {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grouping content
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address margin not present in IE 8/9 and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
figure {
|
|
||||||
margin: 1em 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address differences between Firefox and other browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
box-sizing: content-box;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contain overflow in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pre {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address odd `em`-unit font size rendering in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
pre,
|
|
||||||
samp {
|
|
||||||
font-family: monospace, monospace;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
|
||||||
* styling of `select`, unless a `border` property is set.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct color not being inherited.
|
|
||||||
* Known issue: affects color of disabled elements.
|
|
||||||
* 2. Correct font properties not being inherited.
|
|
||||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
color: inherit; /* 1 */
|
|
||||||
font: inherit; /* 2 */
|
|
||||||
margin: 0; /* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
|
||||||
* All other form control elements do not inherit `text-transform` values.
|
|
||||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
|
||||||
* Correct `select` style inheritance in Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
|
||||||
* and `video` controls.
|
|
||||||
* 2. Correct inability to style clickable `input` types in iOS.
|
|
||||||
* 3. Improve usability and consistency of cursor style between image-type
|
|
||||||
* `input` and others.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
html input[type="button"], /* 1 */
|
|
||||||
input[type="reset"],
|
|
||||||
input[type="submit"] {
|
|
||||||
-webkit-appearance: button; /* 2 */
|
|
||||||
cursor: pointer; /* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-set default cursor for disabled elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button[disabled],
|
|
||||||
html input[disabled] {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove inner padding and border in Firefox 4+.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button::-moz-focus-inner,
|
|
||||||
input::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
|
||||||
* the UA stylesheet.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input {
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It's recommended that you don't attempt to style these elements.
|
|
||||||
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
|
||||||
*
|
|
||||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
|
||||||
* 2. Remove excess padding in IE 8/9/10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input[type="checkbox"],
|
|
||||||
input[type="radio"] {
|
|
||||||
box-sizing: border-box; /* 1 */
|
|
||||||
padding: 0; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
|
||||||
* `font-size` values of the `input`, it causes the cursor style of the
|
|
||||||
* decrement button to change from `default` to `text`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
|
||||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
|
||||||
* (include `-moz` to future-proof).
|
|
||||||
*/
|
|
||||||
|
|
||||||
input[type="search"] {
|
|
||||||
-webkit-appearance: textfield; /* 1 */
|
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
-webkit-box-sizing: content-box; /* 2 */
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
|
||||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
|
||||||
* padding (and `textfield` appearance).
|
|
||||||
*/
|
|
||||||
|
|
||||||
input[type="search"]::-webkit-search-cancel-button,
|
|
||||||
input[type="search"]::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define consistent border, margin, and padding.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
border: 1px solid #c0c0c0;
|
|
||||||
margin: 0 2px;
|
|
||||||
padding: 0.35em 0.625em 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
|
||||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
|
||||||
*/
|
|
||||||
|
|
||||||
legend {
|
|
||||||
border: 0; /* 1 */
|
|
||||||
padding: 0; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Don't inherit the `font-weight` (applied by a rule above).
|
|
||||||
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
|
||||||
*/
|
|
||||||
|
|
||||||
optgroup {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tables
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove most spacing between table cells.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td,
|
|
||||||
th {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
@import url('//brick.a.ssl.fastly.net/Roboto:400,400i,700')
|
|
||||||
@import url('//fonts.googleapis.com/css?family=Raleway:800')
|
|
||||||
@import url('//fonts.googleapis.com/css?family=Fira+Mono:400,400i')
|
|
||||||
@import url('//brick.a.ssl.fastly.net/EB+Garamond:400i')
|
|
||||||
|
|
||||||
$body-font-size: 17px
|
|
||||||
$body-line-height: 1.7
|
|
||||||
|
|
||||||
@mixin font-size($multiplier, $lhmultiplier, $size, $line-height)
|
|
||||||
font-size: $size * $multiplier
|
|
||||||
@if $line-height != none
|
|
||||||
line-height: $line-height * $lhmultiplier
|
|
||||||
|
|
||||||
@mixin body-font
|
|
||||||
font-family: 'Roboto', sans-serif
|
|
||||||
font-weight: 400
|
|
||||||
|
|
||||||
@mixin italic-font
|
|
||||||
font-family: 'eb garamond', serif
|
|
||||||
font-weight: 400
|
|
||||||
font-style: italic
|
|
||||||
+kernliga
|
|
||||||
|
|
||||||
@mixin headline-font
|
|
||||||
font-family: 'eb garamond', serif
|
|
||||||
font-weight: 400
|
|
||||||
font-style: italic
|
|
||||||
+kernliga
|
|
||||||
|
|
||||||
@mixin caps-font
|
|
||||||
text-transform: uppercase
|
|
||||||
letter-spacing: 2px
|
|
||||||
|
|
||||||
@mixin mono-font
|
|
||||||
font-family: 'fira mono', monospace
|
|
||||||
font-weight: 400
|
|
||||||
letter-spacing: -0.5px
|
|
||||||
+no-antialias
|
|
||||||
|
|
||||||
@mixin bold-font
|
|
||||||
font-family: 'raleway', sans-serif
|
|
||||||
font-weight: 800
|
|
||||||
|
|
||||||
/*
|
|
||||||
* sizes
|
|
||||||
*/
|
|
||||||
|
|
||||||
@mixin italic-font-size($size, $line-height: none)
|
|
||||||
+font-size(1.0, 1.0, $size, $line-height)
|
|
||||||
|
|
||||||
@mixin headline-font-size($size, $line-height: none)
|
|
||||||
+font-size(1.0, 1.0, $size, $line-height)
|
|
||||||
|
|
||||||
@mixin bold-font-size($size, $line-height: none)
|
|
||||||
+font-size(1.0, 1.0, $size, $line-height)
|
|
||||||
|
|
||||||
@mixin mono-font-size($size, $line-height: none)
|
|
||||||
+font-size(1.0, 1.0, $size, $line-height)
|
|
@ -1,41 +0,0 @@
|
|||||||
@mixin kernliga
|
|
||||||
font-size-adjust: none
|
|
||||||
// don't display digraphs in languages that don't support it
|
|
||||||
-webkit-font-language-override: normal
|
|
||||||
font-language-override: normal
|
|
||||||
|
|
||||||
// use font-defined kerning info
|
|
||||||
-webkit-font-kerning: auto
|
|
||||||
font-kerning: auto
|
|
||||||
|
|
||||||
// opentype options: kerning, ligatures, horiz ligatures, discretionary ligatures, contextual swash
|
|
||||||
// https://en.wikipedia.org/wiki/List_of_typographic_features
|
|
||||||
-webkit-font-feature-settings: "kern", "liga", "dlig", "hlig", "cswh"
|
|
||||||
font-feature-settings: "kern", "liga", "dlig", "hlig", "cswh"
|
|
||||||
|
|
||||||
// allow browser to auto-infer missing glyphs
|
|
||||||
font-synthesis: weight style
|
|
||||||
|
|
||||||
// swashes on first letters
|
|
||||||
// &:first-letter
|
|
||||||
// font-feature-settings: "kern", "swsh"
|
|
||||||
// -webkit-font-feature-settings: "kern", "swsh"
|
|
||||||
|
|
||||||
@mixin antialias
|
|
||||||
text-rendering: optimizeLegibility
|
|
||||||
-webkit-font-smoothing: antialiased
|
|
||||||
-moz-osx-font-smoothing: grayscale
|
|
||||||
|
|
||||||
@mixin no-antialias
|
|
||||||
text-rendering: auto
|
|
||||||
-webkit-font-smoothing: subpixel-antialiased
|
|
||||||
-moz-osx-font-smoothing: auto
|
|
||||||
|
|
||||||
@mixin clearfix
|
|
||||||
&:after
|
|
||||||
display: table
|
|
||||||
content: ''
|
|
||||||
clear: both
|
|
||||||
height: 0
|
|
||||||
zoom: 1
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* .about-the-site
|
|
||||||
*/
|
|
||||||
|
|
||||||
.about-the-site
|
|
||||||
&
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
.container
|
|
||||||
text-align: center
|
|
||||||
padding: 3em 80px
|
|
||||||
margin: 0 auto
|
|
||||||
+clearfix
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
padding-left: 40px
|
|
||||||
padding-right: 40px
|
|
||||||
|
|
||||||
&:before
|
|
||||||
content: ''
|
|
||||||
position: absolute
|
|
||||||
display: block
|
|
||||||
left: 20px
|
|
||||||
right: 20px
|
|
||||||
top: 0
|
|
||||||
border-top: solid 1px $hairline
|
|
||||||
|
|
||||||
&
|
|
||||||
+body-font
|
|
||||||
font-size: 0.85em
|
|
||||||
color: lighten($gray, 20%)
|
|
||||||
|
|
||||||
a, a:visited
|
|
||||||
color: lighten($gray, 10%)
|
|
||||||
box-shadow: inset 0 -1px rgba(black, 0.05)
|
|
||||||
padding-bottom: 2px
|
|
||||||
|
|
||||||
a:hover, a:focus
|
|
||||||
color: $black
|
|
||||||
box-shadow: inset 0 -2px $accent
|
|
||||||
|
|
||||||
strong
|
|
||||||
+bold-font
|
|
||||||
|
|
||||||
strong a, strong a:visited
|
|
||||||
color: $black
|
|
||||||
box-shadow: none
|
|
||||||
|
|
||||||
strong a:hover, strong a:focus
|
|
||||||
color: $black
|
|
||||||
box-shadow: inset 0 -2px $accent
|
|
||||||
|
|
||||||
.identity
|
|
||||||
margin: 0
|
|
||||||
margin-top: 0.2em
|
|
||||||
float: right
|
|
||||||
+italic-font
|
|
||||||
+italic-font-size(2.5em)
|
|
||||||
|
|
||||||
.identity a,
|
|
||||||
.identity a:visited
|
|
||||||
color: $black
|
|
||||||
box-shadow: none
|
|
||||||
|
|
||||||
.identity a:hover,
|
|
||||||
.identity a:focus
|
|
||||||
color: $accent
|
|
||||||
|
|
||||||
.blurb
|
|
||||||
margin: 0
|
|
||||||
max-width: 500px
|
|
||||||
text-align: left
|
|
||||||
float: left
|
|
||||||
line-height: 1.55
|
|
||||||
|
|
||||||
.back
|
|
||||||
float: right
|
|
||||||
margin-top: 0.4em
|
|
||||||
margin-right: 2em
|
|
||||||
|
|
||||||
.fleuron:before
|
|
||||||
content: '\f492'
|
|
||||||
font-family: Ionicons
|
|
||||||
font-size: 16px
|
|
||||||
font-weight: normal
|
|
||||||
font-style: normal
|
|
||||||
display: inline-block
|
|
||||||
vertical-align: middle
|
|
||||||
color: $black
|
|
||||||
margin: 0 7px
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
display: none
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
.identity
|
|
||||||
float: left
|
|
||||||
clear: both
|
|
||||||
.blurb
|
|
||||||
float: none
|
|
||||||
margin-bottom: 1em
|
|
||||||
width: auto
|
|
||||||
.back
|
|
||||||
float: right
|
|
||||||
margin-right: 0
|
|
@ -1,66 +0,0 @@
|
|||||||
.big-button,
|
|
||||||
a.big-button
|
|
||||||
display: inline-block
|
|
||||||
|
|
||||||
width: 180px
|
|
||||||
height: 50px
|
|
||||||
line-height: 50px - 2px
|
|
||||||
padding: 0
|
|
||||||
border-radius: 30px
|
|
||||||
font-size: 0.85em
|
|
||||||
box-shadow: none
|
|
||||||
background: transparent
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
width: 140px
|
|
||||||
height: 40px
|
|
||||||
line-height: 40px - 2px
|
|
||||||
|
|
||||||
&, &:visited
|
|
||||||
border: solid 2px $accent
|
|
||||||
color: $accent
|
|
||||||
|
|
||||||
&.-back,
|
|
||||||
&.-back:visited
|
|
||||||
border: solid 1px rgba($gray, 0.2)
|
|
||||||
color: $gray
|
|
||||||
|
|
||||||
&.-back:before,
|
|
||||||
&.-next:after
|
|
||||||
font-family: Ionicons
|
|
||||||
font-size: 20px
|
|
||||||
display: inline-block
|
|
||||||
vertical-align: middle
|
|
||||||
position: relative
|
|
||||||
top: -1px
|
|
||||||
|
|
||||||
&.-back:before
|
|
||||||
content: '\f38f'
|
|
||||||
|
|
||||||
&.-next:after
|
|
||||||
content: '\f3d1'
|
|
||||||
margin-left: 16px
|
|
||||||
top: 0
|
|
||||||
|
|
||||||
&.-slim
|
|
||||||
width: 60px
|
|
||||||
border-width: 2px
|
|
||||||
height: 60px
|
|
||||||
line-height: 60px
|
|
||||||
border-radius: 50%
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
width: 40px
|
|
||||||
height: 40px
|
|
||||||
line-height: 40px
|
|
||||||
|
|
||||||
&:hover, &:focus
|
|
||||||
background: $accent
|
|
||||||
border-color: transparent
|
|
||||||
color: white
|
|
||||||
box-shadow: none
|
|
||||||
|
|
||||||
&.-back:hover,
|
|
||||||
&.-back:focus
|
|
||||||
background: $accent
|
|
||||||
color: white
|
|
@ -1,8 +0,0 @@
|
|||||||
/*
|
|
||||||
* .brief-intro -- Brief introduction
|
|
||||||
*/
|
|
||||||
|
|
||||||
.brief-intro
|
|
||||||
font-size: 1.1em
|
|
||||||
color: $gray
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* .full-image -- full width image containers
|
|
||||||
*/
|
|
||||||
|
|
||||||
.full-image
|
|
||||||
&
|
|
||||||
overflow: hidden
|
|
||||||
text-align: center
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
img
|
|
||||||
background: #fcfcfc
|
|
||||||
|
|
||||||
&.cropped img,
|
|
||||||
&.cropped img:first-child:last-child
|
|
||||||
margin-bottom: -50px
|
|
||||||
display: block
|
|
||||||
background: transparent
|
|
||||||
|
|
||||||
&.cropped:after
|
|
||||||
content: ''
|
|
||||||
display: block
|
|
||||||
position: absolute
|
|
||||||
bottom: 0
|
|
||||||
left: 20px
|
|
||||||
right: 20px
|
|
||||||
border-bottom: solid 1px $lightgray
|
|
||||||
|
|
||||||
&.stretched img
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
margin-left: -20px
|
|
||||||
margin-right: -20px
|
|
||||||
|
|
||||||
@media (min-width: 769px)
|
|
||||||
width: 100vw
|
|
||||||
margin-left: calc(-50vw + #{$page-width} / 2)
|
|
@ -1,14 +0,0 @@
|
|||||||
.hint--top, .hint--bottom
|
|
||||||
&:before
|
|
||||||
margin-top: -14px
|
|
||||||
margin-left: -8px
|
|
||||||
border-radius: 2px
|
|
||||||
|
|
||||||
&:before, &:after
|
|
||||||
transition-duration: 10ms
|
|
||||||
|
|
||||||
&:after
|
|
||||||
box-shadow: none
|
|
||||||
border-radius: 2px
|
|
||||||
text-shadow: none
|
|
||||||
margin-left: -55px
|
|
@ -1,78 +0,0 @@
|
|||||||
.hljs-literal,
|
|
||||||
.hljs-number,
|
|
||||||
.hljs-string,
|
|
||||||
.hljs-symbol,
|
|
||||||
.hljs-value
|
|
||||||
color: $accent
|
|
||||||
|
|
||||||
.hljs-key,
|
|
||||||
.hljs-attribute
|
|
||||||
color: darken($accent, 20%)
|
|
||||||
|
|
||||||
.hljs-keyword,
|
|
||||||
.hljs-constant
|
|
||||||
color: $black
|
|
||||||
|
|
||||||
.hljs-comment
|
|
||||||
color: $gray
|
|
||||||
font-style: italic
|
|
||||||
|
|
||||||
//
|
|
||||||
// Prism
|
|
||||||
//
|
|
||||||
|
|
||||||
.token.comment,
|
|
||||||
.token.prolog,
|
|
||||||
.token.doctype,
|
|
||||||
.token.cdata
|
|
||||||
color: $gray
|
|
||||||
font-style: italic
|
|
||||||
|
|
||||||
.token.punctuation
|
|
||||||
color: #999
|
|
||||||
|
|
||||||
.token.property,
|
|
||||||
.token.tag,
|
|
||||||
.token.boolean,
|
|
||||||
.token.number,
|
|
||||||
.token.constant,
|
|
||||||
.token.symbol,
|
|
||||||
.token.deleted
|
|
||||||
color: #905
|
|
||||||
|
|
||||||
.token.selector,
|
|
||||||
.token.attr-name,
|
|
||||||
.token.string,
|
|
||||||
.token.char,
|
|
||||||
.token.builtin,
|
|
||||||
.token.inserted
|
|
||||||
color: $accent
|
|
||||||
|
|
||||||
.token.operator,
|
|
||||||
.token.entity,
|
|
||||||
.token.url,
|
|
||||||
.language-css .token.string,
|
|
||||||
.style .token.string
|
|
||||||
color: #a67f59
|
|
||||||
|
|
||||||
.token.atrule,
|
|
||||||
.token.attr-value,
|
|
||||||
.token.keyword
|
|
||||||
color: #07a
|
|
||||||
|
|
||||||
.token.function
|
|
||||||
color: #DD4A68
|
|
||||||
|
|
||||||
.token.regex,
|
|
||||||
.token.important,
|
|
||||||
.token.variable
|
|
||||||
color: #e90
|
|
||||||
|
|
||||||
.token.important,
|
|
||||||
.token.bold
|
|
||||||
font-weight: bold
|
|
||||||
.token.italic
|
|
||||||
font-style: italic
|
|
||||||
|
|
||||||
.token.entity
|
|
||||||
cursor: help
|
|
@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
* .next-article -- lead into the next article
|
|
||||||
*/
|
|
||||||
|
|
||||||
.next-article
|
|
||||||
$bg: darken(#8e44ad, 15%)
|
|
||||||
$textcolor: saturate(mix(white, $bg, 85%), 90%)
|
|
||||||
|
|
||||||
&
|
|
||||||
display: block
|
|
||||||
padding: 0
|
|
||||||
margin-left: 40px
|
|
||||||
margin-right: 40px
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
|
|
||||||
&, &:hover, &:focus
|
|
||||||
box-shadow: none
|
|
||||||
|
|
||||||
&:after
|
|
||||||
content: ''
|
|
||||||
display: block
|
|
||||||
position: absolute
|
|
||||||
left: -20px
|
|
||||||
right: -20px
|
|
||||||
bottom: 0
|
|
||||||
border-bottom: solid 1px $hairline
|
|
||||||
|
|
||||||
// suppress its hairline
|
|
||||||
& + .about-the-site:before
|
|
||||||
display: none
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
margin-left: 0
|
|
||||||
margin-right: 0
|
|
||||||
|
|
||||||
// remove horizontal line
|
|
||||||
& + .about-the-site:before
|
|
||||||
display: none
|
|
||||||
|
|
||||||
.container
|
|
||||||
display: block
|
|
||||||
text-align: center
|
|
||||||
padding: 10em 20px
|
|
||||||
|
|
||||||
@media (min-width: 769px)
|
|
||||||
margin-top: 8em
|
|
||||||
padding: 8em 20px
|
|
||||||
|
|
||||||
.h3
|
|
||||||
display: block
|
|
||||||
margin: 0 auto auto
|
|
||||||
padding: 0
|
|
||||||
font-size: 2.2em
|
|
||||||
line-height: 1.3em
|
|
||||||
+bold-font
|
|
||||||
color: white
|
|
||||||
transition: all 250ms linear
|
|
||||||
|
|
||||||
// &:hover > span
|
|
||||||
// box-shadow: inset 0 -2px $accent
|
|
||||||
// text-shadow: 0 0 4px $bg, 0 0 4px $bg, 0 0 4px $bg, 0 0 4px $bg, 0 0 4px $bg, 0 0 4px $bg, 0 0 4px $bg, 0 0 4px $bg, 0 0 4px $bg
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
font-size: 1.5em
|
|
||||||
|
|
||||||
.h3, .excerpt
|
|
||||||
max-width: $page-width * 0.9
|
|
||||||
|
|
||||||
.h3 + .excerpt
|
|
||||||
margin-top: 0.5em
|
|
||||||
|
|
||||||
.excerpt
|
|
||||||
display: block
|
|
||||||
margin-left: auto
|
|
||||||
margin-right: auto
|
|
||||||
font-size: 1em
|
|
||||||
line-height: 1.6em
|
|
||||||
|
|
||||||
.big-button
|
|
||||||
margin-top: 2em
|
|
||||||
|
|
||||||
h3 a:hover,
|
|
||||||
h3 a:focus
|
|
||||||
color: $accent
|
|
||||||
|
|
||||||
.heading
|
|
||||||
display: block
|
|
||||||
+caps-font
|
|
||||||
font-size: 0.7em
|
|
||||||
margin-bottom: 1em
|
|
||||||
|
|
||||||
.heading:before
|
|
||||||
font-family: Ionicons
|
|
||||||
content: '\f4a8'
|
|
||||||
margin-right: 15px
|
|
||||||
font-size: 16px
|
|
||||||
display: inline-block
|
|
||||||
vertical-align: middle
|
|
||||||
color: $accent
|
|
||||||
|
|
||||||
.big-button,
|
|
||||||
a.big-button
|
|
||||||
background: transparent
|
|
||||||
|
|
||||||
&, &:visited
|
|
||||||
border-color: $accent
|
|
||||||
color: white
|
|
||||||
|
|
||||||
&:hover, &:focus
|
|
||||||
background: $accent
|
|
||||||
border-color: transparent
|
|
||||||
|
|
||||||
@mixin recolor-article($bg, $bg2, $url: '', $a: 0.9, $angle: 177deg)
|
|
||||||
$w: 1500
|
|
||||||
$h: 10
|
|
||||||
$textcolor: mix(white, $bg, 75%)
|
|
||||||
$notch: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='#{$w}' height='#{$h}' version='1.1'><polyline fill='white' points='#{$w},0 0,0 0,#{$h}'/></svg>"
|
|
||||||
|
|
||||||
&
|
|
||||||
background: url($notch) -50px top / 110% auto no-repeat, linear-gradient(to right, rgba(adjust-color($bg, $lightness: 0%), $a), rgba(adjust-color($bg2, $lightness: 0%), $a)), linear-gradient($angle, rgba($bg, 0.0), rgba($bg, 0.9)), url($url) center center / cover, $bg
|
|
||||||
&:hover, &:focus
|
|
||||||
background: url($notch) -50px top / 110% auto no-repeat, linear-gradient(to right, rgba(adjust-color($bg, $lightness: 1%), $a), rgba(adjust-color($bg2, $lightness: 1%), $a)), linear-gradient($angle, rgba($bg, 0.0), rgba($bg, 0.9)), url($url) center center / cover, $bg
|
|
||||||
.excerpt, .heading
|
|
||||||
color: $textcolor
|
|
||||||
|
|
||||||
.next-article
|
|
||||||
&
|
|
||||||
+recolor-article(#612e76, #5867cc)
|
|
||||||
&.-v2
|
|
||||||
text-shadow: 0 1px 1px rgba(black, 0.5)
|
|
||||||
+recolor-article(#027d65, #00536b, "bg/pebbles.jpg", 0.7, $angle: 35deg)
|
|
||||||
&.-v3
|
|
||||||
text-shadow: 0 1px 1px rgba(black, 0.5)
|
|
||||||
+recolor-article(#1d2434, #202a3e, "bg/roughwall.jpg", 0.9, $angle: 1deg)
|
|
||||||
&.-v4
|
|
||||||
text-shadow: 0 1px 1px rgba(black, 0.5)
|
|
||||||
+recolor-article(#902014, #c77e0a, "bg/woodfloor.jpg", 0.45, $angle: 1deg)
|
|
||||||
&.-v5
|
|
||||||
text-shadow: 0 1px 1px rgba(black, 0.5)
|
|
||||||
+recolor-article(#17283a, #25295e, "bg/stairs.jpg", 0.85, $angle: 1deg)
|
|
||||||
// &.next-article
|
|
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* .post-headline -- H1's of posts
|
|
||||||
*/
|
|
||||||
|
|
||||||
.post-headline
|
|
||||||
&
|
|
||||||
margin: 1.5em auto 3em auto
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
.post-icon
|
|
||||||
margin-bottom: 2px
|
|
||||||
|
|
||||||
h1
|
|
||||||
text-align: center
|
|
||||||
margin-bottom: 0
|
|
||||||
+headline-font
|
|
||||||
+headline-font-size(2.8em, 1.2)
|
|
||||||
width: 80%
|
|
||||||
margin-left: auto
|
|
||||||
margin-right: auto
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
+headline-font-size(2em)
|
|
||||||
|
|
||||||
.meta
|
|
||||||
display: block
|
|
||||||
text-align: center
|
|
||||||
margin: 0
|
|
||||||
margin-top: 1em
|
|
||||||
font-weight: normal
|
|
||||||
+caps-font
|
|
||||||
font-size: 0.8em
|
|
||||||
|
|
||||||
.meta .author,
|
|
||||||
.meta .date
|
|
||||||
margin: 0 5px
|
|
||||||
padding-bottom: 2px
|
|
||||||
|
|
||||||
.meta a,
|
|
||||||
.meta a:visited
|
|
||||||
color: $gray
|
|
||||||
|
|
||||||
.meta a:hover,
|
|
||||||
.meta a:focus
|
|
||||||
&, span
|
|
||||||
color: $gray
|
|
||||||
|
|
||||||
time
|
|
||||||
color: $black
|
|
||||||
box-shadow: inset 0 -2px $accent
|
|
||||||
|
|
||||||
a, a:visited, a:focus, a:hover
|
|
||||||
color: $black
|
|
||||||
text-decoration: none
|
|
||||||
box-shadow: none
|
|
||||||
|
|
||||||
.pubbox
|
|
||||||
margin-top: 32px
|
|
||||||
font-size: 16px
|
|
||||||
line-height: 1.5
|
|
||||||
|
|
||||||
.carbon-img
|
|
||||||
margin-top: 4px
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* .post-icon -- category icons
|
|
||||||
*/
|
|
||||||
|
|
||||||
$icon-size: 56px
|
|
||||||
|
|
||||||
@mixin iconify($color, $text)
|
|
||||||
&
|
|
||||||
background: $color
|
|
||||||
color: lighten($color, 50%)
|
|
||||||
box-shadow: -2px 2px #e67e22, 2px -2px #f1c40f, 1px -2px rgba($color, 0.2), 1px 3px rgba($color, 0.3)
|
|
||||||
|
|
||||||
span:after
|
|
||||||
content: $text
|
|
||||||
|
|
||||||
.post-icon,
|
|
||||||
abbr.post-icon
|
|
||||||
border: 0
|
|
||||||
margin: 0
|
|
||||||
display: inline-block
|
|
||||||
width: $icon-size
|
|
||||||
height: $icon-size
|
|
||||||
line-height: $icon-size + 2px
|
|
||||||
text-align: center
|
|
||||||
border-radius: 50%
|
|
||||||
color: #aaa
|
|
||||||
background: #eee
|
|
||||||
|
|
||||||
+body-font
|
|
||||||
font-size: 16px
|
|
||||||
letter-spacing: 1px
|
|
||||||
|
|
||||||
span:after
|
|
||||||
content: attr(data-label)
|
|
||||||
font-size: 0.9em
|
|
||||||
position: relative
|
|
||||||
top: -1px
|
|
||||||
|
|
||||||
@media (max-width: 480px)
|
|
||||||
transform: scale(0.75)
|
|
||||||
|
|
||||||
&.-icon-css
|
|
||||||
+iconify(#3498db, 'CSS')
|
|
||||||
font-size: 14px
|
|
||||||
line-height: $icon-size + 2px
|
|
||||||
&.-icon-development
|
|
||||||
+iconify(#34495e, 'DEV')
|
|
||||||
font-size: 14px
|
|
||||||
line-height: $icon-size + 2px
|
|
||||||
&.-icon-ruby
|
|
||||||
+iconify(#e74c3c, 'RB')
|
|
||||||
text-indent: 2px
|
|
||||||
&.-icon-javascript
|
|
||||||
+iconify(#2ecc71, 'JS')
|
|
||||||
text-indent: 1px
|
|
||||||
&.-icon-productivity
|
|
||||||
+iconify(#2ecc71, 'PROD')
|
|
||||||
text-indent: 1px
|
|
@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* .post-index (et al) -- utility classes
|
|
||||||
*/
|
|
||||||
|
|
||||||
.post-index
|
|
||||||
&
|
|
||||||
margin: 0 auto 4em auto
|
|
||||||
position: relative
|
|
||||||
padding-top: 4em
|
|
||||||
font-size: 0.9em
|
|
||||||
|
|
||||||
.container
|
|
||||||
overflow: hidden
|
|
||||||
max-width: $page-width
|
|
||||||
margin: 0 auto
|
|
||||||
|
|
||||||
h3
|
|
||||||
+caps-font
|
|
||||||
color: $gray
|
|
||||||
font-size: 1em
|
|
||||||
|
|
||||||
.post-index-item
|
|
||||||
display: block
|
|
||||||
overflow: hidden
|
|
||||||
padding: 6px 20px
|
|
||||||
border-top: solid 1px $hairline
|
|
||||||
|
|
||||||
&, &:hover, &:focus
|
|
||||||
box-shadow: none
|
|
||||||
|
|
||||||
&:hover, &:focus
|
|
||||||
.article
|
|
||||||
transition: none
|
|
||||||
|
|
||||||
.date
|
|
||||||
display: block
|
|
||||||
|
|
||||||
.date
|
|
||||||
width: 100px
|
|
||||||
font-size: 0.8em
|
|
||||||
color: $gray
|
|
||||||
|
|
||||||
@media (min-width: 768px)
|
|
||||||
padding-left: 0
|
|
||||||
padding-right: 0
|
|
||||||
|
|
||||||
.date, .tag
|
|
||||||
margin-top: 0.2em
|
|
||||||
|
|
||||||
.date, article
|
|
||||||
float: left
|
|
||||||
|
|
||||||
.tag
|
|
||||||
float: right
|
|
||||||
|
|
||||||
.article
|
|
||||||
color: $text
|
|
||||||
margin-right: 3px
|
|
||||||
transition: all 100ms linear
|
|
||||||
|
|
||||||
&:hover .article
|
|
||||||
color: $accent
|
|
||||||
|
|
||||||
&:nth-of-type(1) .article,
|
|
||||||
&:nth-of-type(2) .article,
|
|
||||||
&:nth-of-type(3) .article,
|
|
||||||
&:nth-of-type(4) .article
|
|
||||||
+bold-font
|
|
||||||
|
|
||||||
.tag
|
|
||||||
color: $gray
|
|
||||||
font-weight: normal
|
|
||||||
font-size: 0.8em
|
|
@ -1,23 +0,0 @@
|
|||||||
.post-item
|
|
||||||
max-width: $page-width
|
|
||||||
margin: 0 auto
|
|
||||||
|
|
||||||
.post-list
|
|
||||||
margin: 40px auto
|
|
||||||
padding: 20px
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
margin: 0 auto
|
|
||||||
|
|
||||||
.post-list > .post-item:not(:first-child)
|
|
||||||
&:before
|
|
||||||
content: ''
|
|
||||||
display: block
|
|
||||||
width: 150px
|
|
||||||
height: 1px
|
|
||||||
background: $hairline
|
|
||||||
margin: 7em auto
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
margin: 4em auto
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
.site-header
|
|
||||||
text-align: center
|
|
||||||
padding: 0 20px
|
|
||||||
|
|
||||||
.container
|
|
||||||
padding: 15px 20px
|
|
||||||
font-size: 0.9em
|
|
||||||
color: rgba($gray, 0.6)
|
|
||||||
border-bottom: solid 1px $hairline
|
|
||||||
|
|
||||||
em
|
|
||||||
+italic-font
|
|
||||||
+italic-font-size(1.1em)
|
|
||||||
font-size: 1.1em
|
|
||||||
color: $text
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* .social-list -- social share icons
|
|
||||||
*/
|
|
||||||
|
|
||||||
.social-list
|
|
||||||
&, li
|
|
||||||
margin: 0
|
|
||||||
padding: 0
|
|
||||||
|
|
||||||
&
|
|
||||||
display: block
|
|
||||||
text-align: center
|
|
||||||
width: 100%
|
|
||||||
margin-top: 2em
|
|
||||||
|
|
||||||
@media (min-width: 768px)
|
|
||||||
margin-top: 4em
|
|
||||||
|
|
||||||
&.-top
|
|
||||||
margin-top: -2em
|
|
||||||
|
|
||||||
@media (min-width: 768px)
|
|
||||||
margin-top: -1em
|
|
||||||
|
|
||||||
li
|
|
||||||
display: inline-block
|
|
||||||
|
|
||||||
a
|
|
||||||
display: inline-block
|
|
||||||
padding: 6px
|
|
||||||
text-align: center
|
|
||||||
box-shadow: none
|
|
||||||
|
|
||||||
a:before, a:after
|
|
||||||
transition: all 100ms linear
|
|
||||||
|
|
||||||
.text
|
|
||||||
display: none
|
|
||||||
|
|
||||||
a:before
|
|
||||||
font-family: 'Ionicons'
|
|
||||||
font-weight: normal
|
|
||||||
font-style: normal
|
|
||||||
font-size: 18px
|
|
||||||
width: 40px
|
|
||||||
height: 40px
|
|
||||||
line-height: 40px
|
|
||||||
display: inline-block
|
|
||||||
text-align: center
|
|
||||||
border: solid 2px transparent
|
|
||||||
border-radius: 50%
|
|
||||||
|
|
||||||
@mixin socialiconify($color, $content, $filled)
|
|
||||||
&
|
|
||||||
color: darken($lightgray, 10%)
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus
|
|
||||||
color: $color
|
|
||||||
&:before
|
|
||||||
content: $filled
|
|
||||||
&:hover:before,
|
|
||||||
&:focus:before
|
|
||||||
border-color: $color
|
|
||||||
content: $filled
|
|
||||||
|
|
||||||
.facebook a
|
|
||||||
+socialiconify(#4c66a4, '\f230', '\f231')
|
|
||||||
.twitter a
|
|
||||||
+socialiconify(dodgerblue, '\f242', '\f243')
|
|
||||||
.googleplus a
|
|
||||||
+socialiconify(#f53, '\f234', '\f235')
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
|||||||
/*
|
|
||||||
* html base (html, body)
|
|
||||||
*/
|
|
||||||
|
|
||||||
*
|
|
||||||
+antialias
|
|
||||||
|
|
||||||
html
|
|
||||||
font-size: $body-font-size
|
|
||||||
line-height: $body-line-height
|
|
||||||
background: $bg
|
|
||||||
color: $text
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
font-size: floor($body-font-size * 14/17)
|
|
||||||
|
|
||||||
html
|
|
||||||
transition: opacity 200ms linear
|
|
||||||
opacity: 1
|
|
||||||
|
|
||||||
html, input, textarea, td, th
|
|
||||||
+body-font
|
|
||||||
|
|
||||||
html, body
|
|
||||||
overflow-x: hidden
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fouc prevention
|
|
||||||
*/
|
|
||||||
|
|
||||||
body
|
|
||||||
transition: opacity 100ms linear
|
|
||||||
|
|
||||||
html.no-js *
|
|
||||||
opacity: 0
|
|
||||||
|
|
||||||
/*
|
|
||||||
* basic styles (a, p, img...)
|
|
||||||
*/
|
|
||||||
|
|
||||||
a, a:visited
|
|
||||||
color: $text
|
|
||||||
text-decoration: none
|
|
||||||
box-shadow: inset 0 -1px rgba(#888, 0.3)
|
|
||||||
transition: all 100ms linear
|
|
||||||
|
|
||||||
a:focus, a:hover
|
|
||||||
box-shadow: inset 0 -2px $accent
|
|
||||||
color: $black
|
|
||||||
|
|
||||||
strong, b
|
|
||||||
&, a, a:visited
|
|
||||||
color: $black
|
|
||||||
|
|
||||||
h3, p, ul, ol
|
|
||||||
margin: 1.5em 0
|
|
||||||
|
|
||||||
/*
|
|
||||||
* iframes
|
|
||||||
*/
|
|
||||||
|
|
||||||
p > iframe
|
|
||||||
margin: 0 auto
|
|
||||||
display: block
|
|
||||||
|
|
||||||
/*
|
|
||||||
* lists
|
|
||||||
*/
|
|
||||||
|
|
||||||
ul, ol
|
|
||||||
padding-left: 1.5em
|
|
||||||
|
|
||||||
ul
|
|
||||||
&
|
|
||||||
list-style: none
|
|
||||||
|
|
||||||
> li
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
> li:before
|
|
||||||
content: ''
|
|
||||||
display: block
|
|
||||||
position: absolute
|
|
||||||
left: -1.4em
|
|
||||||
top: 0
|
|
||||||
margin-top: 0.7em
|
|
||||||
width: 4px
|
|
||||||
height: 4px
|
|
||||||
border-radius: 50%
|
|
||||||
border: solid 2px $lightgray
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
width: 3px
|
|
||||||
height: 3px
|
|
||||||
|
|
||||||
/*
|
|
||||||
* headings
|
|
||||||
*/
|
|
||||||
|
|
||||||
h2
|
|
||||||
&, a, a:visited
|
|
||||||
color: $black
|
|
||||||
|
|
||||||
h2
|
|
||||||
text-align: center
|
|
||||||
+headline-font
|
|
||||||
+headline-font-size(2em, 1.4)
|
|
||||||
margin: 2em auto 0 auto
|
|
||||||
@media (max-width: 768px)
|
|
||||||
+headline-font-size(1.6em)
|
|
||||||
|
|
||||||
@media (min-width: 769px)
|
|
||||||
h2:before,
|
|
||||||
h2:after
|
|
||||||
content: ''
|
|
||||||
display: inline-block
|
|
||||||
vertical-align: middle
|
|
||||||
width: 46px
|
|
||||||
height: 1px
|
|
||||||
background: $lightgray
|
|
||||||
margin: 0 30px
|
|
||||||
|
|
||||||
h3
|
|
||||||
+bold-font
|
|
||||||
+bold-font-size(1.1em)
|
|
||||||
margin-top: 2em
|
|
||||||
|
|
||||||
&, a, a:visited
|
|
||||||
color: $black
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
margin-top: 1.5em
|
|
||||||
|
|
||||||
h3 + p
|
|
||||||
margin-top: -1.7em
|
|
||||||
|
|
||||||
/*
|
|
||||||
* images
|
|
||||||
*/
|
|
||||||
|
|
||||||
p > img:first-child:last-child
|
|
||||||
display: block
|
|
||||||
margin: 0 auto
|
|
||||||
max-width: 100%
|
|
||||||
|
|
||||||
code
|
|
||||||
+mono-font
|
|
||||||
+mono-font-size(0.9em)
|
|
||||||
|
|
||||||
hr
|
|
||||||
width: 200px
|
|
||||||
height: 1px
|
|
||||||
border: 0
|
|
||||||
background: $lightgray
|
|
||||||
margin: 3em auto
|
|
||||||
display: block
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
|||||||
$term-border: mix($accent, white, 50%)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* pre > code -- code blocks
|
|
||||||
*/
|
|
||||||
|
|
||||||
pre > code
|
|
||||||
+mono-font
|
|
||||||
+mono-font-size(0.82em)
|
|
||||||
color: $text
|
|
||||||
padding-right: 20px
|
|
||||||
// box-shadow: inset 1px 1px rgba(black, 0.06)
|
|
||||||
|
|
||||||
pre
|
|
||||||
padding: 20px 50px
|
|
||||||
border-radius: 4px
|
|
||||||
background: $wash
|
|
||||||
margin: 2.2em -50px
|
|
||||||
line-height: 1.2em
|
|
||||||
|
|
||||||
@media (min-width: 768px)
|
|
||||||
border-top: solid 1px #eef3fa
|
|
||||||
border-bottom: solid 1px #c7d7ee
|
|
||||||
border-radius: 4px
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
padding: 20px
|
|
||||||
margin: 2em -20px
|
|
||||||
background: darken($wash, 3%)
|
|
||||||
|
|
||||||
@media (max-width: 660px)
|
|
||||||
border-radius: 0
|
|
||||||
|
|
||||||
pre + pre
|
|
||||||
margin-top: -1.5em
|
|
||||||
|
|
||||||
pre.medium
|
|
||||||
> code
|
|
||||||
font-size: 1em
|
|
||||||
@media (min-width: 768px)
|
|
||||||
padding-top: 30px
|
|
||||||
padding-bottom: 30px
|
|
||||||
|
|
||||||
pre.large
|
|
||||||
> code
|
|
||||||
font-size: 1.1em
|
|
||||||
line-height: 1.4em
|
|
||||||
@media (min-width: 768px)
|
|
||||||
padding-top: 30px
|
|
||||||
padding-bottom: 30px
|
|
||||||
|
|
||||||
pre.terminal,
|
|
||||||
pre.light
|
|
||||||
&
|
|
||||||
background: white
|
|
||||||
border: solid 1px $term-border
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
pre.light
|
|
||||||
background: #fdfdff
|
|
||||||
|
|
||||||
pre.terminal
|
|
||||||
&
|
|
||||||
padding-top: 56px
|
|
||||||
|
|
||||||
&.large
|
|
||||||
padding-top: 65px
|
|
||||||
|
|
||||||
&:before
|
|
||||||
content: ''
|
|
||||||
display: block
|
|
||||||
height: 34px
|
|
||||||
line-height: 34px
|
|
||||||
background: white
|
|
||||||
border-bottom: solid 1px $term-border
|
|
||||||
border-top-left-radius: 3px
|
|
||||||
border-top-right-radius: 3px
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
+bold-font
|
|
||||||
|
|
||||||
&:after
|
|
||||||
content: ''
|
|
||||||
position: absolute
|
|
||||||
display: block
|
|
||||||
width: 4px
|
|
||||||
height: 4px
|
|
||||||
border-radius: 50%
|
|
||||||
left: 15px
|
|
||||||
top: 15px
|
|
||||||
font-size: 20px
|
|
||||||
background-color: $term-border
|
|
||||||
box-shadow: 20px 0 $term-border, 40px 0 $term-border
|
|
||||||
|
|
||||||
@media (max-width: 768px)
|
|
||||||
margin-left: 0
|
|
||||||
margin-right: 0
|
|
||||||
|
|
||||||
pre + pre
|
|
||||||
margin-top: -1.7em
|
|
||||||
|
|
||||||
@media (min-width: 769px)
|
|
||||||
pre.cursor > code > :last-child:after
|
|
||||||
content: ''
|
|
||||||
display: inline-block
|
|
||||||
width: 3px
|
|
||||||
height: 1em
|
|
||||||
transform: scaleY(1.5) translateY(0.1em)
|
|
||||||
margin-left: 0.4em
|
|
||||||
background-color: $accent
|
|
||||||
-webkit-animation: blink 700ms steps(2,end) infinite
|
|
||||||
-moz-animation: blink 700ms steps(2,end) infinite
|
|
||||||
animation: blink 700ms steps(2,end) infinite
|
|
||||||
|
|
||||||
h3 + pre,
|
|
||||||
h3 + table
|
|
||||||
margin-top: -1em
|
|
@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* table
|
|
||||||
*/
|
|
||||||
|
|
||||||
table
|
|
||||||
min-width: 100%
|
|
||||||
margin-top: 2em
|
|
||||||
margin-bottom: 2em
|
|
||||||
font-size: 0.9em
|
|
||||||
border-bottom: solid 1px $gray
|
|
||||||
|
|
||||||
thead > tr:first-child > th,
|
|
||||||
thead > tr:first-child > td,
|
|
||||||
tbody > tr:first-child > th,
|
|
||||||
tbody > tr:first-child > td
|
|
||||||
border-top: solid 1px $gray
|
|
||||||
|
|
||||||
td, th
|
|
||||||
text-align: left
|
|
||||||
border-top: solid 1px $hairline
|
|
||||||
padding: 8px 10px
|
|
||||||
|
|
||||||
th
|
|
||||||
+bold-font
|
|
||||||
color: $black
|
|
||||||
|
|
||||||
td:first-child,
|
|
||||||
th:first-child
|
|
||||||
text-align: left
|
|
||||||
padding-left: 0
|
|
||||||
|
|
||||||
td:last-child,
|
|
||||||
th:last-child
|
|
||||||
padding-right: 0
|
|
||||||
|
|
||||||
table.no-head
|
|
||||||
thead
|
|
||||||
display: none
|
|
||||||
|
|
||||||
table.shortcuts
|
|
||||||
&
|
|
||||||
table-layout: fixed
|
|
||||||
|
|
||||||
thead
|
|
||||||
display: none
|
|
||||||
|
|
||||||
th:nth-child(1), td:nth-child(1)
|
|
||||||
width: 25%
|
|
||||||
|
|
||||||
th:nth-child(2), td:nth-child(2)
|
|
||||||
width: 75%
|
|
||||||
|
|
||||||
td:first-child > code
|
|
||||||
background: #fcfcfc
|
|
||||||
padding: 5px 10px
|
|
||||||
border-radius: 2px
|
|
||||||
|
|
||||||
table.lite-headings
|
|
||||||
&
|
|
||||||
border-bottom: solid 1px mix($gray, $lightgray, 50%)
|
|
||||||
|
|
||||||
th
|
|
||||||
color: mix($gray, $lightgray, 50%)
|
|
||||||
font-size: 0.9em
|
|
||||||
|
|
||||||
thead > tr:first-child > td,
|
|
||||||
thead > tr:first-child > th
|
|
||||||
border-top: solid 1px mix($gray, $lightgray, 50%)
|
|
||||||
|
|
||||||
tbody > tr:first-child > td,
|
|
||||||
tbody > tr:first-child > th
|
|
||||||
border-top: solid 1px $hairline
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
@keyframes blink
|
|
||||||
0%
|
|
||||||
opacity: 0
|
|
||||||
100%
|
|
||||||
opacity: 1
|
|
||||||
|
|
||||||
@-webkit-keyframes blink
|
|
||||||
0%
|
|
||||||
opacity: 0
|
|
||||||
100%
|
|
||||||
opacity: 1
|
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* .center (et al) -- utility classes
|
|
||||||
*/
|
|
||||||
|
|
||||||
.center
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
.spaced
|
|
||||||
@media (min-width: 769px)
|
|
||||||
margin-top: 4em
|
|
||||||
margin-bottom: 4em
|
|
||||||
|
|
||||||
.spaced-more
|
|
||||||
@media (min-width: 769px)
|
|
||||||
margin-top: 6em
|
|
||||||
margin-bottom: 6em
|
|
||||||
|
|
||||||
.spaced-less
|
|
||||||
@media (min-width: 769px)
|
|
||||||
margin-top: 2em
|
|
||||||
margin-bottom: 2em
|
|
||||||
|
|
||||||
.wide
|
|
||||||
@media (min-width: 920px)
|
|
||||||
width: 140%
|
|
||||||
margin-left: -25%
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* .top/.bottom -- margin helpers
|
|
||||||
*/
|
|
||||||
|
|
||||||
.top-collapse-0
|
|
||||||
margin-top: 0
|
|
||||||
.top-collapse-1
|
|
||||||
margin-top: -1em
|
|
||||||
.top-collapse-2
|
|
||||||
margin-top: -2em
|
|
||||||
.top-collapse-4
|
|
||||||
margin-top: -4em
|
|
||||||
|
|
||||||
.top-space-0
|
|
||||||
margin-top: 0
|
|
||||||
.top-space-1
|
|
||||||
margin-top: 1em
|
|
||||||
.top-space-2
|
|
||||||
margin-top: 2em
|
|
||||||
.top-space-4
|
|
||||||
margin-top: 2em
|
|
||||||
@media (min-width: 769px)
|
|
||||||
margin-top: 4em
|
|
||||||
|
|
||||||
.bottom-collapse-0
|
|
||||||
margin-bottom: 0
|
|
||||||
.bottom-collapse-1
|
|
||||||
margin-bottom: -1em
|
|
||||||
.bottom-collapse-2
|
|
||||||
margin-bottom: -2em
|
|
||||||
.bottom-collapse-4
|
|
||||||
margin-bottom: -4em
|
|
||||||
|
|
||||||
.bottom-space-0
|
|
||||||
margin-bottom: 0
|
|
||||||
.bottom-space-1
|
|
||||||
margin-bottom: 1em
|
|
||||||
.bottom-space-2
|
|
||||||
margin-bottom: 2em
|
|
||||||
.bottom-space-4
|
|
||||||
margin-bottom: 2em
|
|
||||||
@media (min-width: 769px)
|
|
||||||
margin-bottom: 4em
|
|
@ -1,57 +0,0 @@
|
|||||||
// base colors
|
|
||||||
$text: #555
|
|
||||||
$black: #151515
|
|
||||||
$bg: #fff
|
|
||||||
$wash: #f4f6f8
|
|
||||||
|
|
||||||
// grays
|
|
||||||
$gray: #939aa1
|
|
||||||
$lightgray: #c0d3da
|
|
||||||
$hairline: #eef3fa
|
|
||||||
|
|
||||||
// accents
|
|
||||||
$accent: #1ea3ff
|
|
||||||
$accent2: #f53
|
|
||||||
|
|
||||||
// metrics
|
|
||||||
$page-width: 620px
|
|
||||||
|
|
||||||
@import url('//code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css')
|
|
||||||
@import url('//cdn.jsdelivr.net/hint.css/1.3.2/hint.min.css')
|
|
||||||
|
|
||||||
@import 'base/utils'
|
|
||||||
@import 'base/typography'
|
|
||||||
@import 'base/normalize'
|
|
||||||
|
|
||||||
@import 'elements/body'
|
|
||||||
@import 'elements/table'
|
|
||||||
@import 'elements/code'
|
|
||||||
|
|
||||||
@import 'components/about-the-site'
|
|
||||||
@import 'components/big-button'
|
|
||||||
@import 'components/brief-intro'
|
|
||||||
@import 'components/full-image'
|
|
||||||
@import 'components/hint'
|
|
||||||
@import 'components/hljs'
|
|
||||||
@import 'components/next-article'
|
|
||||||
@import 'components/post-headline'
|
|
||||||
@import 'components/post-icon'
|
|
||||||
@import 'components/post-index'
|
|
||||||
@import 'components/post-list'
|
|
||||||
@import 'components/site-header'
|
|
||||||
@import 'components/social-list'
|
|
||||||
|
|
||||||
@import 'helpers/general'
|
|
||||||
@import 'helpers/margins'
|
|
||||||
@import 'helpers/blink'
|
|
||||||
|
|
||||||
// Shim for headline-pub
|
|
||||||
$base-mute: #678
|
|
||||||
$base-b: #333
|
|
||||||
$base-b3: #678
|
|
||||||
$base-text: #333
|
|
||||||
$shadow3: 0 6px 8px rgba($base-mute, 0.03), 0 1px 2px rgba($base-mute, 0.30)
|
|
||||||
$shadow6: 0 6px 8px rgba($base-mute, 0.03), 0 1px 2px rgba($base-mute, 0.30), 0 8px 12px rgba($base-b3, 0.1)
|
|
||||||
$gray-text: $base-mute
|
|
||||||
|
|
||||||
@import '../2017/components/headline-pub.scss'
|
|
@ -1,30 +0,0 @@
|
|||||||
$bounce: cubic-bezier(0.75, -0.5, 0, 1.75);
|
|
||||||
|
|
||||||
/*
|
|
||||||
/* "Preloader":
|
|
||||||
* This makes the content semi-transparent before the page ad loads.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.post-content {
|
|
||||||
html.WithJs & {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Defer "loading" until page's onload event fires.
|
|
||||||
* (The page actually already loaded, we just pretend like it hasn't)
|
|
||||||
*/
|
|
||||||
|
|
||||||
.pages-list,
|
|
||||||
.post-content.-wrapified,
|
|
||||||
.intro-content {
|
|
||||||
html.WithJs & {
|
|
||||||
opacity: 0.98;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.WithJs.LoadDone & {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 100ms linear 100ms;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
.push-button {
|
|
||||||
@extend %push-button;
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
@import './variables';
|
|
||||||
@import '../vendor/modularscale/modularscale';
|
|
||||||
@import '../vendor/ionicons-inline/ionicons';
|
|
||||||
@import './utils/font-size';
|
|
||||||
@import './utils/gutter';
|
|
||||||
@import './utils/heading-style';
|
|
||||||
@import './utils/section-gutter';
|
|
||||||
@import './utils/section-with-container';
|
|
||||||
@import './placeholders/push-button';
|
|
||||||
@import './base/base';
|
|
||||||
@import './base/fade';
|
|
||||||
@import './markdown/a-em';
|
|
||||||
@import './markdown/code';
|
|
||||||
@import './markdown/headings';
|
|
||||||
@import './markdown/local-anchor';
|
|
||||||
@import './markdown/p';
|
|
||||||
@import './markdown/table';
|
|
||||||
@import './markdown/ul';
|
|
||||||
@import './components/attribute-peg';
|
|
||||||
@import './components/announcements-item';
|
|
||||||
@import './components/announcements-list';
|
|
||||||
@import './components/back-button';
|
|
||||||
@import './components/body-area';
|
|
||||||
@import './components/comments-area';
|
|
||||||
@import './components/comments-details';
|
|
||||||
@import './components/comments-section';
|
|
||||||
@import './components/h2-section';
|
|
||||||
@import './components/h3-section';
|
|
||||||
@import './components/h3-section-list';
|
|
||||||
@import './components/headline-pub';
|
|
||||||
@import './components/hint-mark';
|
|
||||||
@import './components/home-button';
|
|
||||||
@import './components/intro-content';
|
|
||||||
@import './components/main-heading';
|
|
||||||
@import './components/missing-message';
|
|
||||||
@import './components/notice-box';
|
|
||||||
@import './components/page-actions';
|
|
||||||
@import './components/pages-list';
|
|
||||||
@import './components/pre-footer';
|
|
||||||
@import './components/push-button';
|
|
||||||
@import './components/related-posts-area';
|
|
||||||
@import './components/related-posts-callout';
|
|
||||||
@import './components/related-posts-group';
|
|
||||||
@import './components/related-posts-section';
|
|
||||||
@import './components/related-post-list';
|
|
||||||
@import './components/related-post-item';
|
|
||||||
@import './components/search-box';
|
|
||||||
@import './components/search-footer';
|
|
||||||
@import './components/site-header';
|
|
||||||
@import './components/top-nav';
|
|
||||||
@import './components/top-sheet';
|
|
@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
(
|
|
||||||
echo https://devhints.io/
|
|
||||||
(
|
|
||||||
git ls-files \
|
|
||||||
| grep -E '\.md$' \
|
|
||||||
| grep -v -E 'CONTRIBUTING|README|Readme' \
|
|
||||||
| grep -v -E '^_' \
|
|
||||||
| sort \
|
|
||||||
| uniq \
|
|
||||||
| sed 's/\.md$//g'
|
|
||||||
) \
|
|
||||||
| sed 's#^#https://devhints.io/#g'
|
|
||||||
) \
|
|
||||||
| xargs curl >/dev/null
|
|
@ -1,23 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Helper to copy the latest cheatsheets to the clipboard for CloudFlare
|
|
||||||
# purging. This ensures visitors will see new versions.
|
|
||||||
|
|
||||||
(
|
|
||||||
git log "master@{3 days ago}..HEAD" --pretty="" --name-only \
|
|
||||||
| grep -E '\.md$' \
|
|
||||||
| grep -v -E 'CONTRIBUTING|README|Readme' \
|
|
||||||
| grep -v -E '^_' \
|
|
||||||
| sort \
|
|
||||||
| uniq \
|
|
||||||
| sed 's/\.md$//g'
|
|
||||||
) \
|
|
||||||
| sed 's#^#https://devhints.io/#g' \
|
|
||||||
| xargs echo https://devhints.io/ \
|
|
||||||
| pbcopy
|
|
||||||
|
|
||||||
echo "Copied to clipboard."
|
|
||||||
echo "Purge it here:"
|
|
||||||
echo ""
|
|
||||||
echo " https://www.cloudflare.com/a/caching/devhints.io"
|
|
||||||
echo ""
|
|
||||||
echo "Then click 'Purge Individual Files'"
|
|
@ -1,32 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -eou pipefail
|
|
||||||
|
|
||||||
exit_failure() {
|
|
||||||
echo 'Failed :('
|
|
||||||
echo ''
|
|
||||||
echo ' If your build failed at this point, it means'
|
|
||||||
echo ' the site failed to generate. Check the project'
|
|
||||||
echo ' out locally and try to find out why.'
|
|
||||||
}
|
|
||||||
|
|
||||||
trap exit_failure ERR
|
|
||||||
|
|
||||||
|
|
||||||
files=(
|
|
||||||
_site/vim.html
|
|
||||||
_site/react.html
|
|
||||||
_site/index.html
|
|
||||||
)
|
|
||||||
|
|
||||||
for fn in "${files[@]}"; do
|
|
||||||
echo ''
|
|
||||||
echo -n "→ Checking: $fn... "
|
|
||||||
test -f "$fn"
|
|
||||||
grep -q '<script src' "$fn"
|
|
||||||
grep -q 'assets/packed/app.js' "$fn"
|
|
||||||
grep -q 'doctype html' "$fn"
|
|
||||||
grep -q 'link rel="canonical"' "$fn"
|
|
||||||
done
|
|
||||||
echo ''
|
|
||||||
echo ''
|
|
||||||
echo "✓ Smoke tests good :)"
|
|
@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
type: other
|
|
||||||
---
|
|
||||||
@import '2015/style.sass'
|
|
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
type: other
|
|
||||||
---
|
|
||||||
// Generated by parcel (relative to _sass)
|
|
||||||
@import '../../assets/packed/app.css';
|
|
||||||
|
|
||||||
// From _sass
|
|
||||||
@import '2017/style.scss';
|
|
@ -1,48 +0,0 @@
|
|||||||
.site-header, .social-list, .about-the-site,
|
|
||||||
#see-also, #see-also+ul {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/*.post-headline.-cheatsheet .prelude span:before {
|
|
||||||
content: 'cheatsheet for'
|
|
||||||
}*/
|
|
||||||
.post-list {
|
|
||||||
margin: 0;
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
.post-item {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
.post-headline.-cheatsheet .prelude {
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
.post-headline, p.prelude {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.post-headline.-cheatsheet .prelude {
|
|
||||||
font-size: 0.6em;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
.post-headline {
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
}
|
|
||||||
.post-headline.-cheatsheet .prelude span {
|
|
||||||
padding: 0.75em 20px;
|
|
||||||
border-bottom: solid 1.5px #111;
|
|
||||||
}
|
|
||||||
.post-headline.-cheatsheet h1 {
|
|
||||||
font-size: 1.75em;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
border-top: solid 1px #ddd;
|
|
||||||
border-bottom: solid 1px #ddd;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
margin-top: 1.1em;
|
|
||||||
margin-bottom: 1.1em;
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var tags = document.querySelectorAll('h1,h2,h3,h4,h5,h6,li,p,span');
|
|
||||||
|
|
||||||
for (var i=0, len=tags.length; i<len; i++) {
|
|
||||||
var tag = tags[i];
|
|
||||||
if ((~tag.innerHTML.indexOf('>') ||
|
|
||||||
(!tag.innerHTML.match(/https?:\/\//))))
|
|
||||||
continue;
|
|
||||||
tag.innerHTML = tag.innerHTML.replace(/https?:\/\/[^ ]*/g, function (url) {
|
|
||||||
url = url.replace(/[\.\),!]*$/, '');
|
|
||||||
var label = url;
|
|
||||||
label = label.replace(/^https?:\/\//, '')
|
|
||||||
.replace(/\/$/, '');
|
|
||||||
return "<a href='"+url+"'>"+label+"</a>";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* unorphan */
|
|
||||||
(function () {
|
|
||||||
var els = document.querySelectorAll('h1 a, h1, h2, p.brief-intro, .pull-quote');
|
|
||||||
for (var i = 0, len = els.length; i < len; i++) {
|
|
||||||
var el = els[i];
|
|
||||||
var last = el.lastChild;
|
|
||||||
|
|
||||||
if (last && last.nodeType === 3) {
|
|
||||||
console.log('=>', last.nodeValue, last.nodeValue.replace(/\s+([^\s]+\s*)$/g, '\xA0$1'));
|
|
||||||
last.nodeValue = last.nodeValue.replace(/\s+([^\s]+\s*)$/g, '\xA0$1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* loaded */
|
|
||||||
document.documentElement.className += ' loaded';
|
|
||||||
|
|
||||||
/* hljs */
|
|
||||||
(function () {
|
|
||||||
var codes = document.querySelectorAll('pre > code');
|
|
||||||
for (var i = 0, len = codes.length; i < len; i++) {
|
|
||||||
var block = codes[i];
|
|
||||||
hljs.highlightBlock(block);
|
|
||||||
}
|
|
||||||
})();
|
|
201
assets/style.css
201
assets/style.css
@ -1,201 +0,0 @@
|
|||||||
/*
|
|
||||||
* pages list
|
|
||||||
*/
|
|
||||||
|
|
||||||
.pages-list {
|
|
||||||
font-size: 0.9em;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 60px auto;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pages-list a {
|
|
||||||
display: block;
|
|
||||||
padding: 6px 0;
|
|
||||||
text-align: left;
|
|
||||||
float: left;
|
|
||||||
width: 44%;
|
|
||||||
margin: 0 3%;
|
|
||||||
box-shadow: none;
|
|
||||||
transition: all 100ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pages-list a .title,
|
|
||||||
.pages-list a .date {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pages-list a .title {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #111;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pages-list a .date {
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 0.9em;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pages-list a:hover .title,
|
|
||||||
.pages-list a:focus .title {
|
|
||||||
color: dodgerblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* post headline
|
|
||||||
*/
|
|
||||||
|
|
||||||
.post-headline.-cheatsheet .prelude {
|
|
||||||
color: #111;
|
|
||||||
font-size: 0.85em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-headline.-cheatsheet .prelude span:before {
|
|
||||||
content: 'Cheatsheet for';
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
|
||||||
.post-headline.-cheatsheet .prelude {
|
|
||||||
max-width: 230px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
.post-headline.-cheatsheet .prelude span {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-headline.-cheatsheet h1 {
|
|
||||||
color: #111;
|
|
||||||
font-size: 3.5em;
|
|
||||||
text-shadow: 2px 2px 0 white, 3px 3px 0 #ddd;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* about the site
|
|
||||||
*/
|
|
||||||
|
|
||||||
.about-the-site {
|
|
||||||
margin-top: 8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-the-site .back {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* markdown
|
|
||||||
*/
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
h2 {
|
|
||||||
margin-top: 3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* grey code
|
|
||||||
*/
|
|
||||||
|
|
||||||
.greycode td:first-child code,
|
|
||||||
.greycode th:first-child code {
|
|
||||||
background: white;
|
|
||||||
padding: 6px 8px 5px 8px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greycode td:first-child code + em,
|
|
||||||
.greycode th:first-child code + em {
|
|
||||||
color: #808890;
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greycode a {
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
table.greycode {
|
|
||||||
background: #fcfcfc;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-top: 0;
|
|
||||||
border-bottom: solid 1px #c7d7ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.greycode:not(.wide) {
|
|
||||||
width: calc(620px + 100px);
|
|
||||||
margin-left: -50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.greycode thead:first-child > tr:first-child > th,
|
|
||||||
table.greycode thead:first-child > tr:first-child > td,
|
|
||||||
table.greycode tbody:first-child > tr:first-child > th,
|
|
||||||
table.greycode tbody:first-child > tr:first-child > td,
|
|
||||||
table.greycode.no-head thead:nth-child(2) > tr:first-child > th,
|
|
||||||
table.greycode.no-head thead:nth-child(2) > tr:first-child > td,
|
|
||||||
table.greycode.no-head tbody:nth-child(2) > tr:first-child > th,
|
|
||||||
table.greycode.no-head tbody:nth-child(2) > tr:first-child > td {
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.greycode thead > tr:first-child > th,
|
|
||||||
table.greycode thead > tr:first-child > td,
|
|
||||||
table.greycode tbody > tr:first-child > th,
|
|
||||||
table.greycode tbody > tr:first-child > td {
|
|
||||||
border-top: solid 1px #c7d7ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.greycode td:first-child,
|
|
||||||
table.greycode th:first-child {
|
|
||||||
padding-left: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.greycode td:last-child,
|
|
||||||
table.greycode th:last-child {
|
|
||||||
padding-right: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-comment {
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-codes code {
|
|
||||||
background: #fdfdff;
|
|
||||||
padding: 3px 8px 3px 8px;
|
|
||||||
border-radius: 3px;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-codes code + code {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-codes pre code {
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-list.-collapse {
|
|
||||||
margin-top: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 40vh;
|
|
||||||
left: 30px;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.social-list.-collapse {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
24
astro.config.mjs
Normal file
24
astro.config.mjs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { defineConfig } from 'astro/config'
|
||||||
|
import partytown from '@astrojs/partytown'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://astro.build/config
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
site: 'https://devhints.io',
|
||||||
|
build: {
|
||||||
|
format: 'file' /* generate /my-post.html instead of /my-post/index.html */
|
||||||
|
},
|
||||||
|
prefetch: {
|
||||||
|
prefetchAll: true
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: true
|
||||||
|
} /* access from https://192.168.x.x/ */,
|
||||||
|
integrations: [partytown({ config: { forward: ['dataLayer.push'] } })],
|
||||||
|
|
||||||
|
markdown: {
|
||||||
|
// Syntax highlighting is handled by render()
|
||||||
|
syntaxHighlight: false
|
||||||
|
}
|
||||||
|
})
|
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
layout: blank
|
|
||||||
type: other
|
|
||||||
---
|
|
||||||
{% assign pages = site.pages
|
|
||||||
| where_exp: "page", 'page.type == "article"'
|
|
||||||
%}[
|
|
||||||
{% for page in pages
|
|
||||||
%}{% if forloop.index0 != 0 %},{% endif %}{
|
|
||||||
"id": {{ page.url | replace: ".html", "" | slice: 1, 9999 | jsonify }},
|
|
||||||
"title": {{ page.title | jsonify }},
|
|
||||||
"url": {{ page.url | replace: ".html", "" | jsonify }},
|
|
||||||
"category": {{ page.category | jsonify }},
|
|
||||||
"keywords": {{ page.keywords | jsonify }},
|
|
||||||
"content_html": {{ page.content | markdownify | strip | jsonify }},
|
|
||||||
"intro_html": {{ page.intro | markdownify | strip | jsonify }},
|
|
||||||
"description_html": {{ page.description | markdownify | strip | jsonify }},
|
|
||||||
"tags": {{ page.tags | jsonify }},
|
|
||||||
"updated": {{ page.updated | jsonify }}
|
|
||||||
}{% endfor %}
|
|
||||||
]
|
|
@ -1,19 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: .
|
|
||||||
volumes:
|
|
||||||
- .:/app
|
|
||||||
- rubygems:/usr/local/bundle
|
|
||||||
- ./node_modules:/app/node_modules
|
|
||||||
- yarn_cache:/root/.cache/yarn
|
|
||||||
ports:
|
|
||||||
- '4001:4001'
|
|
||||||
- '35729:35729'
|
|
||||||
command: >
|
|
||||||
bash -c 'yarn; bundle; env PORT=4001 HOST=0.0.0.0 yarn run dev'
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
rubygems:
|
|
||||||
node_modules:
|
|
||||||
yarn_cache:
|
|
258
netlify.toml
258
netlify.toml
@ -1,139 +1,121 @@
|
|||||||
|
# Note: Please don't edit redirects by hand.
|
||||||
|
# redirects are managed via src/redirects.ts.
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/brew"
|
||||||
|
to = "/homebrew"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/commander-js"
|
||||||
|
to = "/commander.js"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/css-animation"
|
||||||
|
to = "/css#animation"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/css-background"
|
||||||
|
to = "/css#background"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/css-font"
|
||||||
|
to = "/css#fonts"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/css-selectors"
|
||||||
|
to = "/css#selectors"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/date"
|
||||||
|
to = "/datetime"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/es2015"
|
||||||
|
to = "/es6"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/es2016"
|
||||||
|
to = "/es6"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/es2017"
|
||||||
|
to = "/es6"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/es2018"
|
||||||
|
to = "/es6"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/expect.js"
|
||||||
|
to = "/expectjs"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/factory_girl"
|
||||||
|
to = "/factory_bot"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/fetch"
|
||||||
|
to = "/js-fetch"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/flexbox"
|
||||||
|
to = "/css-flexbox"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/flowtype"
|
||||||
|
to = "/flow"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/gpgconf"
|
||||||
|
to = "/gnupg"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/gpg"
|
||||||
|
to = "/gnupg"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/gutom"
|
||||||
|
to = "/ph-food-delivery"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/handlebars-js"
|
||||||
|
to = "/handlebars.js"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/harvey-js"
|
||||||
|
to = "/harvey.js"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/immutable-js"
|
||||||
|
to = "/immutable.js"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/jade"
|
||||||
|
to = "/pug"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/jinja2"
|
||||||
|
to = "/jinja"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/package.json"
|
||||||
|
to = "/package-json"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/package"
|
||||||
|
to = "/package-json"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/phoenix-ecto@1.3"
|
||||||
|
to = "/phoenix-ecto"
|
||||||
|
[[redirects]]
|
||||||
|
force = true
|
||||||
|
from = "/sh"
|
||||||
|
to = "/bash"
|
||||||
[build]
|
[build]
|
||||||
command = "yarn build"
|
command = "npm run build"
|
||||||
publish = "_site/"
|
publish = "_site/"
|
||||||
environment = { NODE_VERSION = "20.9.0", RUBY_VERSION = "3.2.3", PYTHON_VERSION = "3.8" }
|
[build.environment]
|
||||||
|
NODE_VERSION = "18.12.0"
|
||||||
[[redirects]]
|
PYTHON_VERSION = "3.8"
|
||||||
force = true
|
RUBY_VERSION = "2.7.6"
|
||||||
from = "/flexbox"
|
|
||||||
to = "/css-flexbox"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/flowtype"
|
|
||||||
to = "/flow"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/gpgconf"
|
|
||||||
to = "/gnupg"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/gpg"
|
|
||||||
to = "/gnupg"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/gutom"
|
|
||||||
to = "/ph-food-delivery"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/handlebars-js"
|
|
||||||
to = "/handlebars.js"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/harvey-js"
|
|
||||||
to = "/harvey.js"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/immutable-js"
|
|
||||||
to = "/immutable.js"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/jade"
|
|
||||||
to = "/pug"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/package"
|
|
||||||
to = "/package-json"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/package.json"
|
|
||||||
to = "/package-json"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/phoenix-ecto@1.3"
|
|
||||||
to = "/phoenix-ecto"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/sh"
|
|
||||||
to = "/bash"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/commander-js"
|
|
||||||
to = "/commander.js"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/es2015"
|
|
||||||
to = "/es6"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/es2016"
|
|
||||||
to = "/es6"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/es2017"
|
|
||||||
to = "/es6"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/es2018"
|
|
||||||
to = "/es6"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/expect.js"
|
|
||||||
to = "/expectjs"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/factory_girl"
|
|
||||||
to = "/factory_bot"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/css-animation"
|
|
||||||
to = "/css#animation"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/css-background"
|
|
||||||
to = "/css#background"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/css-font"
|
|
||||||
to = "/css#fonts"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/css-selectors"
|
|
||||||
to = "/css#selectors"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/brew"
|
|
||||||
to = "/homebrew"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/date"
|
|
||||||
to = "/datetime"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
force = true
|
|
||||||
from = "/fetch"
|
|
||||||
to = "/js-fetch"
|
|
||||||
|
113
package.json
113
package.json
@ -1,64 +1,61 @@
|
|||||||
{
|
{
|
||||||
"name": "cheatsheets",
|
"name": "devhints-astro",
|
||||||
"description": "Devhints.io",
|
"version": "0.0.1",
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "Rico Sta. Cruz <rstacruz@users.noreply.github.com>",
|
|
||||||
"dependencies": {
|
|
||||||
"autoprefixer": "^9.8.2",
|
|
||||||
"dom101": "^2.2.1",
|
|
||||||
"hint.css": "^2.6.0",
|
|
||||||
"isotope-layout": "^3.0.6",
|
|
||||||
"lodash.noop": "^3.0.1",
|
|
||||||
"modularscale-sass": "^3.0.10",
|
|
||||||
"onmount": "^1.3.0",
|
|
||||||
"postcss-modules": "^2.0.0",
|
|
||||||
"prismjs": "^1.20.0",
|
|
||||||
"sanitize.css": "^11.0.1",
|
|
||||||
"sass": "^1.26.8"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/core": "^7.10.2",
|
|
||||||
"@babel/preset-env": "^7.10.2",
|
|
||||||
"@rstacruz/prettier-plugin-markdown-code-fences": "^1.0.0",
|
|
||||||
"jest": "26.0.1",
|
|
||||||
"jest-html": "1.5.0",
|
|
||||||
"netlify-plugin-minify-html": "^0.2.3",
|
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"parcel-bundler": "^1.12.4",
|
|
||||||
"prettier": "^2.0.5",
|
|
||||||
"wait-on": "^5.0.1"
|
|
||||||
},
|
|
||||||
"homepage": "https://devhints.io/",
|
|
||||||
"jest": {
|
|
||||||
"snapshotSerializers": [
|
|
||||||
"<rootDir>/node_modules/jest-html"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "index.js",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": "https://github.com/rstacruz/cheatsheets.git",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "run-s -s 'parcel:*:build' jekyll:build",
|
"astro": "astro",
|
||||||
"dev": "run-p -sl jekyll:watch 'parcel:*:watch'",
|
"build": "concurrently -m 1 \"npm:cache_markdown\" \"astro build --silent\"",
|
||||||
"jekyll:build": "bundle exec jekyll build",
|
"cache_markdown": "bundle exec ruby src/ruby/cache_kramdown.rb *.md */*.md",
|
||||||
"jekyll:watch": "wait-on assets/packed/app.js && wait-on _includes/2017/critical/critical-sheet.css && bundle exec jekyll serve --safe --trace --drafts --watch --incremental --host ${HOST:-0.0.0.0} --port ${PORT:-3000}",
|
"ci": "concurrently -g \"npm:*:check\" \"vitest run\" \"npm:test:*\" \"npm:build\"",
|
||||||
"jest-html": "jest-html",
|
"dev": "astro dev",
|
||||||
"parcel:app:build": "parcel build '_parcel/app.js' -d assets/packed --no-source-maps --no-autoinstall",
|
"eslint:check": "eslint .",
|
||||||
"parcel:app:watch": "parcel watch '_parcel/app.js' -d assets/packed --no-source-maps --no-autoinstall",
|
"eslint:format": "eslint --fix .",
|
||||||
"parcel:build": "run-s -s 'parcel:*:build'",
|
"format": "concurrently -m 1 \"npm:*:format\"",
|
||||||
"parcel:critical:build": "parcel build '_parcel/critical*.js' -d _includes/2017/critical --no-source-maps --no-autoinstall",
|
"prettier:check": "prettier --cache --list-different .",
|
||||||
"parcel:critical:watch": "parcel watch '_parcel/critical*.js' -d _includes/2017/critical --no-source-maps --no-autoinstall",
|
"prettier:format": "prettier --cache --write .",
|
||||||
"predev": "rm -rf assets/packed _includes/2017/critical",
|
"preview": "astro preview",
|
||||||
"prejekyll:build": "bundle",
|
"start": "astro dev",
|
||||||
"prejekyll:watch": "bundle",
|
"test": "vitest",
|
||||||
"prettier:format": "prettier --write '_parcel/**/*.{js,scss}'",
|
"test:playwright": "playwright test",
|
||||||
"test": "jest",
|
"test:ruby": "bundle exec ruby src/ruby/*.test.rb"
|
||||||
"test:smoke": "bash _support/smoke_test.sh"
|
|
||||||
},
|
},
|
||||||
"volta": {
|
"dependencies": {
|
||||||
"node": "18.19.1",
|
"@astrojs/partytown": "^2.0.2",
|
||||||
"yarn": "1.22.22"
|
"@astrojs/ts-plugin": "^1.3.1",
|
||||||
|
"@fontsource/cousine": "^5.0.15",
|
||||||
|
"@mapbox/rehype-prism": "^0.8.0",
|
||||||
|
"@playwright/test": "^1.38.1",
|
||||||
|
"@rstacruz/rehype-sectionize": "^0.7.0",
|
||||||
|
"@types/mapbox__rehype-prism": "^0.8.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
|
"@typescript-eslint/parser": "^7.2.0",
|
||||||
|
"astro": "^4.0.3",
|
||||||
|
"autocompleter": "^9.1.0",
|
||||||
|
"concurrently": "^8.2.1",
|
||||||
|
"eslint": "^8.52.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-astro": "^0.31.4",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
|
"fuse.js": "^6.6.2",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"happy-dom": "^12.9.1",
|
||||||
|
"hint.css": "^2.7.0",
|
||||||
|
"html-inline-external": "^1.0.10",
|
||||||
|
"playwright": "^1.38.1",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"prettier-plugin-astro": "^0.13.0",
|
||||||
|
"prettier-plugin-organize-imports": "^3.2.3",
|
||||||
|
"prismjs": "1.29.0",
|
||||||
|
"rehype-parse": "^9.0.0",
|
||||||
|
"rehype-stringify": "^10.0.0",
|
||||||
|
"sanitize.css": "13.0.0",
|
||||||
|
"sass": "^1.69.1",
|
||||||
|
"snarkdown": "^2.0.0",
|
||||||
|
"tsx": "^4.7.1",
|
||||||
|
"unified": "^11.0.3",
|
||||||
|
"vitest": "^1.4.0",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
"packageManager": "pnpm@8.15.4+sha256.cea6d0bdf2de3a0549582da3983c70c92ffc577ff4410cbf190817ddc35137c2"
|
||||||
}
|
}
|
||||||
|
20
playwright.config.ts
Normal file
20
playwright.config.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { defineConfig } from '@playwright/test'
|
||||||
|
|
||||||
|
const port = process.env.PORT ?? 4321
|
||||||
|
|
||||||
|
const isCI = Boolean(process.env.CI)
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testMatch: /.*\.e2e\.[^.]*/,
|
||||||
|
webServer: {
|
||||||
|
command: `npm run dev -- --port ${port}`,
|
||||||
|
url: `http://localhost:${port}/`,
|
||||||
|
reuseExistingServer: !isCI /* Spawn dev server on CI */
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
timeout: isCI ? 5000 : 2500 /* default: 5000 */
|
||||||
|
},
|
||||||
|
use: {
|
||||||
|
baseURL: `http://localhost:${port}/`
|
||||||
|
}
|
||||||
|
})
|
7826
pnpm-lock.yaml
generated
Normal file
7826
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@
|
|||||||
Powerline:
|
|
||||||
⮂
|
|
||||||
⮀
|
|
||||||
⮃
|
|
||||||
⮁
|
|
||||||
⭤
|
|
||||||
⭡
|
|
||||||
⭠
|
|
||||||
|
|
||||||
|
|
||||||
┌─┐
|
|
||||||
└─
|
|
||||||
✈
|
|
||||||
⋅
|
|
2
public/_headers
Normal file
2
public/_headers
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/_astro/*
|
||||||
|
Cache-Control: public, max-age=604800, immutable
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
10
sitemap.xml
10
sitemap.xml
@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
layout: blank
|
|
||||||
type: other
|
|
||||||
---
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
<url><loc>{{ site.url }}/</loc></url>
|
|
||||||
{% for page in site.pages %}{% if page.type == 'article' %}<url><loc>{{ site.url }}{{ page.url | remove: '.html' }}</loc></url>
|
|
||||||
{% endif %}{% endfor %}
|
|
||||||
</urlset>
|
|
11
src/analytics/CloudflareAnalytics.astro
Normal file
11
src/analytics/CloudflareAnalytics.astro
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
type Props = { token: string }
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const token = { props }
|
||||||
|
---
|
||||||
|
|
||||||
|
<script
|
||||||
|
defer
|
||||||
|
type="text/partytown"
|
||||||
|
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||||
|
data-cf-beacon={`{"token": "${token}"}`}></script>
|
25
src/analytics/GoogleAnalytics.astro
Normal file
25
src/analytics/GoogleAnalytics.astro
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
type Props = { measurementId: string }
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const measurementId = props.measurementId
|
||||||
|
---
|
||||||
|
|
||||||
|
<script
|
||||||
|
type="text/partytown"
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}></script>
|
||||||
|
{/* prettier-ignore */}
|
||||||
|
<script
|
||||||
|
type="text/partytown"
|
||||||
|
data-ga-measurement-id={measurementId}
|
||||||
|
id="ga-init"
|
||||||
|
>
|
||||||
|
const measurementId = document
|
||||||
|
.getElementById('ga-init')
|
||||||
|
.getAttribute('data-ga-measurement-id')
|
||||||
|
window.dataLayer = window.dataLayer || []
|
||||||
|
function gtag() {
|
||||||
|
dataLayer.push(arguments) // eslint-disable-line
|
||||||
|
}
|
||||||
|
gtag('js', new Date())
|
||||||
|
gtag('config', measurementId)
|
||||||
|
</script>
|
42
src/components/BaseLayout.astro
Normal file
42
src/components/BaseLayout.astro
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
import '../sass/full.scss'
|
||||||
|
import '@fontsource/cousine/400.css'
|
||||||
|
import '@fontsource/cousine/700.css'
|
||||||
|
import GoogleAnalytics from '~/analytics/GoogleAnalytics.astro'
|
||||||
|
import CloudflareAnalytics from '~/analytics/CloudflareAnalytics.astro'
|
||||||
|
import { cloudflareAnalytics, googleAnalytics } from '~/config'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
title?: string
|
||||||
|
bodyClass?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const analyticsEnabled = import.meta.env.PROD || true
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<link rel="shortcut icon" type="image/png" href="/assets/favicon.png" />
|
||||||
|
{/* Title */}
|
||||||
|
{props.title ? <title>{props.title}</title> : null}
|
||||||
|
{/* Google tag */}
|
||||||
|
{
|
||||||
|
analyticsEnabled && googleAnalytics.measurementId ? (
|
||||||
|
<GoogleAnalytics measurementId={googleAnalytics.measurementId} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
analyticsEnabled && cloudflareAnalytics.token ? (
|
||||||
|
<CloudflareAnalytics token={cloudflareAnalytics.token} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
<slot name="head" />
|
||||||
|
</head>
|
||||||
|
<body class={props.bodyClass ?? ''}>
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
46
src/components/SEO/SEO.astro
Normal file
46
src/components/SEO/SEO.astro
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
/*
|
||||||
|
* Simplified replacement for astro-seo. Is less opinionated
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
title?: string
|
||||||
|
meta?: Record<string, string | string[]>
|
||||||
|
metaProperties?: Record<string, string | string[]>
|
||||||
|
links?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
|
||||||
|
function toArray(input: string | string[]): string[] {
|
||||||
|
return (Array.isArray(input) ? input : [input]).filter(Boolean)
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
{props.title ? <title>{props.title}</title> : null}
|
||||||
|
|
||||||
|
{
|
||||||
|
props.meta
|
||||||
|
? Object.entries(props.meta).flatMap(([name, contents]) =>
|
||||||
|
toArray(contents).map((content) => (
|
||||||
|
<meta name={name} content={content} />
|
||||||
|
))
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.metaProperties
|
||||||
|
? Object.entries(props.metaProperties).flatMap(([property, contents]) =>
|
||||||
|
toArray(contents).map((content) => (
|
||||||
|
<meta property={property} content={content} />
|
||||||
|
))
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.links
|
||||||
|
? Object.entries(props.links).map(([rel, href]) => (
|
||||||
|
<link rel={rel} href={href} />
|
||||||
|
))
|
||||||
|
: null
|
||||||
|
}
|
52
src/components/SocialList.astro
Normal file
52
src/components/SocialList.astro
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
import { getUrlFromPage } from '~/lib/links'
|
||||||
|
import type { SheetPage } from '~/lib/page'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
class?: string
|
||||||
|
page?: SheetPage
|
||||||
|
}
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const page = props.page
|
||||||
|
const url = getUrlFromPage(page, Astro.site)
|
||||||
|
|
||||||
|
const t = {
|
||||||
|
facebookShare: 'Share on Facebook',
|
||||||
|
twitterShare: 'Share on Twitter',
|
||||||
|
sheetDescription: 'The ultimate cheatsheet for {title}',
|
||||||
|
defaultDescription: 'Ridiculous collection of web development cheatsheets'
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = page?.frontmatter?.title
|
||||||
|
|
||||||
|
const description = title
|
||||||
|
? t.sheetDescription.replace('{title}', title)
|
||||||
|
: t.defaultDescription
|
||||||
|
|
||||||
|
const tweet = `${description} ${url}`
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const facebookURL = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`
|
||||||
|
// prettier-ignore
|
||||||
|
const twitterURL = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweet)}`
|
||||||
|
---
|
||||||
|
|
||||||
|
<ul class={`social-list ${props.class ?? ''}`}>
|
||||||
|
<li class="facebook link hint--bottom" data-hint={t.facebookShare}>
|
||||||
|
<a
|
||||||
|
href={facebookURL}
|
||||||
|
target="share"
|
||||||
|
aria-label={t.facebookShare}
|
||||||
|
role="button"><span class="text"></span></a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{' '}
|
||||||
|
<li class="twitter link hint--bottom" data-hint={t.twitterShare}>
|
||||||
|
<a
|
||||||
|
href={twitterURL}
|
||||||
|
target="share"
|
||||||
|
aria-label={t.twitterShare}
|
||||||
|
role="button"><span class="text"></span></a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
67
src/components/TopNav.astro
Normal file
67
src/components/TopNav.astro
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
import type { SheetPage } from '~/lib/page'
|
||||||
|
import SocialList from './SocialList.astro'
|
||||||
|
import { getEditLink } from '~/lib/links'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
noBack?: boolean
|
||||||
|
noShare?: boolean
|
||||||
|
noEdit?: boolean
|
||||||
|
page?: SheetPage
|
||||||
|
}
|
||||||
|
const props = Astro.props as Props
|
||||||
|
|
||||||
|
const t = {
|
||||||
|
title: 'Devhints.io',
|
||||||
|
editOnGitHub: 'Edit on GitHub',
|
||||||
|
edit: 'Edit',
|
||||||
|
backToHome: 'Back to home'
|
||||||
|
}
|
||||||
|
|
||||||
|
const editLink = getEditLink(props.page)
|
||||||
|
---
|
||||||
|
|
||||||
|
<nav class="top-nav" data-js-no-preview role="navigation">
|
||||||
|
<div class="container">
|
||||||
|
{
|
||||||
|
props.noBack !== true ? (
|
||||||
|
<div class="left">
|
||||||
|
<a class="home back-button" href="/" aria-label={t.backToHome} />
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
<a class="brand" href="/">
|
||||||
|
{t.title}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{
|
||||||
|
props.noShare !== true ? (
|
||||||
|
<div class="actions">
|
||||||
|
{/* Social share links */}
|
||||||
|
<SocialList class="social page-actions" page={props.page} />
|
||||||
|
|
||||||
|
{props.noEdit !== true ? (
|
||||||
|
<ul class="page-actions">
|
||||||
|
<li
|
||||||
|
class="link github -button hint--bottom"
|
||||||
|
data-hint={t.editOnGitHub}
|
||||||
|
>
|
||||||
|
<a href={editLink}>
|
||||||
|
<span class="text -visible">{t.edit}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../sass/2017/utils';
|
||||||
|
@import '../sass/2017/components/back-button';
|
||||||
|
@import '../sass/2017/components/page-actions';
|
||||||
|
@import '../sass/2017/components/top-nav';
|
||||||
|
</style>
|
17
src/components/V2017/CarbonBox.astro
Normal file
17
src/components/V2017/CarbonBox.astro
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
import { carbon } from '~/config'
|
||||||
|
const useCarbon = import.meta.env.PROD
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="HeadlinePub" role="complementary">
|
||||||
|
{useCarbon ? <script async src={carbon.src} id="_carbonads_js" /> : null}
|
||||||
|
<span class="placeholder -one"></span>
|
||||||
|
<span class="placeholder -two"></span>
|
||||||
|
<span class="placeholder -three"></span>
|
||||||
|
<span class="placeholder -four"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/headline-pub';
|
||||||
|
</style>
|
16
src/components/V2017/PushButton.astro
Normal file
16
src/components/V2017/PushButton.astro
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
export type Props = {
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
const props = Astro.props as Props
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={`push-button ${props.class ?? ''}`}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/placeholders/push-button';
|
||||||
|
@import '../../sass/2017/components/push-button';
|
||||||
|
</style>
|
34
src/components/V2017Home/Announcements.astro
Normal file
34
src/components/V2017Home/Announcements.astro
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
import { announcement } from '~/config'
|
||||||
|
import snarkdown from 'snarkdown'
|
||||||
|
|
||||||
|
const body = snarkdown(announcement.body)
|
||||||
|
.split('<br />')
|
||||||
|
.map((text) => `<p>${text}</p>`)
|
||||||
|
.join('')
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="announcements-list">
|
||||||
|
<div
|
||||||
|
class="announcements-item item -hide"
|
||||||
|
data-js-dismissable={`{"id":"${announcement.id}"}`}
|
||||||
|
>
|
||||||
|
<h3 class="title">{announcement.title}</h3>
|
||||||
|
<div class="body" set:html={body} />
|
||||||
|
<button data-js-dismiss class="close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/announcements-item';
|
||||||
|
@import '../../sass/2017/components/announcements-list';
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { setupDismiss } from '~/scripts/v2017/behaviors_2/dismiss'
|
||||||
|
import { setupDismissable } from '~/scripts/v2017/behaviors_2/dismissable'
|
||||||
|
|
||||||
|
setupDismiss()
|
||||||
|
setupDismissable()
|
||||||
|
</script>
|
17
src/components/V2017Home/FeaturedPages.astro
Normal file
17
src/components/V2017Home/FeaturedPages.astro
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
import type { SheetPage } from '~/lib/page'
|
||||||
|
import PageListItem from './PageListItem.astro'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
pages: SheetPage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
---
|
||||||
|
|
||||||
|
{props.pages.map((page) => <PageListItem page={page} class="top-sheet" />)}
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/top-sheet';
|
||||||
|
</style>
|
22
src/components/V2017Home/PageListItem.astro
Normal file
22
src/components/V2017Home/PageListItem.astro
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
import type { SheetPage } from '../../lib/page'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
page: SheetPage
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const page = props.page
|
||||||
|
const url = `/${page.slug}`
|
||||||
|
---
|
||||||
|
|
||||||
|
<a href={url} class={`article item -item-${page.slug} ${props.class ?? ''}`}>
|
||||||
|
<span class="info">
|
||||||
|
<code class="slug">{page.slug}</code>
|
||||||
|
<span class="title">
|
||||||
|
{page.frontmatter.title ?? page.slug}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* TODO attribute-peg */}
|
||||||
|
</span>
|
||||||
|
</a>
|
124
src/components/V2017Sheet.astro
Normal file
124
src/components/V2017Sheet.astro
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
// import "../sass/critical-sheet.scss";
|
||||||
|
import snarkdown from 'snarkdown'
|
||||||
|
import { render } from '../lib/render'
|
||||||
|
import type { SheetPage } from '../lib/page'
|
||||||
|
import BaseLayout from './BaseLayout.astro'
|
||||||
|
import TopNav from './TopNav.astro'
|
||||||
|
import CommentsArea from './V2017Sheet/CommentsArea.astro'
|
||||||
|
import SearchFooter from './V2017Sheet/SearchFooter.astro'
|
||||||
|
import RelatedPosts from './V2017Sheet/RelatedPosts.astro'
|
||||||
|
import CarbonBox from './V2017/CarbonBox.astro'
|
||||||
|
import { getSEOPropsForPage } from '~/lib/seo/seo'
|
||||||
|
import { getJSONLDsForPage } from '~/lib/seo/jsonLd'
|
||||||
|
import SEO from '~/components/SEO/SEO.astro'
|
||||||
|
import NoticeBox from './V2017Sheet/NoticeBox.astro'
|
||||||
|
import { getEditLink } from '~/lib/links'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
page: SheetPage
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const page = props.page
|
||||||
|
const mkdn = await render(page.markdown)
|
||||||
|
const seoProps = getSEOPropsForPage(page)
|
||||||
|
const jsonLdSchemas = getJSONLDsForPage(page)
|
||||||
|
const tags = page.frontmatter.tags ?? []
|
||||||
|
const deprecatedBy = page.frontmatter.deprecated_by
|
||||||
|
const title: string = page.frontmatter.title ?? page.slug
|
||||||
|
const editUrl = getEditLink(page)
|
||||||
|
const intro: string | null = page.frontmatter.intro
|
||||||
|
? `<p>${snarkdown(page.frontmatter.intro)}</p>`
|
||||||
|
: null
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout bodyClass="UseCompactHeader HighlightPubFirstLine">
|
||||||
|
<Fragment slot="head">
|
||||||
|
<SEO {...seoProps} />
|
||||||
|
{
|
||||||
|
jsonLdSchemas.map((schema) => (
|
||||||
|
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<TopNav page={page} />
|
||||||
|
<div class="body-area">
|
||||||
|
<header class="main-heading -center" role="banner">
|
||||||
|
<h1 class="h1">{title}{' '}<em>cheatsheet</em></h1>
|
||||||
|
|
||||||
|
{/* Publicite */}
|
||||||
|
<div class="pubbox" data-js-no-preview><CarbonBox /></div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* WIP */}
|
||||||
|
{
|
||||||
|
tags.includes('WIP') ? (
|
||||||
|
<NoticeBox>
|
||||||
|
This page is a work in progress. You can help by{' '}
|
||||||
|
<a href={editUrl}>suggesting edits</a>!
|
||||||
|
</NoticeBox>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* Deprecated */}
|
||||||
|
{
|
||||||
|
deprecatedBy ? (
|
||||||
|
<NoticeBox>
|
||||||
|
<strong>Deprecated:</strong> This guide covers an older version.
|
||||||
|
<a href={deprecatedBy}>A newer version is available here.</a>
|
||||||
|
</NoticeBox>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
intro ? (
|
||||||
|
<div class="intro-content MarkdownBody">
|
||||||
|
<Fragment set:html={intro} />
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
<main
|
||||||
|
class="post-content MarkdownBody"
|
||||||
|
data-js-main-body
|
||||||
|
data-js-anchors
|
||||||
|
role="main"
|
||||||
|
>
|
||||||
|
<Fragment set:html={mkdn.html} />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pre-footer" data-js-no-preview><i class="icon"></i></div>
|
||||||
|
<CommentsArea identifier={page.slug} />
|
||||||
|
<SearchFooter />
|
||||||
|
<RelatedPosts page={page} />
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../sass/2017/utils';
|
||||||
|
@import '../sass/2017/markdown/a-em';
|
||||||
|
@import '../sass/2017/markdown/code';
|
||||||
|
@import '../sass/2017/markdown/headings';
|
||||||
|
@import '../sass/2017/markdown/local-anchor';
|
||||||
|
@import '../sass/2017/markdown/p';
|
||||||
|
@import '../sass/2017/markdown/table';
|
||||||
|
@import '../sass/2017/markdown/ul';
|
||||||
|
@import '../sass/2017/components/body-area';
|
||||||
|
@import '../sass/2017/components/h2-section';
|
||||||
|
@import '../sass/2017/components/h3-section';
|
||||||
|
@import '../sass/2017/components/h3-section-list';
|
||||||
|
@import '../sass/2017/components/hint-mark';
|
||||||
|
@import '../sass/2017/components/intro-content';
|
||||||
|
@import '../sass/2017/components/main-heading';
|
||||||
|
@import '../sass/2017/components/pre-footer';
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { setupNoPreview } from '~/scripts/v2017/behaviors_2/no-preview'
|
||||||
|
import { setupAnchors } from '~/scripts/v2017/behaviors_2/anchors'
|
||||||
|
|
||||||
|
setupNoPreview()
|
||||||
|
setupAnchors()
|
||||||
|
</script>
|
60
src/components/V2017Sheet/CommentsArea.astro
Normal file
60
src/components/V2017Sheet/CommentsArea.astro
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
import { disqus, site } from '../../config'
|
||||||
|
|
||||||
|
const t = {
|
||||||
|
suffix: 'for this cheatsheet.',
|
||||||
|
link: 'Write yours!'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
identifier: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const url = `${site.url}/${props.identifier}`
|
||||||
|
const { identifier } = props
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="comments-area" id="comments" data-js-no-preview>
|
||||||
|
<div class="container">
|
||||||
|
<details class="comments-details">
|
||||||
|
<summary>
|
||||||
|
<strong class="count">
|
||||||
|
<span
|
||||||
|
class="disqus-comment-count"
|
||||||
|
data-disqus-identifier={identifier}
|
||||||
|
data-disqus-url={url}>0 Comments</span
|
||||||
|
>
|
||||||
|
</strong>
|
||||||
|
{' '}
|
||||||
|
<span class="suffix">{t.suffix}</span>
|
||||||
|
{' '}
|
||||||
|
<span class="fauxlink">{t.link}</span>
|
||||||
|
</summary>
|
||||||
|
<div class="comments-section">
|
||||||
|
<div class="comments">
|
||||||
|
<div id="disqus_thread"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
<noscript
|
||||||
|
data-js-disqus={JSON.stringify({
|
||||||
|
host: disqus.host,
|
||||||
|
url,
|
||||||
|
identifier
|
||||||
|
})}></noscript>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/comments-area';
|
||||||
|
@import '../../sass/2017/components/comments-details';
|
||||||
|
@import '../../sass/2017/components/comments-section';
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { setupDisqus } from '~/scripts/v2017/behaviors_2/disqus'
|
||||||
|
|
||||||
|
setupDisqus()
|
||||||
|
</script>
|
12
src/components/V2017Sheet/NoticeBox.astro
Normal file
12
src/components/V2017Sheet/NoticeBox.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<aside class="notice-box MarkdownBody">
|
||||||
|
<slot />
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/notice-box';
|
||||||
|
</style>
|
35
src/components/V2017Sheet/RelatedPostItem.astro
Normal file
35
src/components/V2017Sheet/RelatedPostItem.astro
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
import type { SheetPage } from '~/lib/page'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
class?: string
|
||||||
|
page: SheetPage
|
||||||
|
}
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const page = props.page
|
||||||
|
const url = `/${page.slug}`
|
||||||
|
|
||||||
|
const t = {
|
||||||
|
suffix: 'cheatsheet'
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<li class={`related-post-item ${props.class ?? ''}`}>
|
||||||
|
<a href={url}>
|
||||||
|
<strong>{page.frontmatter.title}</strong>
|
||||||
|
{' '}
|
||||||
|
<span>
|
||||||
|
{t.suffix}
|
||||||
|
|
||||||
|
{/* TODO attribute-peg */}
|
||||||
|
<!-- {% if include.page.layout == '2017/sheet' %} -->
|
||||||
|
<!-- <abbr class='attribute-peg -new-layout hint--bottom' data-hint='New layout!'><span></span></abbr> -->
|
||||||
|
<!-- {% endif %} -->
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/related-post-item';
|
||||||
|
</style>
|
93
src/components/V2017Sheet/RelatedPosts.astro
Normal file
93
src/components/V2017Sheet/RelatedPosts.astro
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
import { getPages, type SheetPage } from '~/lib/page'
|
||||||
|
import { getTopPages, getRelatedPages } from '~/lib/page/queries'
|
||||||
|
import { etc } from '~/config'
|
||||||
|
import RelatedPostItem from '~/components/V2017Sheet/RelatedPostItem.astro'
|
||||||
|
import PushButton from '~/components/V2017/PushButton.astro'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
page: SheetPage
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
const page = props.page
|
||||||
|
|
||||||
|
const t = {
|
||||||
|
callout: {
|
||||||
|
description:
|
||||||
|
'Over {size} curated cheatsheets, by developers for developers.',
|
||||||
|
link: 'Devhints home'
|
||||||
|
},
|
||||||
|
|
||||||
|
group: {
|
||||||
|
top: 'Top cheatsheets',
|
||||||
|
other: 'Other cheatsheets',
|
||||||
|
category: 'Other {category} cheatsheets'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const calloutDescription = t.callout.description.replace(
|
||||||
|
'{size}',
|
||||||
|
etc.advertisedSheetCount.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
const category = page.frontmatter.category
|
||||||
|
|
||||||
|
const categoryHeading = category
|
||||||
|
? t.group.category.replace('{category}', category)
|
||||||
|
: t.group.other
|
||||||
|
|
||||||
|
const pages = await getPages()
|
||||||
|
const relatedPages = getRelatedPages(pages, page, { maxCount: 6 })
|
||||||
|
const topPages = getTopPages(pages, page, { maxCount: 6 })
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer class="related-posts-area" id="related" data-js-no-preview>
|
||||||
|
<div class="container">
|
||||||
|
<div class="related-posts-section">
|
||||||
|
{/* Callout */}
|
||||||
|
<div class="callout">
|
||||||
|
<a class="related-posts-callout" href="/">
|
||||||
|
<div class="text">
|
||||||
|
<i class="icon"></i>
|
||||||
|
<span class="description">{calloutDescription}</span>
|
||||||
|
<PushButton class="-dark">{t.callout.link}</PushButton>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Posts in the same category */}
|
||||||
|
<div class="group">
|
||||||
|
<div class="related-posts-group">
|
||||||
|
<h3>{categoryHeading}</h3>
|
||||||
|
</div>
|
||||||
|
<ul class="related-post-list">
|
||||||
|
{
|
||||||
|
relatedPages.map((page) => (
|
||||||
|
<RelatedPostItem page={page} class="item" />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top pages */}
|
||||||
|
<div class="group">
|
||||||
|
<div class="related-posts-group">
|
||||||
|
<h3>{t.group.top}</h3>
|
||||||
|
</div>
|
||||||
|
<ul class="related-post-list">
|
||||||
|
{topPages.map((page) => <RelatedPostItem page={page} class="item" />)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/related-posts-area';
|
||||||
|
@import '../../sass/2017/components/related-posts-callout';
|
||||||
|
@import '../../sass/2017/components/related-posts-group';
|
||||||
|
@import '../../sass/2017/components/related-posts-section';
|
||||||
|
@import '../../sass/2017/components/related-post-list';
|
||||||
|
</style>
|
22
src/components/V2017Sheet/SearchFooter.astro
Normal file
22
src/components/V2017Sheet/SearchFooter.astro
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
import SearchForm from './SearchForm.astro'
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer class="search-footer" data-js-no-preview>
|
||||||
|
<div class="container">
|
||||||
|
<div class="search-footer-section">
|
||||||
|
<div class="search">
|
||||||
|
<SearchForm class="-small" />
|
||||||
|
</div>
|
||||||
|
<div class="links">
|
||||||
|
<a class="home-button" href="/"><i></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/home-button';
|
||||||
|
@import '../../sass/2017/components/search-footer';
|
||||||
|
</style>
|
69
src/components/V2017Sheet/SearchForm.astro
Normal file
69
src/components/V2017Sheet/SearchForm.astro
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
import 'autocompleter/autocomplete.css'
|
||||||
|
|
||||||
|
import { etc } from '../../config'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
class?: string
|
||||||
|
/** True for the homepage */
|
||||||
|
isLive: boolean
|
||||||
|
}
|
||||||
|
const t = {
|
||||||
|
prefix: 'devhints.io',
|
||||||
|
defaultPlaceholder: 'Search {size}+ cheatsheets',
|
||||||
|
homePlaceholder: 'Search...'
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
|
||||||
|
let placeholder = props.isLive ? t.homePlaceholder : t.defaultPlaceholder
|
||||||
|
|
||||||
|
placeholder = placeholder.replace('{size}', etc.advertisedSheetCount.toString())
|
||||||
|
---
|
||||||
|
|
||||||
|
<form class="search" action="/" method="get" data-js-search-form>
|
||||||
|
<label class={`search-box ${props.class ?? ''}`}>
|
||||||
|
<span class="prefix">{t.prefix}</span>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
{
|
||||||
|
(
|
||||||
|
<input
|
||||||
|
name="q"
|
||||||
|
type="text"
|
||||||
|
class="input"
|
||||||
|
{...(props.isLive ? { autofocus: true } : {})}
|
||||||
|
data-js-search-input
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { onScrollVisible } from '~/lib/domutils/onScrollVisible'
|
||||||
|
|
||||||
|
// Prevent <enter> from submitting the form
|
||||||
|
document
|
||||||
|
.querySelectorAll<HTMLFormElement>('[data-js-search-form]')
|
||||||
|
.forEach((form) => {
|
||||||
|
form.addEventListener('submit', (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelectorAll<HTMLInputElement>('[data-js-search-input]')
|
||||||
|
.forEach((input) => {
|
||||||
|
onScrollVisible(input, async () => {
|
||||||
|
const { setup } = await import('./SearchForm.script')
|
||||||
|
setup(input)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../../sass/2017/utils';
|
||||||
|
@import '../../sass/2017/components/search-box';
|
||||||
|
@import '../../sass/2017/components/autocomplete';
|
||||||
|
</style>
|
25
src/components/V2017Sheet/SearchForm.script.ts
Normal file
25
src/components/V2017Sheet/SearchForm.script.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import autocomplete from 'autocompleter'
|
||||||
|
import { fetchFuse, parseFuse } from '~/lib/fuseSearch/fuseSearch'
|
||||||
|
|
||||||
|
export async function setup(input: HTMLInputElement) {
|
||||||
|
let fuse
|
||||||
|
|
||||||
|
autocomplete<{ label: string; value: string }>({
|
||||||
|
input,
|
||||||
|
fetch: async (text: string, update) => {
|
||||||
|
fuse ??= parseFuse(await fetchFuse())
|
||||||
|
|
||||||
|
const rows: Array<{ item: { title: string; slug: string } }> =
|
||||||
|
fuse.search(text, { limit: 10 })
|
||||||
|
|
||||||
|
update(
|
||||||
|
rows.map((row) => ({ label: row.item.title, value: row.item.slug }))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onSelect: (item) => {
|
||||||
|
// ^ { label, value }
|
||||||
|
const slug = item.value
|
||||||
|
window.location.href = `/${slug}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
75
src/config.ts
Normal file
75
src/config.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
export const site = {
|
||||||
|
url: 'https://devhints.io',
|
||||||
|
title: 'Devhints.io cheatsheets'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const etc = {
|
||||||
|
advertisedSheetCount: 357
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const disqus = {
|
||||||
|
enabled: true,
|
||||||
|
host: 'devhints.disqus.com'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const cloudflareAnalytics = {
|
||||||
|
token: '93ebff376c05423d8e6c1dfbe406a172'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const googleAnalytics = {
|
||||||
|
measurementId: 'G-N7TC6B227L'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const github = {
|
||||||
|
repositoryUrl: 'https://github.com/rstacruz/cheatsheets',
|
||||||
|
branch: 'master'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const urls = {
|
||||||
|
newCheatsheetUrl: 'https://github.com/rstacruz/cheatsheets/issues/907'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const carbon = {
|
||||||
|
src: 'https://cdn.carbonads.com/carbon.js?serve=CE7IK5QM&placement=devhintsio'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const categories = [
|
||||||
|
'Analytics',
|
||||||
|
'Ansible',
|
||||||
|
'Apps',
|
||||||
|
'C-like',
|
||||||
|
'CLI',
|
||||||
|
'CSS',
|
||||||
|
'Databases',
|
||||||
|
'Devops',
|
||||||
|
'Elixir',
|
||||||
|
'Git',
|
||||||
|
'HTML',
|
||||||
|
'Java & JVM',
|
||||||
|
'JavaScript',
|
||||||
|
'JavaScript libraries',
|
||||||
|
'Jekyll',
|
||||||
|
'Ledger',
|
||||||
|
'Markup',
|
||||||
|
'macOS',
|
||||||
|
'Node.js',
|
||||||
|
'PHP',
|
||||||
|
'Python',
|
||||||
|
'Rails',
|
||||||
|
'React',
|
||||||
|
'Ruby',
|
||||||
|
'Ruby libraries',
|
||||||
|
'Vim',
|
||||||
|
'Fitness',
|
||||||
|
'Others'
|
||||||
|
]
|
||||||
|
|
||||||
|
export const announcement = {
|
||||||
|
id: '2023-12-14',
|
||||||
|
title: `We're on Twitter ♥️`,
|
||||||
|
body: [
|
||||||
|
`Follow [@devhints](https://twitter.com/devhints) on X/Twitter for daily "today I learned" snippets.`,
|
||||||
|
``,
|
||||||
|
`Also: I've started a new blog with some insights on web development. Have a look! [**ricostacruz.com/posts**](https://ricostacruz.com/posts?utm_source=devhints)`
|
||||||
|
].join('\n')
|
||||||
|
}
|
1
src/env.d.ts
vendored
Normal file
1
src/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="astro/client" />
|
20
src/lib/domutils/onScrollVisible.ts
Normal file
20
src/lib/domutils/onScrollVisible.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Trigger a `callback` when an `element` is made visible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function onScrollVisible(element: HTMLElement, callback: () => void) {
|
||||||
|
if (typeof IntersectionObserver !== 'function') {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
callback()
|
||||||
|
observer.unobserve(element)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
observer.observe(element)
|
||||||
|
}
|
34
src/lib/fuseSearch/__snapshots__/fuseSearch.test.ts.snap
Normal file
34
src/lib/fuseSearch/__snapshots__/fuseSearch.test.ts.snap
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`Simple pages scenario > multi-result search 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"slug": "react",
|
||||||
|
"title": "React",
|
||||||
|
},
|
||||||
|
"refIndex": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"slug": "react-router",
|
||||||
|
"title": "React Router",
|
||||||
|
},
|
||||||
|
"refIndex": 2,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Simple pages scenario > no-result search 1`] = `[]`;
|
||||||
|
|
||||||
|
exports[`Simple pages scenario > one-result search 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"item": {
|
||||||
|
"slug": "vim",
|
||||||
|
"title": "Vim",
|
||||||
|
},
|
||||||
|
"refIndex": 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
37
src/lib/fuseSearch/fuseSearch.test.ts
Normal file
37
src/lib/fuseSearch/fuseSearch.test.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { buildFuseIndex, parseFuse } from './fuseSearch'
|
||||||
|
|
||||||
|
describe('Simple pages scenario', () => {
|
||||||
|
// prettier-ignore
|
||||||
|
const pages = {
|
||||||
|
"react": { slug: "react", frontmatter: { title: "React" } },
|
||||||
|
"vim": { slug: "vim", frontmatter: { title: "Vim" } },
|
||||||
|
"react-router": { slug: "react-router", frontmatter: { title: "React Router" } },
|
||||||
|
}
|
||||||
|
|
||||||
|
let fuse: ReturnType<typeof parseFuse>
|
||||||
|
|
||||||
|
runTest({
|
||||||
|
label: 'one-result search',
|
||||||
|
query: 'vim'
|
||||||
|
})
|
||||||
|
runTest({
|
||||||
|
label: 'multi-result search',
|
||||||
|
query: 'react'
|
||||||
|
})
|
||||||
|
runTest({
|
||||||
|
label: 'no-result search',
|
||||||
|
query: 'skinamarink'
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const externalData = buildFuseIndex(pages)
|
||||||
|
fuse = parseFuse(externalData)
|
||||||
|
})
|
||||||
|
|
||||||
|
function runTest({ label, query }: { label: string; query: string }) {
|
||||||
|
test(label, () => {
|
||||||
|
const results = fuse.search(query)
|
||||||
|
expect(results).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
48
src/lib/fuseSearch/fuseSearch.ts
Normal file
48
src/lib/fuseSearch/fuseSearch.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import Fuse from 'fuse.js'
|
||||||
|
import type { SheetPage } from '~/lib/page'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is what gets served in searchindex.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ExternalSearchData = {
|
||||||
|
index: ReturnType<FuseIndex['toJSON']>
|
||||||
|
rows: Array<{ title: string; slug: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Row = { title: string; slug: string }
|
||||||
|
|
||||||
|
export type FuseIndex = Fuse.FuseIndex<Row>
|
||||||
|
|
||||||
|
/** A subset of `SheetPage` needed for search indexing */
|
||||||
|
type PartialSheetPage = {
|
||||||
|
slug: SheetPage['slug']
|
||||||
|
frontmatter: Pick<SheetPage['frontmatter'], 'title'>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get `pages` and turn them into a fuse index json.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function buildFuseIndex(pages: Record<string, PartialSheetPage>) {
|
||||||
|
const rows = Object.values(pages).map((page): Row => {
|
||||||
|
return { title: (page.frontmatter.title ?? '') as string, slug: page.slug }
|
||||||
|
})
|
||||||
|
const myIndex: FuseIndex = Fuse.createIndex(['title', 'slug'], rows)
|
||||||
|
const indexJSON = myIndex.toJSON()
|
||||||
|
|
||||||
|
const result = { index: indexJSON, rows }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchFuse() {
|
||||||
|
const res = await fetch('/searchindex.json')
|
||||||
|
if (res.status > 400) throw new Error('Failed to fetch searchindex.json')
|
||||||
|
return res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseFuse(data: ExternalSearchData) {
|
||||||
|
const index = Fuse.parseIndex(data.index)
|
||||||
|
const fuse = new Fuse<Row>(data.rows, {}, index)
|
||||||
|
return fuse
|
||||||
|
}
|
58
src/lib/kramdown.ts
Normal file
58
src/lib/kramdown.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { spawn } from 'node:child_process'
|
||||||
|
import crypto from 'node:crypto'
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
|
||||||
|
export type KramdownResult = {
|
||||||
|
html: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders via Ruby Kramdown
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function renderKramdown(input: string): Promise<KramdownResult> {
|
||||||
|
return (await renderKramdownFromCache(input)) ?? renderKramdownJIT(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to get from .cache/ (left by `npm run cache_markdown`).
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function renderKramdownFromCache(
|
||||||
|
input: string
|
||||||
|
): Promise<KramdownResult | undefined> {
|
||||||
|
const digest = crypto.createHash('sha256').update(input.trim()).digest('hex')
|
||||||
|
const cachePath = `.cache/${digest}.html`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await readFile(cachePath, 'utf-8')
|
||||||
|
return { html: result }
|
||||||
|
} catch (err) {
|
||||||
|
if (import.meta.env.PROD) console.log(`Cache MISS (${cachePath})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders via Kramdown by invoking Ruby.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function renderKramdownJIT(input: string): Promise<KramdownResult> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let output = ''
|
||||||
|
const child = spawn('bundle', ['exec', 'ruby', './src/ruby/kramdown.rb'])
|
||||||
|
child.stdin.write(input)
|
||||||
|
child.stdin.end()
|
||||||
|
|
||||||
|
child.stdout.on('data', (data: string) => {
|
||||||
|
output += data
|
||||||
|
})
|
||||||
|
|
||||||
|
child.on('exit', (code: number) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(new Error(`Exited with code ${code}`))
|
||||||
|
} else {
|
||||||
|
resolve({ html: output })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
17
src/lib/links.ts
Normal file
17
src/lib/links.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { github } from '~/config'
|
||||||
|
import type { SheetPage } from './page'
|
||||||
|
|
||||||
|
export function getEditLink(page: { slug: string } | null | undefined) {
|
||||||
|
if (!page) return null
|
||||||
|
return `${github.repositoryUrl}/blob/${github.branch}/${page.slug}.md`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUrlFromPage(
|
||||||
|
page?: SheetPage | undefined,
|
||||||
|
siteUrl?: URL | undefined
|
||||||
|
) {
|
||||||
|
if (!siteUrl) throw new Error('No site URL found')
|
||||||
|
if (!page) return siteUrl.toString()
|
||||||
|
if (page.slug) return `${siteUrl}${page.slug}`
|
||||||
|
throw new Error("Can't get URL from page")
|
||||||
|
}
|
89
src/lib/page.test.ts
Normal file
89
src/lib/page.test.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { getPages, mapGlobToPages } from './page'
|
||||||
|
|
||||||
|
let pages: Awaited<ReturnType<typeof getPages>>
|
||||||
|
let keys: string[]
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages ??= await getPages()
|
||||||
|
keys ??= Object.keys(pages)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('return pages', () => {
|
||||||
|
expect(keys).toContain('react')
|
||||||
|
expect(keys).toContain('bash')
|
||||||
|
expect(keys).toContain('tests/basic')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('frontmatter', () => {
|
||||||
|
const page = pages.bash
|
||||||
|
expect(page.slug).toEqual('bash')
|
||||||
|
expect(typeof page.markdown).toEqual('string')
|
||||||
|
expect(typeof page.frontmatter.title).toEqual('string')
|
||||||
|
expect(typeof page.frontmatter.keywords).toEqual('object')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mapGlobToPages()', () => {
|
||||||
|
test('basic scenario', () => {
|
||||||
|
const result = mapGlobToPages({
|
||||||
|
bash: ['---', 'title: Bash', '---', '# hi'].join('\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"bash": {
|
||||||
|
"frontmatter": {
|
||||||
|
"title": "Bash",
|
||||||
|
},
|
||||||
|
"markdown": "# hi",
|
||||||
|
"slug": "bash",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parsing numbers', () => {
|
||||||
|
const result = mapGlobToPages({
|
||||||
|
'101': ['---', 'title: 101', '---', '# hi'].join('\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"101": {
|
||||||
|
"frontmatter": {
|
||||||
|
"title": "101",
|
||||||
|
},
|
||||||
|
"markdown": "# hi",
|
||||||
|
"slug": "101",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('invalid type', () => {
|
||||||
|
expect(() => {
|
||||||
|
mapGlobToPages({
|
||||||
|
'101': ['---', 'title: false', '---', '# hi'].join('\n')
|
||||||
|
})
|
||||||
|
}).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`[FrontmatterValidationError: Zod validation error: '101' {"title":["Invalid input"]}]`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('missing frontmatter', () => {
|
||||||
|
const result = mapGlobToPages({
|
||||||
|
'101': ['---', 'title: 101', '---', '# hi'].join('\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"101": {
|
||||||
|
"frontmatter": {
|
||||||
|
"title": "101",
|
||||||
|
},
|
||||||
|
"markdown": "# hi",
|
||||||
|
"slug": "101",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
78
src/lib/page.ts
Normal file
78
src/lib/page.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import grayMatter from 'gray-matter'
|
||||||
|
import { ZodError } from 'zod'
|
||||||
|
import {
|
||||||
|
SheetFrontmatterSchema,
|
||||||
|
type SheetFrontmatter
|
||||||
|
} from '~/types/SheetFrontmatter'
|
||||||
|
|
||||||
|
export type SheetPage = {
|
||||||
|
slug: string
|
||||||
|
markdown: string
|
||||||
|
frontmatter: SheetFrontmatter
|
||||||
|
}
|
||||||
|
|
||||||
|
class FrontmatterValidationError extends Error {
|
||||||
|
filePath: string
|
||||||
|
|
||||||
|
constructor(message: string, options: ErrorOptions & { filePath: string }) {
|
||||||
|
super(message, options)
|
||||||
|
this.name = 'FrontmatterValidationError'
|
||||||
|
this.filePath = options.filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns pages
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getPages(): Promise<Record<string, SheetPage>> {
|
||||||
|
const files = import.meta.glob(
|
||||||
|
[
|
||||||
|
'../../*.md',
|
||||||
|
'../../*/*.md',
|
||||||
|
'!../../README.md',
|
||||||
|
'!../../CONTRIBUTING.md',
|
||||||
|
'!../../404.md',
|
||||||
|
'!../../index.md',
|
||||||
|
'!../../index@2016.md'
|
||||||
|
],
|
||||||
|
{
|
||||||
|
eager: true,
|
||||||
|
query: '?raw',
|
||||||
|
import: 'default'
|
||||||
|
}
|
||||||
|
) as Record<string, string>
|
||||||
|
|
||||||
|
return mapGlobToPages(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapGlobToPages(
|
||||||
|
files: Record<string, string>
|
||||||
|
): Record<string, SheetPage> {
|
||||||
|
const result: Record<string, SheetPage> = {}
|
||||||
|
|
||||||
|
for (const [filePath, rawContent] of Object.entries(files)) {
|
||||||
|
const slug = filePath.replace(/^\.\.\/\.\.\//, '').replace(/\.md$/, '')
|
||||||
|
// ^ filePath == "../../README.md"
|
||||||
|
|
||||||
|
const res = grayMatter(rawContent)
|
||||||
|
// ^ res == { content: '...', data: {} }
|
||||||
|
|
||||||
|
const frontmatter = (() => {
|
||||||
|
try {
|
||||||
|
return SheetFrontmatterSchema.parse(res.data)
|
||||||
|
} catch (err) {
|
||||||
|
if (!(err instanceof ZodError)) throw err
|
||||||
|
const debugInfo = JSON.stringify(err.flatten().fieldErrors)
|
||||||
|
const newErr = new FrontmatterValidationError(
|
||||||
|
`Zod validation error: '${filePath}' ${debugInfo}`,
|
||||||
|
{ cause: err, filePath }
|
||||||
|
)
|
||||||
|
|
||||||
|
throw newErr
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
result[slug] = { slug, markdown: res.content, frontmatter }
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
24
src/lib/page/accessors.ts
Normal file
24
src/lib/page/accessors.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Accessors: things that get stuff from a specific record. Usually in the form
|
||||||
|
* of `get(page, ...) -> any`
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { SheetPage } from '../page'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a page has a tag
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function hasTag(page: SheetPage, tagName: string): boolean {
|
||||||
|
return (
|
||||||
|
(page.frontmatter.tags && page.frontmatter.tags.includes(tagName)) || false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if something should appear on the homepage
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function isListed(page: SheetPage): boolean {
|
||||||
|
return page.frontmatter.category !== 'Hidden'
|
||||||
|
}
|
122
src/lib/page/queries.ts
Normal file
122
src/lib/page/queries.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { categories } from '../../config'
|
||||||
|
import type { SheetPage } from '../page'
|
||||||
|
import { hasTag, isListed } from './accessors'
|
||||||
|
|
||||||
|
export type Category = {
|
||||||
|
pages: SheetPage[]
|
||||||
|
title: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns categories and their corresponding pages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getPagesByCategory(pages: Record<string, SheetPage>) {
|
||||||
|
const pageCategories: Record<string, Category> = {}
|
||||||
|
|
||||||
|
for (const category of categories) {
|
||||||
|
pageCategories[category] = { pages: [], title: category, id: category }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const page of Object.values(pages)) {
|
||||||
|
if (!isListed(page)) continue
|
||||||
|
|
||||||
|
const categoryName = page.frontmatter.category ?? 'Others'
|
||||||
|
if (!categoryName) continue
|
||||||
|
|
||||||
|
let cat = pageCategories[categoryName]
|
||||||
|
if (!cat) cat = pageCategories['Others']
|
||||||
|
|
||||||
|
cat.pages.push(page)
|
||||||
|
}
|
||||||
|
return pageCategories
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns categories and their corresponding pages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getRecentPages(
|
||||||
|
pages: Record<string, SheetPage>,
|
||||||
|
options?: { maxCount?: number }
|
||||||
|
): SheetPage[] {
|
||||||
|
const { maxCount = 8 } = options ?? {}
|
||||||
|
|
||||||
|
return Object.values(pages)
|
||||||
|
.filter((page) => isListed(page) && page.frontmatter.updated)
|
||||||
|
.sort(compare('desc', (page: SheetPage) => page.frontmatter.updated ?? ''))
|
||||||
|
.slice(0, maxCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return featured pages for the home page
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getFeaturedPages(
|
||||||
|
pages: Record<string, SheetPage>,
|
||||||
|
options?: { maxCount?: number }
|
||||||
|
): SheetPage[] {
|
||||||
|
const { maxCount = 8 } = options ?? {}
|
||||||
|
|
||||||
|
return Object.values(pages)
|
||||||
|
.filter((page) => isListed(page) && hasTag(page, 'Featured'))
|
||||||
|
.sort(compare('asc', (page: SheetPage) => page.slug ?? ''))
|
||||||
|
.slice(0, maxCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top pages (highest weight)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getTopPages(
|
||||||
|
pages: Record<string, SheetPage>,
|
||||||
|
referencePage: SheetPage,
|
||||||
|
options?: { maxCount?: number }
|
||||||
|
): SheetPage[] {
|
||||||
|
const { maxCount = 6 } = options ?? {}
|
||||||
|
|
||||||
|
return Object.values(pages)
|
||||||
|
.filter((page) => isListed(page) && 'weight' in page.frontmatter)
|
||||||
|
.filter((page) => page.slug !== referencePage.slug)
|
||||||
|
.sort(compare('asc', (page: SheetPage) => page.slug ?? ''))
|
||||||
|
.sort(compare('asc', (page: SheetPage) => page.frontmatter.weight ?? 0))
|
||||||
|
.slice(0, maxCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top pages (highest weight)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getRelatedPages(
|
||||||
|
pages: Record<string, SheetPage>,
|
||||||
|
referencePage: SheetPage,
|
||||||
|
options?: { maxCount?: number }
|
||||||
|
): SheetPage[] {
|
||||||
|
const { maxCount = 6 } = options ?? {}
|
||||||
|
const category = referencePage.frontmatter.category
|
||||||
|
|
||||||
|
return Object.values(pages)
|
||||||
|
.filter((page) => isListed(page) && page.frontmatter.category === category)
|
||||||
|
.filter((page) => page.slug !== referencePage.slug)
|
||||||
|
.sort(compare('asc', (page: SheetPage) => page.slug ?? ''))
|
||||||
|
.sort(compare('asc', (page: SheetPage) => page.frontmatter.weight ?? 0))
|
||||||
|
.slice(0, maxCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: Create a comparator function
|
||||||
|
*/
|
||||||
|
|
||||||
|
function compare<T>(
|
||||||
|
direction: 'asc' | 'desc',
|
||||||
|
accessor: (input: T) => number | string
|
||||||
|
) {
|
||||||
|
const k = direction === 'desc' ? -1 : 1
|
||||||
|
|
||||||
|
return (a: T, b: T): number => {
|
||||||
|
const va = accessor(a)
|
||||||
|
const vb = accessor(b)
|
||||||
|
return k * (va === vb ? 0 : va > vb ? 1 : -1)
|
||||||
|
}
|
||||||
|
}
|
89
src/lib/render.test.ts
Normal file
89
src/lib/render.test.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { render } from './render'
|
||||||
|
|
||||||
|
it('h3 only', async () => {
|
||||||
|
const input = ['### H3', '', 'This is some h3'].join('\n')
|
||||||
|
const { html } = await render(input)
|
||||||
|
expect(html).toMatchInlineSnapshot(`
|
||||||
|
"<section class="h2-section"><div class="body h3-section-list">
|
||||||
|
<section class="h3-section"><h3 id="h3">H3</h3><div class="body">
|
||||||
|
|
||||||
|
<p>This is some h3</p>
|
||||||
|
</div></section></div></section>"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('multiple h3s', async () => {
|
||||||
|
const input = ['### One', 'x', '### Two', 'y'].join('\n')
|
||||||
|
const { html } = await render(input)
|
||||||
|
expect(html).toMatchInlineSnapshot(`
|
||||||
|
"<section class="h2-section"><div class="body h3-section-list">
|
||||||
|
<section class="h3-section"><h3 id="one">One</h3><div class="body">
|
||||||
|
<p>x</p>
|
||||||
|
</div></section><section class="h3-section"><h3 id="two">Two</h3><div class="body">
|
||||||
|
<p>y</p>
|
||||||
|
</div></section></div></section>"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('multiple h2s and h3s', async () => {
|
||||||
|
const input = [
|
||||||
|
'## Sun',
|
||||||
|
'### One',
|
||||||
|
'x',
|
||||||
|
'### Two',
|
||||||
|
'y',
|
||||||
|
'## Moon',
|
||||||
|
'### Three',
|
||||||
|
'x',
|
||||||
|
'### Four',
|
||||||
|
'y'
|
||||||
|
].join('\n')
|
||||||
|
const { html } = await render(input)
|
||||||
|
expect(html).toMatchInlineSnapshot(`
|
||||||
|
"<section class="h2-section"><h2 id="sun">Sun</h2><div class="body h3-section-list">
|
||||||
|
<section class="h3-section"><h3 id="one">One</h3><div class="body">
|
||||||
|
<p>x</p>
|
||||||
|
</div></section><section class="h3-section"><h3 id="two">Two</h3><div class="body">
|
||||||
|
<p>y</p>
|
||||||
|
</div></section></div></section><section class="h2-section"><h2 id="moon">Moon</h2><div class="body h3-section-list">
|
||||||
|
<section class="h3-section"><h3 id="three">Three</h3><div class="body">
|
||||||
|
<p>x</p>
|
||||||
|
</div></section><section class="h3-section"><h3 id="four">Four</h3><div class="body">
|
||||||
|
<p>y</p>
|
||||||
|
</div></section></div></section>"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('nothing', async () => {
|
||||||
|
const input = ['Nothing'].join('\n')
|
||||||
|
const { html } = await render(input)
|
||||||
|
expect(html).toMatchInlineSnapshot(`
|
||||||
|
"<section class="h2-section"><div class="body h3-section-list">
|
||||||
|
<section class="h3-section"><div class="body"><p>Nothing</p>
|
||||||
|
</div></section></div></section>"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('h3s with a class', async () => {
|
||||||
|
const input = ['### One', 'x', '### Two', '{: .-prime}', 'y'].join('\n')
|
||||||
|
const { html } = await render(input)
|
||||||
|
expect(html).toMatchInlineSnapshot(`
|
||||||
|
"<section class="h2-section"><div class="body h3-section-list">
|
||||||
|
<section class="h3-section"><h3 id="one">One</h3><div class="body">
|
||||||
|
<p>x</p>
|
||||||
|
</div></section><section class="h3-section -prime"><h3 class="-prime" id="two">Two</h3><div class="body -prime">
|
||||||
|
<p>y</p>
|
||||||
|
</div></section></div></section>"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('h2 class', async () => {
|
||||||
|
const input = ['## Intro', '{: .-three-column}', '### One', 'x'].join('\n')
|
||||||
|
const { html } = await render(input)
|
||||||
|
expect(html).toMatchInlineSnapshot(`
|
||||||
|
"<section class="h2-section -three-column"><h2 class="-three-column" id="intro">Intro</h2><div class="body h3-section-list -three-column">
|
||||||
|
<section class="h3-section"><h3 id="one">One</h3><div class="body">
|
||||||
|
<p>x</p>
|
||||||
|
</div></section></div></section>"
|
||||||
|
`)
|
||||||
|
})
|
119
src/lib/render.ts
Normal file
119
src/lib/render.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import rehypePrism from '@mapbox/rehype-prism'
|
||||||
|
import rehypeParse from 'rehype-parse'
|
||||||
|
import rehypeStringify from 'rehype-stringify'
|
||||||
|
import { unified } from 'unified'
|
||||||
|
import { renderKramdown } from './kramdown'
|
||||||
|
import { plugin as rehypeSectionize } from '@rstacruz/rehype-sectionize'
|
||||||
|
|
||||||
|
const PRISM_CONFIG = {
|
||||||
|
// For a list of languages Prism supports:
|
||||||
|
// https://github.com/PrismJS/prism/tree/master/components
|
||||||
|
alias: {
|
||||||
|
bash: ['sh', 'fish'],
|
||||||
|
ini: ['dosini'],
|
||||||
|
pug: ['jade'],
|
||||||
|
// "ignore" actually is for gitignore files, but it's closest to a
|
||||||
|
// neutral highlighting that I can find
|
||||||
|
ignore: [
|
||||||
|
// "nohighlight" was a Jekyll thing to prevent syntax highlighting
|
||||||
|
'nohighlight',
|
||||||
|
'org'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const REHYPE_SECTIONIZE_CONFIG = [
|
||||||
|
{
|
||||||
|
level: 'h2',
|
||||||
|
prelude: {
|
||||||
|
enabled: true,
|
||||||
|
tagName: 'section',
|
||||||
|
properties: { className: 'h3-section-list' }
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
addHeadingClass: true,
|
||||||
|
tagName: 'section',
|
||||||
|
properties: { className: 'h2-section' }
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
addHeadingClass: true,
|
||||||
|
enabled: true,
|
||||||
|
tagName: 'div',
|
||||||
|
properties: { className: 'body h3-section-list' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 'h3',
|
||||||
|
prelude: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
addHeadingClass: true,
|
||||||
|
properties: { className: 'h3-section' },
|
||||||
|
tagName: 'section'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
addHeadingClass: true,
|
||||||
|
enabled: true,
|
||||||
|
properties: { className: 'body' },
|
||||||
|
tagName: 'div'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders Markdown to HTML via Kramdown and applies some post-processing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function render(input: string): Promise<{ html: string }> {
|
||||||
|
let { html } = await renderKramdown(input)
|
||||||
|
html = addInitialH2(html)
|
||||||
|
html = addH3s(html)
|
||||||
|
html = await processRehype(html)
|
||||||
|
html = removeBlankHeadings(html)
|
||||||
|
return { html }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject extra H2 if needed. This fixes layout issues
|
||||||
|
function addInitialH2(html: string): string {
|
||||||
|
if (html.trim().startsWith('<h2')) return html
|
||||||
|
return `<h2></h2>\n${html}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extra H3's in cases of `h2 + (ul|p|ol)`
|
||||||
|
// Fixes layout issues in "Also see" sections (eg, /awscli)
|
||||||
|
// The blank H3's will be removed later
|
||||||
|
function addH3s(html: string): string {
|
||||||
|
html = html.replace(
|
||||||
|
/(\/h2>[\s\r\n]*)(<(?:ul|p|ol))/g,
|
||||||
|
(_, closing, opening) => {
|
||||||
|
return `${closing}<h3></h3>${opening}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBlankHeadings(html: string): string {
|
||||||
|
return html.replace(/<h3><\/h3>/g, '').replace(/<h2><\/h2>/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs through Rehype to add syntax highlighting and more.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function processRehype(inputHtml: string): Promise<string> {
|
||||||
|
const processResult = await unified()
|
||||||
|
.use(rehypeParse)
|
||||||
|
// @ts-expect-error dunno how to fix this
|
||||||
|
.use(rehypePrism, PRISM_CONFIG)
|
||||||
|
.use(rehypeStringify)
|
||||||
|
// @ts-expect-error dunno how to fix this
|
||||||
|
.use(rehypeSectionize, REHYPE_SECTIONIZE_CONFIG)
|
||||||
|
.process(inputHtml)
|
||||||
|
|
||||||
|
let html = String(processResult)
|
||||||
|
html = html
|
||||||
|
.replace('<html><head></head><body>', '')
|
||||||
|
.replace('</body></html>', '')
|
||||||
|
return html
|
||||||
|
}
|
136
src/lib/seo/__snapshots__/seo.test.ts.snap
Normal file
136
src/lib/seo/__snapshots__/seo.test.ts.snap
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`getSEOPropsForPage() > title only 1`] = `
|
||||||
|
{
|
||||||
|
"links": {
|
||||||
|
"canonical": "https://devhints.io/react",
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"app:pageurl": "https://devhints.io/react",
|
||||||
|
"description": "The one-page guide to React: usage, examples, links, snippets, and more.",
|
||||||
|
},
|
||||||
|
"metaProperties": {
|
||||||
|
"article:tag": [],
|
||||||
|
"og:description": "The one-page guide to React: usage, examples, links, snippets, and more.",
|
||||||
|
"og:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"og:image:height": "471",
|
||||||
|
"og:image:width": "900",
|
||||||
|
"og:site_name": "Devhints.io cheatsheets",
|
||||||
|
"og:title": "React cheatsheet",
|
||||||
|
"og:type": "article",
|
||||||
|
"og:url": "https://devhints.io/react",
|
||||||
|
"twitter:description": "The one-page guide to React: usage, examples, links, snippets, and more.",
|
||||||
|
"twitter:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"twitter:title": "React cheatsheet",
|
||||||
|
},
|
||||||
|
"title": "React cheatsheet",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getSEOPropsForPage() > with Markdown description 1`] = `
|
||||||
|
{
|
||||||
|
"links": {
|
||||||
|
"canonical": "https://devhints.io/react",
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"app:pageurl": "https://devhints.io/react",
|
||||||
|
"description": "A React cheatsheet (Markdown text) · One-page guide to React",
|
||||||
|
},
|
||||||
|
"metaProperties": {
|
||||||
|
"article:tag": [],
|
||||||
|
"og:description": "A React cheatsheet (Markdown text) · One-page guide to React",
|
||||||
|
"og:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"og:image:height": "471",
|
||||||
|
"og:image:width": "900",
|
||||||
|
"og:site_name": "Devhints.io cheatsheets",
|
||||||
|
"og:title": "React cheatsheet",
|
||||||
|
"og:type": "article",
|
||||||
|
"og:url": "https://devhints.io/react",
|
||||||
|
"twitter:description": "A React cheatsheet (Markdown text) · One-page guide to React",
|
||||||
|
"twitter:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"twitter:title": "React cheatsheet",
|
||||||
|
},
|
||||||
|
"title": "React cheatsheet",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getSEOPropsForPage() > with description 1`] = `
|
||||||
|
{
|
||||||
|
"links": {
|
||||||
|
"canonical": "https://devhints.io/react",
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"app:pageurl": "https://devhints.io/react",
|
||||||
|
"description": "A React cheatsheet · One-page guide to React",
|
||||||
|
},
|
||||||
|
"metaProperties": {
|
||||||
|
"article:tag": [],
|
||||||
|
"og:description": "A React cheatsheet · One-page guide to React",
|
||||||
|
"og:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"og:image:height": "471",
|
||||||
|
"og:image:width": "900",
|
||||||
|
"og:site_name": "Devhints.io cheatsheets",
|
||||||
|
"og:title": "React cheatsheet",
|
||||||
|
"og:type": "article",
|
||||||
|
"og:url": "https://devhints.io/react",
|
||||||
|
"twitter:description": "A React cheatsheet · One-page guide to React",
|
||||||
|
"twitter:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"twitter:title": "React cheatsheet",
|
||||||
|
},
|
||||||
|
"title": "React cheatsheet",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getSEOPropsForPage() > with description, keywords 1`] = `
|
||||||
|
{
|
||||||
|
"links": {
|
||||||
|
"canonical": "https://devhints.io/react",
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"app:pageurl": "https://devhints.io/react",
|
||||||
|
"description": "A React cheatsheet · One-page guide to React",
|
||||||
|
},
|
||||||
|
"metaProperties": {
|
||||||
|
"article:tag": [],
|
||||||
|
"og:description": "A React cheatsheet · One-page guide to React",
|
||||||
|
"og:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"og:image:height": "471",
|
||||||
|
"og:image:width": "900",
|
||||||
|
"og:site_name": "Devhints.io cheatsheets",
|
||||||
|
"og:title": "React cheatsheet",
|
||||||
|
"og:type": "article",
|
||||||
|
"og:url": "https://devhints.io/react",
|
||||||
|
"twitter:description": "A React cheatsheet · One-page guide to React",
|
||||||
|
"twitter:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"twitter:title": "React cheatsheet",
|
||||||
|
},
|
||||||
|
"title": "React cheatsheet",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getSEOPropsForPage() > with keywords 1`] = `
|
||||||
|
{
|
||||||
|
"links": {
|
||||||
|
"canonical": "https://devhints.io/react",
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"app:pageurl": "https://devhints.io/react",
|
||||||
|
"description": "hooks · components · props · One-page guide to React",
|
||||||
|
},
|
||||||
|
"metaProperties": {
|
||||||
|
"article:tag": [],
|
||||||
|
"og:description": "hooks · components · props · One-page guide to React",
|
||||||
|
"og:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"og:image:height": "471",
|
||||||
|
"og:image:width": "900",
|
||||||
|
"og:site_name": "Devhints.io cheatsheets",
|
||||||
|
"og:title": "React cheatsheet",
|
||||||
|
"og:type": "article",
|
||||||
|
"og:url": "https://devhints.io/react",
|
||||||
|
"twitter:description": "hooks · components · props · One-page guide to React",
|
||||||
|
"twitter:image": "https://assets.devhints.io/previews/react.jpg",
|
||||||
|
"twitter:title": "React cheatsheet",
|
||||||
|
},
|
||||||
|
"title": "React cheatsheet",
|
||||||
|
}
|
||||||
|
`;
|
53
src/lib/seo/jsonJd.test.ts
Normal file
53
src/lib/seo/jsonJd.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { getJSONLDsForPage } from './jsonLd'
|
||||||
|
|
||||||
|
it('works', () => {
|
||||||
|
const page = {
|
||||||
|
slug: 'react',
|
||||||
|
frontmatter: {
|
||||||
|
title: 'React',
|
||||||
|
description: 'A React cheatsheet',
|
||||||
|
category: 'JavaScript'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = getJSONLDsForPage(page)
|
||||||
|
expect(output).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"@context": "http://schema.org",
|
||||||
|
"@type": "NewsArticle",
|
||||||
|
"description": "A React cheatsheet · One-page guide to React",
|
||||||
|
"headline": "React cheatsheet",
|
||||||
|
"image": [
|
||||||
|
"https://assets.devhints.io/previews/react.jpg",
|
||||||
|
],
|
||||||
|
"mainEntityOfPage": {
|
||||||
|
"@id": "https://google.com/article",
|
||||||
|
"@type": "WebPage",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@context": "http://schema.org",
|
||||||
|
"@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"item": {
|
||||||
|
"@id": "https://devhints.io/#javascript",
|
||||||
|
"name": "JavaScript",
|
||||||
|
},
|
||||||
|
"position": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
"item": {
|
||||||
|
"@id": "https://devhints.io/react",
|
||||||
|
"name": "React cheatsheet",
|
||||||
|
},
|
||||||
|
"position": 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
})
|
59
src/lib/seo/jsonLd.ts
Normal file
59
src/lib/seo/jsonLd.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import type { JsonLdDocument } from '~/types/JsonLdDocument'
|
||||||
|
import { site } from '../../config'
|
||||||
|
import { getDescription, getPageImage, getPageURL } from './seo'
|
||||||
|
|
||||||
|
export function getJSONLDsForPage(page: {
|
||||||
|
// TODO: rename getJsonLdSchemasForPage
|
||||||
|
slug: string
|
||||||
|
frontmatter: {
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
category?: string
|
||||||
|
}
|
||||||
|
}): Array<JsonLdDocument> {
|
||||||
|
const description = getDescription(page)
|
||||||
|
const image = getPageImage(page)
|
||||||
|
const url = getPageURL(page)
|
||||||
|
const category = page.frontmatter.category ?? 'Others'
|
||||||
|
const categoryAnchor = category.toLowerCase().replace(/ /g, '-')
|
||||||
|
const headline = page.frontmatter.title
|
||||||
|
? `${page.frontmatter.title} cheatsheet`
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const newsArticle: JsonLdDocument = {
|
||||||
|
'@context': 'http://schema.org',
|
||||||
|
'@type': 'NewsArticle',
|
||||||
|
mainEntityOfPage: {
|
||||||
|
'@type': 'WebPage',
|
||||||
|
'@id': 'https://google.com/article'
|
||||||
|
},
|
||||||
|
headline,
|
||||||
|
image: [image],
|
||||||
|
description: description
|
||||||
|
}
|
||||||
|
|
||||||
|
const breadcrumb: JsonLdDocument = {
|
||||||
|
'@context': 'http://schema.org',
|
||||||
|
'@type': 'BreadcrumbList',
|
||||||
|
itemListElement: [
|
||||||
|
{
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: 1,
|
||||||
|
item: {
|
||||||
|
'@id': `${site.url}/#${categoryAnchor}`,
|
||||||
|
name: category
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: 2,
|
||||||
|
item: {
|
||||||
|
'@id': url,
|
||||||
|
name: headline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [newsArticle, breadcrumb]
|
||||||
|
}
|
58
src/lib/seo/seo.test.ts
Normal file
58
src/lib/seo/seo.test.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { getSEOPropsForPage, toPlainText } from './seo'
|
||||||
|
|
||||||
|
const slug = 'react'
|
||||||
|
const title = 'React'
|
||||||
|
const description = 'A React cheatsheet'
|
||||||
|
const markdownDescription =
|
||||||
|
'A [React](https://react.dev) cheatsheet (*Markdown text*)'
|
||||||
|
const keywords = ['hooks', 'components', 'props']
|
||||||
|
|
||||||
|
describe('getSEOPropsForPage()', () => {
|
||||||
|
runTest({
|
||||||
|
title: 'title only',
|
||||||
|
input: { slug, frontmatter: { title } }
|
||||||
|
})
|
||||||
|
runTest({
|
||||||
|
title: 'with description',
|
||||||
|
input: { slug, frontmatter: { title, description } }
|
||||||
|
})
|
||||||
|
runTest({
|
||||||
|
title: 'with Markdown description',
|
||||||
|
input: { slug, frontmatter: { title, description: markdownDescription } }
|
||||||
|
})
|
||||||
|
runTest({
|
||||||
|
title: 'with description, keywords',
|
||||||
|
input: { slug, frontmatter: { title, description, keywords } }
|
||||||
|
})
|
||||||
|
runTest({
|
||||||
|
title: 'with keywords',
|
||||||
|
input: { slug, frontmatter: { title, keywords } }
|
||||||
|
})
|
||||||
|
|
||||||
|
function runTest({ title, input }: { title: string; input: any }) {
|
||||||
|
test(title, () => {
|
||||||
|
const output = getSEOPropsForPage(input)
|
||||||
|
expect(output).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('toPlainText()', () => {
|
||||||
|
test('converts markdown to plain text', () => {
|
||||||
|
const input = 'This is a **bold** text'
|
||||||
|
const output = toPlainText(input)
|
||||||
|
expect(output).toEqual('This is a bold text')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('removes HTML tags', () => {
|
||||||
|
const input = '<p>This is a <strong>bold</strong> text</p>'
|
||||||
|
const output = toPlainText(input)
|
||||||
|
expect(output).toEqual('This is a bold text')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles empty input', () => {
|
||||||
|
const input = ''
|
||||||
|
const output = toPlainText(input)
|
||||||
|
expect(output).toEqual('')
|
||||||
|
})
|
||||||
|
})
|
151
src/lib/seo/seo.ts
Normal file
151
src/lib/seo/seo.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import snarkdown from 'snarkdown'
|
||||||
|
import { site } from '../../config'
|
||||||
|
import type { SheetPage } from '../page'
|
||||||
|
|
||||||
|
export function getSEOPropsForHome() {
|
||||||
|
const t = {
|
||||||
|
title: 'Devhints — TL;DR for developer documentation',
|
||||||
|
description: 'A ridiculous collection of web development cheatsheets'
|
||||||
|
}
|
||||||
|
const url = site.url
|
||||||
|
const image = 'https://assets.devhints.io/previews/index.jpg'
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t.title,
|
||||||
|
links: {
|
||||||
|
canonical: url
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
description: t.description,
|
||||||
|
'app:pageurl': url
|
||||||
|
},
|
||||||
|
metaProperties: denull({
|
||||||
|
'og:description': t.description,
|
||||||
|
'og:image:height': '471',
|
||||||
|
'og:image': image,
|
||||||
|
'og:image:width': '900',
|
||||||
|
'og:site_name': site.title,
|
||||||
|
'og:title': t.title,
|
||||||
|
'og:type': 'website',
|
||||||
|
'og:url': url,
|
||||||
|
// BUG: twitter card props should be metaNames
|
||||||
|
'twitter:title': image,
|
||||||
|
'twitter:image': image,
|
||||||
|
'twitter:description': t.description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSEOPropsForPage(
|
||||||
|
page: Pick<SheetPage, 'slug' | 'frontmatter'>
|
||||||
|
) {
|
||||||
|
const title = `${page.frontmatter.title} cheatsheet`
|
||||||
|
const description = getDescription(page)
|
||||||
|
const image = getPageImage(page)
|
||||||
|
const url = getPageURL(page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
links: {
|
||||||
|
canonical: url
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
description,
|
||||||
|
'app:pageurl': url
|
||||||
|
},
|
||||||
|
metaProperties: denull({
|
||||||
|
'og:description': description,
|
||||||
|
'og:image:height': '471',
|
||||||
|
'og:image': image,
|
||||||
|
'og:image:width': '900',
|
||||||
|
'og:site_name': site.title,
|
||||||
|
'og:title': title,
|
||||||
|
'og:type': 'article',
|
||||||
|
'og:url': url,
|
||||||
|
'article:tag': page.frontmatter.tags ?? [],
|
||||||
|
'article:section': page.frontmatter.category,
|
||||||
|
// BUG: twitter card props should be metaNames
|
||||||
|
'twitter:title': title,
|
||||||
|
'twitter:image': image,
|
||||||
|
'twitter:description': description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a description for a page
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getDescription(page: {
|
||||||
|
frontmatter: {
|
||||||
|
title?: string
|
||||||
|
intro?: string
|
||||||
|
description?: string
|
||||||
|
keywords?: string[]
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const t = {
|
||||||
|
withDescriptionAndIntro: `{description} {intro}`,
|
||||||
|
withDescription: `{description} · One-page guide to {title}`,
|
||||||
|
withKeywordsAndIntro: `{keywords} · {intro}`,
|
||||||
|
withKeywords: `{keywords} · One-page guide to {title}`,
|
||||||
|
withIntro: `One-page guide to {title}: usage, examples, and more. {intro}`,
|
||||||
|
default: `The one-page guide to {title}: usage, examples, links, snippets, and more.`
|
||||||
|
}
|
||||||
|
|
||||||
|
let fmt: string = t.default
|
||||||
|
|
||||||
|
if (page.frontmatter.description && page.frontmatter.intro) {
|
||||||
|
fmt = t.withDescriptionAndIntro
|
||||||
|
} else if (page.frontmatter.description) {
|
||||||
|
fmt = t.withDescription
|
||||||
|
} else if (page.frontmatter.keywords && page.frontmatter.intro) {
|
||||||
|
fmt = t.withKeywordsAndIntro
|
||||||
|
} else if (page.frontmatter.keywords) {
|
||||||
|
fmt = t.withKeywords
|
||||||
|
} else if (page.frontmatter.intro) {
|
||||||
|
fmt = t.withIntro
|
||||||
|
} else {
|
||||||
|
fmt = t.default
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt
|
||||||
|
.replace('{title}', () => page.frontmatter.title ?? '')
|
||||||
|
.replace('{keywords}', () => (page.frontmatter.keywords ?? []).join(' · '))
|
||||||
|
.replace('{intro}', () => toPlainText(page.frontmatter.intro ?? ''))
|
||||||
|
.replace('{description}', () =>
|
||||||
|
toPlainText(page.frontmatter.description ?? '')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove falsy values from an object
|
||||||
|
*/
|
||||||
|
|
||||||
|
function denull(
|
||||||
|
record: Record<string, string | string[] | null | undefined>
|
||||||
|
): Record<string, string | string[]> {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(record).filter((entry): entry is [string, string] =>
|
||||||
|
Boolean(entry[1])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPageImage({ slug }: { slug: string }) {
|
||||||
|
return `https://assets.devhints.io/previews/${slug}.jpg`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPageURL({ slug }: { slug: string }) {
|
||||||
|
return new URL(`/${slug}`, site.url).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Markdown to plain text.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function toPlainText(input: string) {
|
||||||
|
const html = snarkdown(input)
|
||||||
|
const plainText = html.replace(/<[^>]*>/g, '').replace(/\n/g, ' ')
|
||||||
|
return plainText
|
||||||
|
}
|
33
src/pages/404.astro
Normal file
33
src/pages/404.astro
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '~/components/BaseLayout.astro'
|
||||||
|
import TopNav from '~/components/TopNav.astro'
|
||||||
|
import SearchForm from '~/components/V2017Sheet/SearchForm.astro'
|
||||||
|
|
||||||
|
const t = {
|
||||||
|
title: 'Not found',
|
||||||
|
description: "Sorry, we don't have a cheatsheet for this yet. Try searching!",
|
||||||
|
goHome: 'Back to home'
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="Not found">
|
||||||
|
<TopNav noEdit noBack />
|
||||||
|
<div class="body-area -slim">
|
||||||
|
<div class="site-header">
|
||||||
|
<h1>{t.title}</h1>
|
||||||
|
<p>{t.description}</p>
|
||||||
|
<SearchForm isLive />
|
||||||
|
<p class="action">
|
||||||
|
<a class="push-button" href="/">{t.goHome}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../sass/2017/utils';
|
||||||
|
@import '../sass/2017/placeholders/push-button';
|
||||||
|
@import '../sass/2017/components/body-area';
|
||||||
|
@import '../sass/2017/components/push-button';
|
||||||
|
@import '../sass/2017/components/site-header';
|
||||||
|
</style>
|
23
src/pages/[...slug].astro
Normal file
23
src/pages/[...slug].astro
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
import V2017Sheet from '~/components/V2017Sheet.astro'
|
||||||
|
import { getPages } from '~/lib/page'
|
||||||
|
import type { SheetPage } from '~/lib/page'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
page: SheetPage
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = Astro.props as Props
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const pages = await getPages()
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
Object.values(pages).map((page) => {
|
||||||
|
return { props: { page }, params: { slug: page.slug } }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<V2017Sheet page={props.page} />
|
14
src/pages/__tests__/searchindex.json.test.ts
Normal file
14
src/pages/__tests__/searchindex.json.test.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { GET } from '../searchindex.json'
|
||||||
|
|
||||||
|
test('has data', async () => {
|
||||||
|
const response = await GET()
|
||||||
|
const data = await response.json()
|
||||||
|
expect(typeof data.index).toEqual('object')
|
||||||
|
expect(Array.isArray(data.index.records)).toBeTruthy()
|
||||||
|
expect(Array.isArray(data.rows)).toBeTruthy()
|
||||||
|
|
||||||
|
for (const row of data.rows) {
|
||||||
|
expect(typeof row.title).toBeTruthy() // some titles are numbers?
|
||||||
|
expect(typeof row.slug).toEqual('string')
|
||||||
|
}
|
||||||
|
})
|
21
src/pages/__tests__/sitemap.xml.test.ts
Normal file
21
src/pages/__tests__/sitemap.xml.test.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { GET } from '../sitemap.xml'
|
||||||
|
|
||||||
|
let lines: string[]
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
if (lines) return
|
||||||
|
const response = await GET()
|
||||||
|
const body = await response.text()
|
||||||
|
lines = body.split('\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('has data', async () => {
|
||||||
|
expect(lines).toContain('<url><loc>https://devhints.io/react</loc></url>')
|
||||||
|
expect(lines).toContain('<url><loc>https://devhints.io/bash</loc></url>')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('skip unlisted sheets', async () => {
|
||||||
|
expect(lines).not.toContain(
|
||||||
|
'<url><loc>https://devhints.io/tests/basic</loc></url>'
|
||||||
|
)
|
||||||
|
})
|
102
src/pages/index.astro
Normal file
102
src/pages/index.astro
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
import { getPages } from '~/lib/page'
|
||||||
|
import { getSEOPropsForHome } from '~/lib/seo/seo'
|
||||||
|
import {
|
||||||
|
getPagesByCategory,
|
||||||
|
getFeaturedPages,
|
||||||
|
getRecentPages
|
||||||
|
} from '~/lib/page/queries'
|
||||||
|
import TopNav from '~/components/TopNav.astro'
|
||||||
|
import BaseLayout from '~/components/BaseLayout.astro'
|
||||||
|
import PageListItem from '~/components/V2017Home/PageListItem.astro'
|
||||||
|
import FeaturedPages from '~/components/V2017Home/FeaturedPages.astro'
|
||||||
|
import Announcements from '~/components/V2017Home/Announcements.astro'
|
||||||
|
import SearchForm from '~/components/V2017Sheet/SearchForm.astro'
|
||||||
|
import CarbonBox from '~/components/V2017/CarbonBox.astro'
|
||||||
|
import SEO from '~/components/SEO/SEO.astro'
|
||||||
|
import { urls } from '~/config'
|
||||||
|
|
||||||
|
const pages = await getPages()
|
||||||
|
const pageCategories = getPagesByCategory(pages)
|
||||||
|
const featuredPages = getFeaturedPages(pages, { maxCount: 8 })
|
||||||
|
const recentPages = getRecentPages(pages, { maxCount: 18 })
|
||||||
|
|
||||||
|
const seoProps = getSEOPropsForHome()
|
||||||
|
const t = {
|
||||||
|
title: "Rico's cheatsheets",
|
||||||
|
tagline: `Hey! I'm <a href='https://ricostacruz.com'>@rstacruz</a> and this is a modest collection of cheatsheets I've written.`,
|
||||||
|
recentlyUpdated: 'Recently updated',
|
||||||
|
seeSomethingMissing: 'See something missing?',
|
||||||
|
requestCheatsheet: 'Request cheatsheet'
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout>
|
||||||
|
<Fragment slot="head">
|
||||||
|
<SEO {...seoProps} />
|
||||||
|
</Fragment>
|
||||||
|
<TopNav noEdit noBack />
|
||||||
|
<div class="body-area -slim">
|
||||||
|
<div class="site-header" role="banner">
|
||||||
|
<h1>{t.title}</h1>
|
||||||
|
<p set:html={t.tagline} />
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<SearchForm isLive />
|
||||||
|
|
||||||
|
{/* Publicite */}
|
||||||
|
<div class="pubbox"><CarbonBox /></div>
|
||||||
|
|
||||||
|
{/* TODO: announcement */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pages-list" role="main">
|
||||||
|
{/* Featured pages */}
|
||||||
|
<FeaturedPages pages={featuredPages} />
|
||||||
|
|
||||||
|
{/* Recent pages */}
|
||||||
|
<h2 class="category item" data-js-searchable-header>
|
||||||
|
<span>{t.recentlyUpdated}</span>
|
||||||
|
</h2>
|
||||||
|
{recentPages.map((page) => <PageListItem page={page} />)}
|
||||||
|
|
||||||
|
{
|
||||||
|
Object.values(pageCategories).map((category) => {
|
||||||
|
if (category.pages.length === 0) return
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2 class="category item" data-js-searchable-header>
|
||||||
|
<span>{category.title}</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{category.pages.map((page) => (
|
||||||
|
<PageListItem page={page} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="message item missing-message">
|
||||||
|
<h3>{t.seeSomethingMissing}</h3>
|
||||||
|
<p>
|
||||||
|
<a class="push-button" href={urls.newCheatsheetUrl}
|
||||||
|
>{t.requestCheatsheet}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Announcements />
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style lang="scss" is:global>
|
||||||
|
@import '../sass/2017/utils';
|
||||||
|
@import '../sass/2017/components/body-area';
|
||||||
|
@import '../sass/2017/components/missing-message';
|
||||||
|
@import '../sass/2017/components/pages-list';
|
||||||
|
@import '../sass/2017/components/push-button';
|
||||||
|
@import '../sass/2017/components/notice-box';
|
||||||
|
@import '../sass/2017/components/site-header';
|
||||||
|
</style>
|
16
src/pages/searchindex.json.ts
Normal file
16
src/pages/searchindex.json.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { buildFuseIndex } from '~/lib/fuseSearch/fuseSearch'
|
||||||
|
import { getPages } from '~/lib/page'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a search index that can be hydrated later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const result = buildFuseIndex(await getPages())
|
||||||
|
return new Response(JSON.stringify(result), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
26
src/pages/sitemap.xml.ts
Normal file
26
src/pages/sitemap.xml.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { site } from '~/config'
|
||||||
|
import { getPages } from '~/lib/page'
|
||||||
|
import { isListed } from '~/lib/page/accessors'
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const lines = [
|
||||||
|
`<?xml version="1.0" encoding="UTF-8"?>`,
|
||||||
|
`<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`
|
||||||
|
]
|
||||||
|
|
||||||
|
const visiblePages = Object.values(await getPages()).filter(isListed)
|
||||||
|
|
||||||
|
for (const page of visiblePages) {
|
||||||
|
const url = `${site.url}/${page.slug}`
|
||||||
|
lines.push(`<url><loc>${url}</loc></url>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`</urlset>`)
|
||||||
|
|
||||||
|
const data = lines.join('\n') + '\n'
|
||||||
|
|
||||||
|
return new Response(data, {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/xml' }
|
||||||
|
})
|
||||||
|
}
|
5
src/pages/test_scenarios/seo_tags_test.astro
Normal file
5
src/pages/test_scenarios/seo_tags_test.astro
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
import SEO from '~/components/SEO/SEO.astro'
|
||||||
|
---
|
||||||
|
|
||||||
|
<SEO metaProperties={{ 'article:tags': ['WIP', 'Featured'] }} />
|
28
src/ruby/cache_kramdown.rb
Normal file
28
src/ruby/cache_kramdown.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Caches Kramdown results in .cache/
|
||||||
|
# Normally, Kramdown is invoked as needed (kramdown.rb), but this is
|
||||||
|
# going to be slow down builds significantly. By doing all Kramdown
|
||||||
|
# rendering beforehand, this cuts down the time from 70s to 10s.
|
||||||
|
require 'fileutils'
|
||||||
|
require 'digest'
|
||||||
|
require_relative 'renderer'
|
||||||
|
|
||||||
|
# This seems to default to US-ASCII in some environments, which causes
|
||||||
|
# errors in some sheets
|
||||||
|
Encoding.default_external = Encoding::UTF_8
|
||||||
|
|
||||||
|
def remove_frontmatter(input_string)
|
||||||
|
input_string.sub(/\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
FileUtils.mkdir_p '.cache'
|
||||||
|
|
||||||
|
ARGV.each do |filepath|
|
||||||
|
input = File.read(filepath)
|
||||||
|
input = remove_frontmatter(input)
|
||||||
|
digest = Digest::SHA2.hexdigest(input.strip)
|
||||||
|
outfile = ".cache/#{digest}.html"
|
||||||
|
output = Renderer.render(input: input)
|
||||||
|
File.write(outfile, output)
|
||||||
|
end
|
4
src/ruby/kramdown.rb
Normal file
4
src/ruby/kramdown.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'renderer'
|
||||||
|
puts Renderer.render(input: $stdin.read)
|
42
src/ruby/renderer.rb
Normal file
42
src/ruby/renderer.rb
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Renders Markdown to HTML, emulating Jekyll 2 quirks.
|
||||||
|
module Renderer
|
||||||
|
module_function
|
||||||
|
|
||||||
|
KRAMDOWN_OPTIONS = {
|
||||||
|
input: 'GFM',
|
||||||
|
hard_wrap: false
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def render(input:)
|
||||||
|
require 'kramdown'
|
||||||
|
|
||||||
|
new_input = input.to_s
|
||||||
|
.gsub(/^{% ?raw ?%}\n/, '') # raw on its own line
|
||||||
|
.gsub(/{% ?raw ?%}/, '') # inline in another line
|
||||||
|
.gsub(/{% include (common\/[^ ]+) title="([^"]+)" %}/) {
|
||||||
|
# a reduced subset of Jekyll includes
|
||||||
|
title = $2
|
||||||
|
file = $1
|
||||||
|
filepath = Pathname.new("_includes/#{file}").cleanpath.to_s
|
||||||
|
|
||||||
|
if filepath.start_with?("/") || filepath.start_with?(".")
|
||||||
|
raise Errno::ENOENT, "Invalid include - #{filepath}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if !File.exist?(filepath)
|
||||||
|
raise Errno::ENOENT, "Cannot find include - #{filepath}"
|
||||||
|
end
|
||||||
|
|
||||||
|
data = File.read(filepath)
|
||||||
|
data = data.gsub(/{{ include.title }}/m, title)
|
||||||
|
data
|
||||||
|
}
|
||||||
|
.gsub(/^{% ?endraw ?%}\n/, '')
|
||||||
|
.gsub(/{% ?endraw ?%}/, '')
|
||||||
|
.gsub(/ {%- if 0 -%}{%- endif -%} /, '') # Used in jinja.md
|
||||||
|
.gsub(/{%- raw -%}\n?/, '') # Used in jinja.md
|
||||||
|
Kramdown::Document.new(new_input, KRAMDOWN_OPTIONS).to_html
|
||||||
|
end
|
||||||
|
end
|
71
src/ruby/renderer.test.rb
Normal file
71
src/ruby/renderer.test.rb
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'minitest/autorun'
|
||||||
|
|
||||||
|
require_relative 'renderer'
|
||||||
|
|
||||||
|
def run_test(input:, expected:, label:)
|
||||||
|
label ||= (input[0...20]).to_s
|
||||||
|
it(label) do
|
||||||
|
output = Renderer.render(input: input)
|
||||||
|
assert_equal(output.strip, expected.strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'Renderer' do
|
||||||
|
it 'with react.md' do
|
||||||
|
input = File.read('./react.md').split("---")[2].strip
|
||||||
|
output = Renderer.render(input: input)
|
||||||
|
refute_includes(output, %(%raw%))
|
||||||
|
refute_includes(output, %(% raw %))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'With "{% include %}" tags' do
|
||||||
|
input = ['{% include common/moment_format.md title="Moment" %}', 'This is some text'].join("\n")
|
||||||
|
output = Renderer.render(input: input)
|
||||||
|
assert_includes(output, %(<h2 class="-three-column" id="moment">Moment</h2>))
|
||||||
|
assert_includes(output, %(<p>This is some text</p>))
|
||||||
|
end
|
||||||
|
|
||||||
|
run_test(
|
||||||
|
label: 'Basic test',
|
||||||
|
input: 'hola mundo',
|
||||||
|
expected: '<p>hola mundo</p>'
|
||||||
|
)
|
||||||
|
|
||||||
|
run_test(
|
||||||
|
label: 'H1',
|
||||||
|
input: '# hola mundo',
|
||||||
|
expected: %(<h1 id="hola-mundo">hola mundo</h1>)
|
||||||
|
)
|
||||||
|
|
||||||
|
run_test(
|
||||||
|
label: 'H1 with class',
|
||||||
|
input: "# hola mundo\n{: .heading}",
|
||||||
|
expected: %(<h1 class="heading" id="hola-mundo">hola mundo</h1>)
|
||||||
|
)
|
||||||
|
|
||||||
|
run_test(
|
||||||
|
label: 'With "{% raw %}" tags',
|
||||||
|
input: ['{% raw %}', 'This is some text', '{% endraw %}'].join("\n"),
|
||||||
|
expected: %(<p>This is some text</p>)
|
||||||
|
)
|
||||||
|
|
||||||
|
run_test(
|
||||||
|
label: 'With "{%raw%}" tags',
|
||||||
|
input: ['{%raw%}', 'This is some text', '{%endraw%}'].join("\n"),
|
||||||
|
expected: %(<p>This is some text</p>)
|
||||||
|
)
|
||||||
|
|
||||||
|
run_test(
|
||||||
|
label: 'With "{% raw %}" tags with new line',
|
||||||
|
input: ['{% raw %}', 'This is some text', '{% endraw %}', 'next line'].join("\n"),
|
||||||
|
expected: %(<p>This is some text\nnext line</p>)
|
||||||
|
)
|
||||||
|
|
||||||
|
run_test(
|
||||||
|
label: 'With "{% raw %}" tags with 2 new lines',
|
||||||
|
input: ['{% raw %}', 'This is some text', '{% endraw %}', '', 'next paragraph'].join("\n"),
|
||||||
|
expected: %(<p>This is some text</p>\n\n<p>next paragraph</p>)
|
||||||
|
)
|
||||||
|
end
|
11
src/sass/2017/_utils.scss
Normal file
11
src/sass/2017/_utils.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Vendor
|
||||||
|
@import './variables';
|
||||||
|
@import '../vendor/ionicons-inline/ionicons';
|
||||||
|
@import '../vendor/modularscale/modularscale';
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
@import './utils/font-size';
|
||||||
|
@import './utils/gutter';
|
||||||
|
@import './utils/heading-style';
|
||||||
|
@import './utils/section-gutter';
|
||||||
|
@import './utils/section-with-container';
|
@ -2,7 +2,8 @@
|
|||||||
* Base
|
* Base
|
||||||
*/
|
*/
|
||||||
|
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
background: $base-body;
|
background: $base-body;
|
||||||
font-family: $body-font;
|
font-family: $body-font;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -21,7 +22,8 @@ body {
|
|||||||
* Code
|
* Code
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pre, code {
|
pre,
|
||||||
|
code {
|
||||||
font-family: $monospace-font;
|
font-family: $monospace-font;
|
||||||
letter-spacing: -0.03em;
|
letter-spacing: -0.03em;
|
||||||
}
|
}
|
@ -7,7 +7,9 @@
|
|||||||
background: white;
|
background: white;
|
||||||
padding-right: 48px;
|
padding-right: 48px;
|
||||||
animation: announcements-item-flyin 500ms ease-out;
|
animation: announcements-item-flyin 500ms ease-out;
|
||||||
transition: opacity 500ms linear, transform 500ms ease-out;
|
transition:
|
||||||
|
opacity 500ms linear,
|
||||||
|
transform 500ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-hide {
|
&.-hide {
|
||||||
@ -53,7 +55,7 @@
|
|||||||
|
|
||||||
& > .close::before {
|
& > .close::before {
|
||||||
// https://stackoverflow.com/a/30421654
|
// https://stackoverflow.com/a/30421654
|
||||||
content: unquote("\"")+str-insert("00D7", "\\", 1)+unquote("\"");
|
content: unquote('"') + str-insert('00D7', '\\', 1) + unquote('"');
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
26
src/sass/2017/components/autocomplete.scss
Normal file
26
src/sass/2017/components/autocomplete.scss
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Overrides for npm:autocompleter
|
||||||
|
|
||||||
|
.autocomplete {
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
padding-top: 0;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
box-shadow: $shadow3;
|
||||||
|
transform: translate(-8px, -2px);
|
||||||
|
}
|
||||||
|
.autocomplete > div {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.autocomplete > div:hover:not(.group) {
|
||||||
|
background: $base-body;
|
||||||
|
}
|
||||||
|
.autocomplete > div + div:not(.selected) {
|
||||||
|
box-shadow: inset 0 1px $line-color;
|
||||||
|
}
|
||||||
|
.autocomplete > div.selected,
|
||||||
|
.autocomplete > div.selected:hover {
|
||||||
|
background: $base-c;
|
||||||
|
color: #fff;
|
||||||
|
}
|
@ -9,6 +9,7 @@
|
|||||||
@include section-gutter(margin-right, $multiplier: -1);
|
@include section-gutter(margin-right, $multiplier: -1);
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
column-gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clearfix
|
// Clearfix
|
||||||
@ -22,8 +23,8 @@
|
|||||||
// Each section
|
// Each section
|
||||||
& > .h3-section {
|
& > .h3-section {
|
||||||
@include section-gutter(padding);
|
@include section-gutter(padding);
|
||||||
float: left;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
break-inside: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
@ -40,9 +41,7 @@
|
|||||||
.h3-section-list,
|
.h3-section-list,
|
||||||
.h3-section-list.-two-column {
|
.h3-section-list.-two-column {
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
& > .h3-section {
|
columns: 2;
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +50,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.h3-section-list.-one-column {
|
.h3-section-list.-one-column {
|
||||||
& > .h3-section {
|
@media (min-width: 769px) {
|
||||||
width: 100%;
|
columns: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .h3-section + .h3-section {
|
& > .h3-section + .h3-section {
|
||||||
@ -66,36 +65,20 @@
|
|||||||
|
|
||||||
.h3-section-list.-three-column {
|
.h3-section-list.-three-column {
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
& > .h3-section {
|
columns: 2;
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 961px) {
|
@media (min-width: 961px) {
|
||||||
& > .h3-section {
|
columns: 3;
|
||||||
width: 33.33%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Three column, left reference
|
|
||||||
*/
|
|
||||||
|
|
||||||
.h3-section-list.-left-reference {
|
.h3-section-list.-left-reference {
|
||||||
@media (min-width: 769px) {
|
@media (min-width: 769px) {
|
||||||
& > .h3-section {
|
columns: 3;
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 961px) {
|
& > .h3-section + .h3-section {
|
||||||
& > .h3-section {
|
width: 200%;
|
||||||
width: 66.67%;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .h3-section:first-child {
|
|
||||||
width: 33.33%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user