Migrate to 11ty (#661)
* Initial config and data
* Get an initial homepage building
* Render homepage correctly
* Get the JS basics working
* Clean up some obsolete cruft
* Nuke asset tag
* Update include syntax and create src dir
* Got everything to build ✨
* Building JS and CSS and other fix-ups
* Remove styles template since it's redundant
* Clean-up some more @optim leftovers
* Remove asset_url
* More clean-up
* Tidy up more syntax highlighting etc
* Fix ext icon on homepage block links
* Run prettier
* Fix broken image link
* Rename *.html -> liquid
* Working search etc
* Remove underscores from directories
* Add content directory
* Fix config for content dir
* Add edit on github link
* Fix leftover highlighting blocks
* Fix repo link
* fix some display issues
* Fix some issues with the build dirs
* Exclude from builds
* Run prettier
* Remove - it's only needed if false
* Fix up next templating
* Set-up basic SEO tags
* Add SEO content
* Run prettier
* Use serialize instead of JSON.stringify
* Configure 404 locally
* Update readme
* Passthrough robots.txt and rename 404
* Fix broken file-extensions
* Fix broken html
* Initial assets pipeline
* Fix bug with non-hashed file paths
* Run prettier
* Remove log statement
* Modify build scripts
* Fix some minor nits, beautify html
* Run prettier fix some naming for deploy scripts
* Fix lint issues
* fix circle config indent
* Fix invalid circle config
* Move deps
|
@ -3,45 +3,24 @@ version: 2.1
|
|||
references:
|
||||
build_container: &build_container
|
||||
docker:
|
||||
- image: circleci/ruby:2.6
|
||||
- image: circleci/node:12
|
||||
working_directory: ~/extension-workshop
|
||||
|
||||
restore_build_cache: &restore_build_cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- rubygems-v1-{{ checksum "Gemfile.lock" }}
|
||||
- rubygems-v1-fallback
|
||||
|
||||
run_bundle_install: &run_bundle_install
|
||||
run:
|
||||
name: Bundle Install
|
||||
command: bundle check || bundle install
|
||||
|
||||
save_build_cache: &save_build_cache
|
||||
save_cache:
|
||||
key: rubygems-v1-{{ checksum "Gemfile.lock" }}
|
||||
paths:
|
||||
- vendor/bundle
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
|
||||
run_yarn_install: &run_yarn_install
|
||||
run:
|
||||
name: yarn install
|
||||
command: |
|
||||
sudo apt-get update && sudo apt-get install -y apt-transport-https
|
||||
sudo apt-get install -y gcc g++ make
|
||||
name: Install Dependencies
|
||||
command: yarn install --immutable
|
||||
|
||||
# install nodejs v10
|
||||
# https://github.com/nodesource/distributions/blob/master/README.md#installation-instructions
|
||||
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# install yarn stable
|
||||
# https://yarnpkg.com/lang/en/docs/install/#debian-stable
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt-get update && sudo apt-get install -y yarn
|
||||
|
||||
yarn install
|
||||
save_build_cache: &save_build_cache
|
||||
save_cache:
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
run_build_test: &run_build_test
|
||||
run:
|
||||
|
@ -56,7 +35,7 @@ references:
|
|||
|
||||
deploy_container: &deploy_container
|
||||
docker:
|
||||
- image: circleci/python:3.8
|
||||
- image: circleci/python:3.8-node
|
||||
|
||||
attach_deploy_workspace: &attach_deploy_workspace
|
||||
attach_workspace:
|
||||
|
@ -74,16 +53,15 @@ jobs:
|
|||
steps:
|
||||
- checkout
|
||||
- *restore_build_cache
|
||||
- *run_bundle_install
|
||||
- *save_build_cache
|
||||
- *run_yarn_install
|
||||
- *save_build_cache
|
||||
- *run_build_test
|
||||
- run:
|
||||
name: Jekyll build unpublished
|
||||
name: Eleventy build unpublished
|
||||
environment:
|
||||
JEKYLL_ENV: production
|
||||
ELEVENTY_ENV: production
|
||||
SVGO_BIN: node_modules/svgo/bin/svgo
|
||||
command: bundle exec jekyll build --unpublished
|
||||
command: yarn build:unpublished
|
||||
no_output_timeout: 15m
|
||||
- *persist_build_workspace
|
||||
|
||||
|
@ -92,16 +70,15 @@ jobs:
|
|||
steps:
|
||||
- checkout
|
||||
- *restore_build_cache
|
||||
- *run_bundle_install
|
||||
- *save_build_cache
|
||||
- *run_yarn_install
|
||||
- *save_build_cache
|
||||
- *run_build_test
|
||||
- run:
|
||||
name: Jekyll build
|
||||
name: Eleventy build
|
||||
environment:
|
||||
JEKYLL_ENV: production
|
||||
ELEVENTY_ENV: production
|
||||
SVGO_BIN: node_modules/svgo/bin/svgo
|
||||
command: bundle exec jekyll build
|
||||
command: yarn build:production
|
||||
no_output_timeout: 15m
|
||||
- *persist_build_workspace
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
node_modules
|
||||
package.json
|
||||
README.md
|
||||
renovate.json
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
.eslintrc.js
|
|
@ -1,3 +1,3 @@
|
|||
_site
|
||||
.utils
|
||||
_assets/js/bundle.js
|
||||
build
|
||||
dist
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
module.exports = {
|
||||
globals: {
|
||||
jQuery: 'readable',
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
plugins: ['prettier'],
|
||||
extends: ['prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
_site
|
||||
.sass-cache
|
||||
.jekyll-metadata
|
||||
.jekyll-cache
|
||||
node_modules/
|
||||
rvm-installer
|
||||
rvm-installer.asc
|
||||
*.log
|
||||
|
||||
.sass-cache
|
||||
node_modules/
|
||||
.DS_Store
|
||||
__MACOSX/
|
||||
|
||||
.vscode/*
|
||||
build
|
||||
dist
|
||||
|
|
|
@ -6,10 +6,12 @@ Dockerfile
|
|||
/node_modules/
|
||||
Gemfile
|
||||
rvm-installer
|
||||
_site/
|
||||
build/
|
||||
*.lock
|
||||
# Allow files we want to process
|
||||
!*.js
|
||||
!*.scss
|
||||
|
||||
_assets/js/bundle.js
|
||||
|
||||
build
|
||||
dist
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
set -ex
|
||||
|
||||
if [ ! -d "_site" ]; then
|
||||
echo "Can't find /_site/ directory. Are you running from the correct"\
|
||||
if [ ! -d "dist" ]; then
|
||||
echo "Can't find /dist/ directory. Are you running from the correct"\
|
||||
"root directory?"
|
||||
exit 1
|
||||
fi
|
||||
|
@ -57,7 +57,7 @@ ACAO="\"Access-Control-Allow-Origin\": \"*\""
|
|||
[ -e version.json ] || $(dirname $0)/build-version-json.sh
|
||||
|
||||
if [ -e version.json ]; then
|
||||
mv version.json _site/__version__
|
||||
mv version.json dist/__version__
|
||||
# __version__ JSON; short cache
|
||||
aws s3 cp \
|
||||
--cache-control "max-age=${TEN_MINUTES}" \
|
||||
|
@ -65,7 +65,7 @@ if [ -e version.json ]; then
|
|||
--metadata "{${ACAO}, ${CSPSTATIC}, ${HSTS}, ${TYPE}, ${XSS}, ${XFRAME}, ${REFERRER}}" \
|
||||
--metadata-directive "REPLACE" \
|
||||
--acl "public-read" \
|
||||
_site/__version__ s3://${EXTENSION_WORKSHOP_BUCKET}/__version__
|
||||
dist/__version__ s3://${EXTENSION_WORKSHOP_BUCKET}/__version__
|
||||
fi
|
||||
|
||||
# HTML; short cache
|
||||
|
@ -77,7 +77,7 @@ aws s3 sync \
|
|||
--metadata "{${CSP}, ${HSTS}, ${TYPE}, ${XSS}, ${XFRAME}, ${REFERRER}}" \
|
||||
--metadata-directive "REPLACE" \
|
||||
--acl "public-read" \
|
||||
_site/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
dist/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
|
||||
# JSON; short cache
|
||||
aws s3 sync \
|
||||
|
@ -88,7 +88,7 @@ aws s3 sync \
|
|||
--metadata "{${ACAO}, ${CSPSTATIC}, ${HSTS}, ${TYPE}, ${XSS}, ${XFRAME}, ${REFERRER}}" \
|
||||
--metadata-directive "REPLACE" \
|
||||
--acl "public-read" \
|
||||
_site/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
dist/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
|
||||
# SVG; cache forever, assign correct content-type
|
||||
aws s3 sync \
|
||||
|
@ -99,7 +99,7 @@ aws s3 sync \
|
|||
--metadata "{${CSPSTATIC}, ${HSTS}, ${TYPE}, ${XSS}, ${XFRAME}, ${REFERRER}}" \
|
||||
--metadata-directive "REPLACE" \
|
||||
--acl "public-read" \
|
||||
_site/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
dist/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
|
||||
# Everything else; cache forever, because it has hashes in the filenames
|
||||
aws s3 sync \
|
||||
|
@ -108,11 +108,11 @@ aws s3 sync \
|
|||
--metadata "{${CSPSTATIC}, ${HSTS}, ${TYPE}, ${XSS}, ${XFRAME}, ${REFERRER}}" \
|
||||
--metadata-directive "REPLACE" \
|
||||
--acl "public-read" \
|
||||
_site/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
dist/ s3://${EXTENSION_WORKSHOP_BUCKET}/
|
||||
|
||||
# HTML - `path/index.html` to `path` resources; short cache
|
||||
for fn in $(find _site -name 'index.html' -not -path '_site/index.html'); do
|
||||
s3path=${fn#_site/}
|
||||
for fn in $(find dist -name 'index.html' -not -path 'dist/index.html'); do
|
||||
s3path=${fn#dist/}
|
||||
s3path=${s3path%/index.html}
|
||||
aws s3 cp \
|
||||
--cache-control "max-age=${TEN_MINUTES}" \
|
||||
|
|
9
404.html
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
layout: 404
|
||||
skip_index: true
|
||||
---
|
||||
|
||||
<h1>404</h1>
|
||||
|
||||
<p><strong>Page not found :(</strong></p>
|
||||
<p>The requested page could not be found.</p>
|
40
Gemfile
|
@ -1,40 +0,0 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
# Hello! This is where you manage which Jekyll version is used to run.
|
||||
# When you want to use a different version, change it below, save the
|
||||
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
|
||||
#
|
||||
# bundle exec jekyll serve
|
||||
#
|
||||
# This will help ensure the proper Jekyll version is running.
|
||||
# Happy Jekylling!
|
||||
# gem "jekyll", "~> 3.7.4"
|
||||
|
||||
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
||||
# gem "minima", "~> 2.0"
|
||||
|
||||
# If you have any plugins, put them here!
|
||||
group :jekyll_plugins do
|
||||
# gem 'jekyll-feed', '~> 0.11'
|
||||
gem 'jekyll-assets', '~> 3.0'
|
||||
gem 'jekyll-seo-tag', '~> 2.6.0'
|
||||
gem 'jekyll_pages_api' # --> for local development of plugin add this --> , :path => '../jekyll_pages_api'
|
||||
# gem 'github-pages', '~> 193'
|
||||
end
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||
|
||||
# Performance-booster for watching directories on Windows
|
||||
gem 'wdm', '~> 0.1.0' if Gem.win_platform?
|
||||
|
||||
# Image optimization
|
||||
gem 'image_optim', '~> 0.26.3'
|
||||
gem 'image_optim_pack', '~> 0.6.0.0'
|
||||
|
||||
# Uglifier for JS minification
|
||||
gem 'uglifier', '~> 4.1', '>= 4.1.20'
|
||||
|
||||
# Automatically adds a target="_blank" rel="noopener noreferrer" attribute
|
||||
# to all external links in Jekyll's content
|
||||
gem 'jekyll-target-blank'
|
129
Gemfile.lock
|
@ -1,129 +0,0 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (5.2.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.1.5)
|
||||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.7.0)
|
||||
exifr (1.3.6)
|
||||
extras (0.3.0)
|
||||
forwardable-extended (~> 2.5)
|
||||
fastimage (2.1.5)
|
||||
ffi (1.11.3)
|
||||
forwardable-extended (2.6.0)
|
||||
fspath (3.1.2)
|
||||
htmlentities (4.3.4)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_optim (0.26.5)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (>= 1.5, < 3)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
image_optim_pack (0.6.0.20191208)
|
||||
fspath (>= 2.1, < 4)
|
||||
image_optim (~> 0.19)
|
||||
image_size (2.0.2)
|
||||
in_threads (1.5.3)
|
||||
jekyll (3.8.6)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 0.7)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (~> 1.14)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3.3)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
safe_yaml (~> 1.0)
|
||||
jekyll-assets (3.0.12)
|
||||
activesupport (~> 5.0)
|
||||
execjs (~> 2.7)
|
||||
extras (~> 0.2)
|
||||
fastimage (~> 2.0, >= 1.8)
|
||||
jekyll (>= 3.5, < 4.0)
|
||||
jekyll-sanity (~> 1.2)
|
||||
liquid-tag-parser (~> 1.0)
|
||||
nokogiri (~> 1.8)
|
||||
pathutil (~> 0.16)
|
||||
sprockets (>= 3.3, < 4.1.beta)
|
||||
jekyll-sanity (1.2.0)
|
||||
jekyll (~> 3.1)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-target-blank (1.1.1)
|
||||
jekyll (~> 3.0)
|
||||
nokogiri (~> 1.8.2)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jekyll_pages_api (0.1.6)
|
||||
htmlentities (~> 4.3)
|
||||
jekyll (>= 2.0, < 4.0)
|
||||
kramdown (1.17.0)
|
||||
liquid (4.0.3)
|
||||
liquid-tag-parser (1.9.0)
|
||||
extras (~> 0.3)
|
||||
liquid (>= 3.0, < 5.0)
|
||||
listen (3.2.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.11.3)
|
||||
nokogiri (1.8.5)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
progress (3.5.2)
|
||||
public_suffix (4.0.1)
|
||||
rack (2.0.7)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
ffi (~> 1.0)
|
||||
rouge (3.14.0)
|
||||
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)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (4.1.20)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
image_optim (~> 0.26.3)
|
||||
image_optim_pack (~> 0.6.0.0)
|
||||
jekyll-assets (~> 3.0)
|
||||
jekyll-seo-tag (~> 2.6.0)
|
||||
jekyll-target-blank
|
||||
jekyll_pages_api
|
||||
tzinfo-data
|
||||
uglifier (~> 4.1, >= 4.1.20)
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.2
|
205
README.md
|
@ -14,14 +14,10 @@ These instructions will get you a copy of the project up and running on your loc
|
|||
|
||||
### Prerequisites
|
||||
|
||||
- [Ruby 2.6.1](https://www.ruby-lang.org/en/downloads/), a dynamic open-source programming language
|
||||
- [Jekyll](https://jekyllrb.com) site generator for [Github Pages](https://pages.github.com)
|
||||
- [Yarn](https://yarnpkg.com/en/) Package Manager
|
||||
|
||||
Once you have Jekyll and yarn insalled you'll need to install the dependencies:
|
||||
- [Node JS](https://nodejs.org/en/). Runnning the LTS release is recommended.
|
||||
- [Yarn](https://yarnpkg.com/en/) for package management.
|
||||
|
||||
```
|
||||
bundle install
|
||||
yarn install
|
||||
```
|
||||
|
||||
|
@ -37,10 +33,9 @@ Note: Running locally will show unpublished content that uses the `published: fa
|
|||
|
||||
| Command | Description |
|
||||
| ------------------- | --------------------------------------------------------------------------------------- |
|
||||
| yarn clean | Clears the output directly and cleans the .jekyll-cache. |
|
||||
| yarn clean | Clears the output directory |
|
||||
| yarn build | Builds the site. |
|
||||
| yarn start | Starts jekyll and includes unpublished content. (Note the first run is slow!) |
|
||||
| yarn start-prodlike | Starts jekyll and doesn't include unpublished content for a production-like experience. |
|
||||
| yarn start | Starts eleventy and includes unpublished content. |
|
||||
|
||||
## Development Guide: Content Updates
|
||||
|
||||
|
@ -48,31 +43,22 @@ This site has three templates: a full-width page, a sidebar page for documentati
|
|||
|
||||
### Uploading media
|
||||
|
||||
1. Add the image files to `_assets/img/`
|
||||
1. Add the image files to `src/assets/img/`
|
||||
2. In your page, link to images using this page structure:
|
||||
|
||||
This tag will output an entire `img` element. Note: that using `@optim` enables the jekyll asset pipeline to optimize the image. This is always recommended unless you see an issue with the output.
|
||||
|
||||
```
|
||||
{% asset "image.jpg" @optim %}
|
||||
```
|
||||
You can reference images with the full path from the assets directory e.g: `/assets/img/image.png`
|
||||
|
||||
For finer control you can use:
|
||||
|
||||
```html
|
||||
<img src="{% asset "image.jpg" @path @optim %}" someattr="whatever" />
|
||||
```
|
||||
|
||||
Here's an example using markdown - note you need to add `@path` and `@optim`.
|
||||
Here's an example in `markdown`:
|
||||
|
||||
```markdown
|
||||
![Remembear subtitle screenshot]({% asset "content-guidelines/remembear-subtitle.png" @path @optim %} "Remembear subtitle text")
|
||||
![Remembear subtitle screenshot](/assets/img/remembear-subtitle.png "Remembear subtitle text")
|
||||
```
|
||||
|
||||
### How to add a "sidebar" layout page
|
||||
|
||||
1. Open `_data/pages.yaml`.
|
||||
2. Add a node with appropriate attributes, in the appropriate location, for the new page. See below for [details on how to understand the pages.yaml structure](#understanding-the-pagesyaml-structure).
|
||||
1. Open `data/pages.json`.
|
||||
2. Add a node with appropriate attributes, in the appropriate location, for the new page. See below for [details on how to understand the pages.json structure](#understanding-the-pagejson-structure).
|
||||
3. Create a new page, nested inside a folder struture that matches the url path. For example, for permalink `/documentation/develop/best-practices-for-collecting-user-data-consents/`, you would create a file called `best-practices-for-collecting-user-data-consents.md` and place it in `documentation` > `develop`.
|
||||
4. For reference on how to create a page, review the `sidebar-master-template.md` file, which lists all available modules. Some notes:
|
||||
- `published: false` will withhold this content from stage and production, to publish content, remove this line.
|
||||
|
@ -81,12 +67,9 @@ Here's an example using markdown - note you need to add `@path` and `@optim`.
|
|||
- Rull for creating section IDs: use the `h2` title of the section, converted to lowercase, spaces replaced with dashes, all non-alphanumeric characters removed. For example, the section `h2` title "Know your privacy settings" would be converted to `know-your-privacy-settings` for the section ID
|
||||
- The first section following the "Page Hero" module should be the "Table of Contents" module: `modules/column-w-toc.html`.
|
||||
|
||||
<h4 id="understanding-the-pagesyaml-structure">Understanding the Pages.yaml structure</h4>
|
||||
<h4 id="understanding-the-pagesjson-structure">Understanding the Pages.json structure</h4>
|
||||
|
||||
- Each node is started with a `-`
|
||||
- Sub nodes are indented below parent nodes
|
||||
- Each new page has a title and url attribute _**Note:** The url attribute must match exactly the permalink attribute of the page's front matter (including leading and trailing slashes)_
|
||||
- Attributes are indented on new lines
|
||||
- Pages can also have a subpageitems node for sections within the page to be referenced in the table of contents for that page:
|
||||
- Each subpageitem node has a title and ID attribute, where the ID matches the ID attribute of the section container _(IDs need to be added to a containing element, rather than the heading element, of the section. This is so that highlighting for that section stays active even when the section title is out of view)_
|
||||
- Overview pages have category nodes for each of the sub categories for that section
|
||||
|
@ -95,64 +78,106 @@ Here's an example using markdown - note you need to add `@path` and `@optim`.
|
|||
|
||||
General overview of the pages layout:
|
||||
|
||||
```
|
||||
- title: "Documentation Topics"
|
||||
subfolderitems:
|
||||
- title: "Develop"
|
||||
url: "/documentation/develop/"
|
||||
subpageitems:
|
||||
- title: "Firefox Tools"
|
||||
id: "firefox-tools"
|
||||
...
|
||||
categories:
|
||||
- category: "Getting Started"
|
||||
pages:
|
||||
- title: "Firefox Workflow Overview"
|
||||
url: "/documentation/develop/firefox-workflow-overview/"
|
||||
...
|
||||
- title: "Publish"
|
||||
url: "/documentation/publish/"
|
||||
subpageitems:
|
||||
- title: "Get your extension signed"
|
||||
id: "get-your-extension-signed"
|
||||
...
|
||||
- title: "Manage"
|
||||
url: "/documentation/manage/"
|
||||
subpageitems:
|
||||
- title: "Stay informed when Firefox changes"
|
||||
id: "stay-informed-when-firefox-changes"
|
||||
...
|
||||
- title: "Enterprise"
|
||||
url: "/documentation/enterprise/"
|
||||
subpageitems:
|
||||
- title: "Section Title"
|
||||
id: "introduction"
|
||||
...
|
||||
- title: "Themes"
|
||||
url: "/documentation/themes/"
|
||||
subpageitems:
|
||||
- title: "What themes are"
|
||||
id: "what-themes-are"
|
||||
...
|
||||
- title: "Extension Basics"
|
||||
url: "/extension-basics/"
|
||||
subpageitems:
|
||||
- title: "Getting started"
|
||||
id: "getting-started"
|
||||
...
|
||||
- title: "Community"
|
||||
url: "/community/"
|
||||
subpageitems:
|
||||
- title: "Who is part of the community?"
|
||||
id: "who-is-part-of-the-community"
|
||||
...
|
||||
categories:
|
||||
- category: "About the Community"
|
||||
pages:
|
||||
- title: ""
|
||||
url: ""
|
||||
...
|
||||
...
|
||||
```json
|
||||
[
|
||||
{
|
||||
"title": "Documentation Topics",
|
||||
"subfolderitems": [
|
||||
{
|
||||
"title": "Develop",
|
||||
"url": "/documentation/develop/",
|
||||
"subpageitems": [
|
||||
{
|
||||
"title": "Firefox Tools",
|
||||
"id": "firefox-tools"
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"category": "Getting Started",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Firefox Workflow Overview",
|
||||
"url": "/documentation/develop/firefox-workflow-overview/"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Publish",
|
||||
"url": "/documentation/publish/",
|
||||
"subpageitems": [
|
||||
{
|
||||
"title": "Get your extension signed",
|
||||
"id": "get-your-extension-signed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Manage",
|
||||
"url": "/documentation/manage/",
|
||||
"subpageitems": [
|
||||
{
|
||||
"title": "Stay informed when Firefox changes",
|
||||
"id": "stay-informed-when-firefox-changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Enterprise",
|
||||
"url": "/documentation/enterprise/",
|
||||
"subpageitems": [
|
||||
{
|
||||
"title": "Section Title",
|
||||
"id": "introduction"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Themes",
|
||||
"url": "/documentation/themes/",
|
||||
"subpageitems": [
|
||||
{
|
||||
"title": "What themes are",
|
||||
"id": "what-themes-are"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Extension Basics",
|
||||
"url": "/extension-basics/",
|
||||
"subpageitems": [
|
||||
{
|
||||
"title": "Getting started",
|
||||
"id": "getting-started"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Community",
|
||||
"url": "/community/",
|
||||
"subpageitems": [
|
||||
{
|
||||
"title": "Who is part of the community?",
|
||||
"id": "who-is-part-of-the-community"
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"category": "About the Community",
|
||||
"pages": [
|
||||
{
|
||||
"title": "",
|
||||
"url": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### How to add a "Content Guidelines" page
|
||||
|
@ -177,7 +202,7 @@ Note: `published: false` will withhold this content from stage and production, t
|
|||
|
||||
#### Add the page to the menu
|
||||
|
||||
Go to `_data/content-guidelines-pages.yaml` and add a new entry for your page:
|
||||
Go to `data/content-guidelines-pages.yaml` and add a new entry for your page:
|
||||
|
||||
```
|
||||
- title: "Page Name"
|
||||
|
@ -187,13 +212,13 @@ Go to `_data/content-guidelines-pages.yaml` and add a new entry for your page:
|
|||
|
||||
#### Controlling draft labelling
|
||||
|
||||
If you don't want the page to be labelled as a draft, as and when it's ready remove `draft-label: true` from the relevant entry in `_data/content-guidelines.yaml`
|
||||
If you don't want the page to be labelled as a draft, as and when it's ready remove `draft-label: true` from the relevant entry in `data/content-guidelines.yaml`
|
||||
|
||||
## Deployment
|
||||
|
||||
All deploys for stage and prod are handled via the [releases](https://github.com/mozilla/extension-workshop/releases) page.
|
||||
|
||||
### Dev Deploys
|
||||
### Dev Deploys
|
||||
|
||||
The site is auto-deployed on commits to master to https://extensionworkshop-dev.allizom.org/. You can check the version on -dev with [the dev version link](https://extensionworkshop-dev.allizom.org/__version__)
|
||||
|
||||
|
@ -201,7 +226,7 @@ The site is auto-deployed on commits to master to https://extensionworkshop-dev.
|
|||
|
||||
Tags with a version ending in `-stage` will be deployed to https://extensionworkshop.allizom.org/. You can check the version on stage with [the stage version link](https://extensionworkshop.allizom.org/__version__)
|
||||
|
||||
A good example of a tag for stage would be `v2.0.1-stage`.
|
||||
A good example of a tag for stage would be `v2.0.1-stage`.
|
||||
|
||||
### Production Deploys
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures.
|
||||
|
||||
To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts
|
||||
|
||||
You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects.
|
||||
|
||||
You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection.
|
|
@ -1,13 +0,0 @@
|
|||
#= require jquery.min
|
||||
#= require purify.min
|
||||
#= require velocity.min
|
||||
#= require velocity.ui.min
|
||||
#= require slick.min
|
||||
#= require tinypubsub
|
||||
#= require breakpoints
|
||||
#= require parallax
|
||||
#= require parallaxFG
|
||||
#= require inview
|
||||
#= require youtubeplayer
|
||||
#= require main
|
||||
|
86
_config.yml
|
@ -1,86 +0,0 @@
|
|||
# Welcome to Jekyll!
|
||||
#
|
||||
# This config file is meant for settings that affect your whole blog, values
|
||||
# which you are expected to set up once and rarely edit after that. If you find
|
||||
# yourself editing this file very often, consider using Jekyll's data files
|
||||
# feature for the data you need to update frequently.
|
||||
#
|
||||
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
||||
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
|
||||
|
||||
# Site settings
|
||||
# These are used to personalize your new site. If you look in the HTML files,
|
||||
# you will see them accessed via {{ site.title }}, {{ site.url }}, and so on.
|
||||
# You can create any custom variable you would like, and they will be accessible
|
||||
# in the templates via {{ site.myvariable }}.
|
||||
title: Firefox Extension Workshop
|
||||
description: >- # this means to ignore newlines until "url:"
|
||||
Get help creating & publishing Firefox extensions.
|
||||
url: "https://extensionworkshop.com" # the base hostname & protocol for your site, e.g. http://example.com
|
||||
google_analytics: UA-77033033-24
|
||||
|
||||
# If you want to link only specific pages in your header, uncomment
|
||||
# this and add the path to the pages in order as they should show up
|
||||
# header_pages:
|
||||
# - about.md
|
||||
|
||||
#defaults:
|
||||
# - scope:
|
||||
# path: "assets/img"
|
||||
# values:
|
||||
# image: true
|
||||
|
||||
# Build settings
|
||||
markdown: kramdown
|
||||
highlighter: rouge
|
||||
kramdown:
|
||||
auto_ids: false
|
||||
|
||||
# theme: minima
|
||||
plugins:
|
||||
- jekyll-target-blank
|
||||
# - jekyll-feed
|
||||
|
||||
# Exclude from processing.
|
||||
# The following items will not be processed, by default. Create a custom list
|
||||
# to override the default setting.
|
||||
exclude:
|
||||
- .jekyll-cache
|
||||
- Gemfile
|
||||
- Gemfile.lock
|
||||
- node_modules
|
||||
- package.json
|
||||
- README.md
|
||||
- renovate.json
|
||||
- rvm-installer
|
||||
- rvm-installer.asc
|
||||
- yarn-error.log
|
||||
- yarn.lock
|
||||
|
||||
assets:
|
||||
compression: true
|
||||
sources:
|
||||
- _assets/img
|
||||
- _assets/css
|
||||
- _assets/fonts
|
||||
- _assets/js
|
||||
- node_modules/foundation-sites/scss
|
||||
- node_modules/slick-carousel/slick
|
||||
- node_modules/jquery/dist
|
||||
- node_modules/dompurify/dist
|
||||
- node_modules/lunr/
|
||||
- node_modules/velocity-animate
|
||||
defaults:
|
||||
js:
|
||||
integrity: true
|
||||
css:
|
||||
integrity: false
|
||||
img:
|
||||
integrity: false
|
||||
plugins:
|
||||
img:
|
||||
optim:
|
||||
jekyll:
|
||||
verbose: false
|
||||
pngout: false
|
||||
svgo:
|
|
@ -1,13 +0,0 @@
|
|||
assets:
|
||||
compression: false
|
||||
caching:
|
||||
path: ".jekyll-cache/assets"
|
||||
type: file
|
||||
enabled: true
|
||||
defaults:
|
||||
js:
|
||||
integrity: false
|
||||
css:
|
||||
integrity: false
|
||||
img:
|
||||
integrity: false
|
|
@ -1,15 +0,0 @@
|
|||
- title: "Optimize Your Product Page"
|
||||
url: "/content-guidelines/optimize-product-page/"
|
||||
draft-label: true
|
||||
|
||||
- title: "Extension Name"
|
||||
url: "/content-guidelines/extension-name/"
|
||||
draft-label: true
|
||||
|
||||
- title: "Extension Title"
|
||||
url: "/content-guidelines/extension-title/"
|
||||
draft-label: true
|
||||
|
||||
- title: "Extension Subtitle"
|
||||
url: "/content-guidelines/extension-subtitle/"
|
||||
draft-label: true
|
|
@ -1,8 +0,0 @@
|
|||
- id: "about"
|
||||
description: "about.md"
|
||||
|
||||
- id: "build"
|
||||
description: "build.md"
|
||||
|
||||
- id: "connect"
|
||||
description: "connect.md"
|
661
_data/pages.yaml
|
@ -1,661 +0,0 @@
|
|||
- title: "Extension Basics"
|
||||
url: "/extension-basics/"
|
||||
subpageitems:
|
||||
- title: "Getting started"
|
||||
id: "getting-started"
|
||||
- title: "Mozilla Developer Network"
|
||||
id: "mozilla-developer-network"
|
||||
- title: "Documentation Topics"
|
||||
subfolderitems:
|
||||
- title: "Develop"
|
||||
url: "/documentation/develop/"
|
||||
subpageitems:
|
||||
- title: "Firefox Tools"
|
||||
id: "firefox-tools"
|
||||
- title: "User Experience"
|
||||
id: "user-experience"
|
||||
- title: "Firefox for Android"
|
||||
id: "firefox-for-android"
|
||||
- title: "Port to Firefox"
|
||||
id: "port-to-firefox"
|
||||
- title: "Test and debug"
|
||||
id: "test-and-debug"
|
||||
categories:
|
||||
- category: "Getting Started"
|
||||
pages:
|
||||
- title: "Unique Firefox Capabilities"
|
||||
url: "/documentation/develop/unique-firefox-capabilities/"
|
||||
- title: "Firefox Workflow Overview"
|
||||
url: "/documentation/develop/firefox-workflow-overview/"
|
||||
- title: "About the WebExtensions API"
|
||||
url: "/documentation/develop/about-the-webextensions-api/"
|
||||
- category: "Cross-Browser Development"
|
||||
pages:
|
||||
- title: "Browser Compatibility"
|
||||
url: "/documentation/develop/browser-compatibility/"
|
||||
subpageitems:
|
||||
- title: "Namespace"
|
||||
id: "namespace"
|
||||
- title: "Asynchronous"
|
||||
id: "asynchronous"
|
||||
- title: "API Coverage"
|
||||
id: "api-coverage"
|
||||
- title: "Manifest keys"
|
||||
id: "manifest-keys"
|
||||
- title: "More information"
|
||||
id: "more-information"
|
||||
- title: "Build cross-browser extensions"
|
||||
url: "https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension"
|
||||
- category: "Firefox Tools"
|
||||
pages:
|
||||
- title: "Build an extension in 5 minutes"
|
||||
url: "https://www.youtube.com/watch?v=Q3AQ5D2QFwc"
|
||||
- title: "Browser Extension Development Tools"
|
||||
url: "/documentation/develop/browser-extension-development-tools/"
|
||||
subpageitems:
|
||||
- title: "Boilerplating tools"
|
||||
id: "boilerplating-tools"
|
||||
- title: "Coding tools"
|
||||
id: "coding-tools"
|
||||
- title: "Testing and debugging tools"
|
||||
id: "testing-and-debugging-tools"
|
||||
- title: "Translation tools"
|
||||
id: "translation-tools"
|
||||
- title: "Tools for Firefox for Android"
|
||||
id: "tools-for-firefox-for-android"
|
||||
- title: "Choosing a Firefox version for extension development"
|
||||
url: "/documentation/develop/choosing-a-firefox-version-for-extension-development/"
|
||||
subpageitems:
|
||||
- title: "Firefox editions"
|
||||
id: "firefox-editions"
|
||||
- title: "Firefox version and their web extension development capabilities"
|
||||
id: "firefox-version-and-their-web-extension-development-capabilities"
|
||||
- title: "Getting started with web-ext"
|
||||
url: "/documentation/develop/getting-started-with-web-ext/"
|
||||
subpageitems:
|
||||
- title: "Installation"
|
||||
id: "installation-section"
|
||||
- title: "Using web-ext"
|
||||
id: "using-web-ext-section"
|
||||
- title: "See also"
|
||||
id: "see-also-section"
|
||||
- title: "web-ext command reference"
|
||||
url: "/documentation/develop/web-ext-command-reference/"
|
||||
subpageitems:
|
||||
- title: "Commands"
|
||||
id: "commands"
|
||||
- title: "Global options"
|
||||
id: "global-options"
|
||||
- title: "Setting option environment variables"
|
||||
id: "setting-option-environment-variables"
|
||||
- title: "See also"
|
||||
id: "see-also"
|
||||
- title: "Web-ext Webpack plug-in"
|
||||
url: "https://github.com/hiikezoe/web-ext-webpack-plugin/blob/master/README.md"
|
||||
- title: "Browser API Polyfill"
|
||||
url: "https://github.com/mozilla/webextension-polyfill"
|
||||
- title: "Extensions and the Add-on ID"
|
||||
url: "/documentation/develop/extensions-and-the-add-on-id/"
|
||||
subpageitems:
|
||||
- title: "Basic workflow with no add-on ID"
|
||||
id: "basic-workflow-with-no-add-on-id"
|
||||
- title: "When do you need an add-on ID?"
|
||||
id: "when-do-you-need-an-add-on-id"
|
||||
- category: "User Experience"
|
||||
pages:
|
||||
- title: "Build a secure extension"
|
||||
url: "/documentation/develop/build-a-secure-extension/"
|
||||
- title: "Request the right permissions"
|
||||
url: "/documentation/develop/request-the-right-permissions/"
|
||||
subpageitems:
|
||||
- title: "Introduction"
|
||||
id: "introduction"
|
||||
- title: "Advised permissions"
|
||||
id: "advised-permissions"
|
||||
- title: "Avoid unnecessary permissions"
|
||||
id: "avoid-unnecessary-permissions"
|
||||
- title: "Request permissions at runtime"
|
||||
id: "request-permissions-at-runtime"
|
||||
- title: "Add information about permissions to your extensions AMO page"
|
||||
id: "add-information-about-permissions-to-your-extensions-amo-page"
|
||||
- title: "Best practices for collecting user data consents"
|
||||
url: "/documentation/develop/best-practices-for-collecting-user-data-consents/"
|
||||
subpageitems:
|
||||
- title: "Know your privacy settings"
|
||||
id: "know-your-privacy-settings"
|
||||
- title: "Get prepared"
|
||||
id: "get-prepared"
|
||||
- title: "Create a privacy policy"
|
||||
id: "create-a-privacy-policy"
|
||||
- title: "Prompt after install or on first use"
|
||||
id: "prompt-after-install-or-on-first-use"
|
||||
- title: "Determine your consent flow"
|
||||
id: "determine-your-consent-flow"
|
||||
- title: "Your consent dialogs"
|
||||
id: "your-consent-dialogs"
|
||||
- title: "Build an accessible extension"
|
||||
url: "/documentation/develop/build-an-accessible-extension/"
|
||||
- title: "Onboard, upboard, offboard users"
|
||||
url: "/documentation/develop/onboard-upboard-offboard-users/"
|
||||
subpageitems:
|
||||
- title: "Onboarding"
|
||||
id: "onboarding"
|
||||
- title: "Upboarding"
|
||||
id: "upboarding"
|
||||
- title: "Offboarding"
|
||||
id: "offboarding"
|
||||
- title: "User experience best practices"
|
||||
url: "/documentation/develop/user-experience-best-practices/"
|
||||
subpageitems:
|
||||
- title: "1. Keep it focused"
|
||||
id: "keep-it-focused"
|
||||
- title: "2. Give users what they need, where they need it"
|
||||
id: "give-users-what-they-need-where-they-need-it"
|
||||
- title: "3. Keep the user informed"
|
||||
id: "keep-the-user-informed"
|
||||
- title: "4. Be Firefoxy in look and feel"
|
||||
id: "be-firefoxy-in-look-and-feel"
|
||||
- title: "5. Great onboarding experience"
|
||||
id: "great-onboarding-experience"
|
||||
- title: "6. Test, test, and then test again"
|
||||
id: "test-test-and-then-test-again"
|
||||
- title: "What’s great content and design?"
|
||||
url: "https://www.youtube.com/watch?v=a0_OsLGI0k4"
|
||||
- title: "Create an appealing listing"
|
||||
url: "/documentation/develop/create-an-appealing-listing/"
|
||||
subpageitems:
|
||||
- title: "Your add-on’s name"
|
||||
id: "your-add-ons-name"
|
||||
- title: "Create a captivating icon"
|
||||
id: "create-a-captivating-icon"
|
||||
- title: "Create a meaningful set of keywords"
|
||||
id: "create-a-meaningful-set-of-keywords"
|
||||
- title: "Make sure your summary is just long enough"
|
||||
id: "make-sure-your-summary-is-just-long-enough"
|
||||
- title: "Focus on key features in your screenshots"
|
||||
id: "focus-on-key-features-in-your-screenshots"
|
||||
- title: "The add-on description can be longer, but not too long"
|
||||
id: "the-add-on-description-can-be-longer-but-not-too-long"
|
||||
- title: "Make it local"
|
||||
id: "make-it-local"
|
||||
- title: "Make it experimental"
|
||||
id: "make-it-experimental"
|
||||
- title: "Select the right platforms and versions"
|
||||
id: "select-the-right-platforms-and-versions"
|
||||
- title: "Categorize well"
|
||||
id: "categorize-well"
|
||||
- title: "Be prepared to provide support"
|
||||
id: "be-prepared-to-provide-support"
|
||||
- title: "Set up a developer profile"
|
||||
id: "set-up-a-developer-profile"
|
||||
- title: "Use plain language in any privacy policy or license agreement"
|
||||
id: "use-plain-language-in-any-privacy-policy-or-license-agreement"
|
||||
- title: "Gently ask for a review"
|
||||
id: "gently-ask-for-a-review"
|
||||
- title: "Some other points"
|
||||
id: "some-other-points"
|
||||
- category: "Mobile"
|
||||
pages:
|
||||
- title: "GeckoView Extensions (Android library)"
|
||||
url: "https://github.com/mozilla/geckoview"
|
||||
- title: "Differences between desktop and Android extensions"
|
||||
url: "/documentation/develop/differences-between-desktop-and-android-extensions/"
|
||||
subpageitems:
|
||||
- title: "User interface"
|
||||
id: "user-interface"
|
||||
- title: "Native application interaction"
|
||||
id: "native-application-interaction"
|
||||
- title: Permissions
|
||||
id: "permissions"
|
||||
- title: "Other notes"
|
||||
id: "other-notes"
|
||||
- title: "Developing extensions for Firefox for Android"
|
||||
url: "/documentation/develop/developing-extensions-for-firefox-for-android/"
|
||||
subpageitems:
|
||||
- title: "Set up your computer and Android emulator or device"
|
||||
id: "set-up-your-computer-and-android-emulator-or-device"
|
||||
- title: "Check for Firefox for Android compatibility"
|
||||
id: "check-for-firefox-for-android-compatibility"
|
||||
- title: "Install and run your extension in Firefox for Android"
|
||||
id: "install-and-run-your-extension-in-firefox-for-android"
|
||||
- title: "Debug your extension"
|
||||
id: "debug-your-extension"
|
||||
- category: "Port Your Extension"
|
||||
pages:
|
||||
- title: "Extension compatibility test"
|
||||
url: "https://www.extensiontest.com/"
|
||||
- title: "Porting a Google Chrome Extension"
|
||||
url: "/documentation/develop/porting-a-google-chrome-extension/"
|
||||
- title: "Porting a legacy Firefox extension"
|
||||
url: "/documentation/develop/porting-a-legacy-firefox-extension/"
|
||||
subpageitems:
|
||||
- title: "Quick start"
|
||||
id: "quick-start"
|
||||
- title: "Migration paths"
|
||||
id: "migration-paths"
|
||||
- title: "Don't see the WebExtensions APIs you need?"
|
||||
id: "dont-see-the-webextensions-apis-you-need"
|
||||
- title: "Tools"
|
||||
id: "tools"
|
||||
- title: "Documentation"
|
||||
id: "documentation"
|
||||
- title: "Contact"
|
||||
id: "contact"
|
||||
- title: "Comparison with the Add-on SDK"
|
||||
url: "/documentation/develop/comparison-with-the-add-on-sdk/"
|
||||
subpageitems:
|
||||
- title: "Manifest files"
|
||||
id: "manifest-files"
|
||||
- title: "Persistent scripts"
|
||||
id: "persistent-scripts"
|
||||
- title: "Content scripts"
|
||||
id: "content-scripts"
|
||||
- title: "UI elements"
|
||||
id: "ui-elements"
|
||||
- title: "Settings"
|
||||
id: "settings"
|
||||
- title: "Internationalization"
|
||||
id: "internationalization"
|
||||
- title: "Command-line tool"
|
||||
id: "command-line-tool"
|
||||
- title: "JavaScript APIs"
|
||||
id: "javascript-apis"
|
||||
- title: "Comparison with XUL/XPCOM extensions"
|
||||
url: "/documentation/develop/comparison-with-xul-xpcom-extensions/"
|
||||
subpageitems:
|
||||
- title: "Manifest"
|
||||
id: "manifest"
|
||||
- title: "UI"
|
||||
id: "ui"
|
||||
- title: "Privileged APIs"
|
||||
id: "privileged-apis"
|
||||
- title: "Interacting with web content"
|
||||
id: "interacting-with-web-content"
|
||||
- title: "Localization"
|
||||
id: "localization"
|
||||
- title: "Settings"
|
||||
id: "settings"
|
||||
- category: "Debug and Test"
|
||||
pages:
|
||||
- title: "Debugging"
|
||||
url: "/documentation/develop/debugging/"
|
||||
subpageitems:
|
||||
- title: "Developer tools toolbox"
|
||||
id: "developer-tools-toolbox"
|
||||
- title: "Debugging background scripts"
|
||||
id: "debugging-background-scripts"
|
||||
- title: "Debugging options pages"
|
||||
id: "debugging-options-pages"
|
||||
- title: "Debugging popups"
|
||||
id: "debugging-popups"
|
||||
- title: "Debugging content scripts"
|
||||
id: "debugging-content-scripts"
|
||||
- title: "Debugging sidebars"
|
||||
id: "debugging-sidebars"
|
||||
- title: "Debugging storage"
|
||||
id: "debugging-storage"
|
||||
- title: "Debugging developer tools pages and panels"
|
||||
id: "debugging-developer-tools-pages-and-panels"
|
||||
- title: "Debug permission requests"
|
||||
id: "debug-permission-requests"
|
||||
- title: "Debugging browser restarts"
|
||||
id: "debugging-browser-restarts"
|
||||
- title: "Temporary Installation in Firefox"
|
||||
url: "/documentation/develop/temporary-installation-in-firefox/"
|
||||
subpageitems:
|
||||
- title: "Reloading a temporary extension"
|
||||
id: "reloading-a-temporary-extension"
|
||||
- title: "Using the command line"
|
||||
id: "using-the-command-line"
|
||||
- title: "Detecting temporary installation"
|
||||
id: "detecting-temporary-installation"
|
||||
- title: "Limitations"
|
||||
id: "limitations"
|
||||
- title: "Testing persistent and restart features"
|
||||
url: "/documentation/develop/testing-persistent-and-restart-features/"
|
||||
subpageitems:
|
||||
- title: "What is an add-on ID?"
|
||||
id: "what-is-an-add-on-id"
|
||||
- title: "What is a Firefox profile?"
|
||||
id: "what-is-a-firefox-profile"
|
||||
- title: "Extension behavior in Firefox"
|
||||
id: "extension-behavior-in-firefox"
|
||||
- title: "What do I do to ensure I can test my extension?"
|
||||
id: "what-do-i-do-to-ensure-i-can-test-my-extension"
|
||||
- title: "Test permission requests"
|
||||
url: "/documentation/develop/test-permission-requests/"
|
||||
subpageitems:
|
||||
- title: "Permission grant behavior during testing"
|
||||
id: "permission-grant-behavior-during-testing"
|
||||
- title: "Observe or verify install time permission requests"
|
||||
id: "observe-or-verify-install-time-permission-requests"
|
||||
- title: "Retest runtime permission grants"
|
||||
id: "retest-runtime-permission-grants"
|
||||
- title: "Publish"
|
||||
url: "/documentation/publish/"
|
||||
subpageitems:
|
||||
- title: "Get your extension signed"
|
||||
id: "get-your-extension-signed"
|
||||
- title: "Distribute your signed extension"
|
||||
id: "distribute-your-signed-extension"
|
||||
- title: "Promote your extension"
|
||||
id: "promote-your-extension"
|
||||
categories:
|
||||
- category: "Policies"
|
||||
pages:
|
||||
- title: "Add-on Policies"
|
||||
url: "/documentation/publish/add-on-policies/"
|
||||
subpageitems:
|
||||
- title: "No Surprises"
|
||||
id: "no-surprises"
|
||||
- title: "Content"
|
||||
id: "content"
|
||||
- title: "Submission Guidelines"
|
||||
id: "submission-guidelines"
|
||||
- title: "Development Practices"
|
||||
id: "development-practices"
|
||||
- title: "Data Disclosure, Collection and Management"
|
||||
id: "data-disclosure-collection-and-management"
|
||||
- title: "Security Vulnerabilities"
|
||||
id: "security-vulnerabilities"
|
||||
- title: "Monetization"
|
||||
id: "monetization"
|
||||
- title: "Compliance & Blocking"
|
||||
id: "compliance-and-blocking"
|
||||
- title: "Firefox Add-on Distribution Agreement"
|
||||
url: "/documentation/publish/firefox-add-on-distribution-agreement/"
|
||||
subpageitems:
|
||||
- title: "1. Introduction"
|
||||
id: "introduction"
|
||||
- title: "2. Accounts"
|
||||
id: "accounts"
|
||||
- title: "3. Privacy Policy"
|
||||
id: "privacy-policy"
|
||||
- title: "4. Distribution, certificates, & review process"
|
||||
id: "distribution-certificates-and-review-process"
|
||||
- title: "5. Your obligations"
|
||||
id: "your-obligations"
|
||||
- title: "6. Licenses; proprietary rights"
|
||||
id: "licenses-proprietary-rights"
|
||||
- title: "7. Content removal"
|
||||
id: "content-removal"
|
||||
- title: "8. Disclaimer of warranties"
|
||||
id: "disclaimer-of-warranties"
|
||||
- title: "9. Limitation of liability"
|
||||
id: "limitation-of-liability"
|
||||
- title: "10. Release; indemnification"
|
||||
id: "release-idemnification"
|
||||
- title: "11. General legal terms"
|
||||
id: "general-legal-terms"
|
||||
- title: "Add-ons Blocking Process"
|
||||
url: "/documentation/publish/add-ons-blocking-process/"
|
||||
subpageitems:
|
||||
- title: "Security Over Choice"
|
||||
id: "security-over-choice"
|
||||
- title: "Blocking Criteria"
|
||||
id: "blocking-criteria"
|
||||
- title: "Developer Outreach"
|
||||
id: "developer-outreach"
|
||||
- title: "Requesting a Block"
|
||||
id: "requesting-a-block"
|
||||
- title: "Blocking Other Types of Third Party Software"
|
||||
id: "blocking-other-types-of-third-party-software"
|
||||
- title: "Third Party Library Usage"
|
||||
url: "/documentation/publish/third-party-library-usage/"
|
||||
subpageitems:
|
||||
- title: "When must links for third-party libraries be provided?"
|
||||
id: "when-must-links-for-third-party-libraries-be-provided"
|
||||
- title: "How to determine the third-party library link"
|
||||
id: "how-to-determine-the-third-party-library-link"
|
||||
- title: "Communicating third-party library links to reviewers"
|
||||
id: "communicating-third-party-library-links-to-the-reviewer"
|
||||
- title: "What does review rejection mean to users?"
|
||||
url: "/documentation/publish/what-does-review-rejection-mean-to-users/"
|
||||
subpageitems:
|
||||
- title: "Review overview"
|
||||
id: "review-overview"
|
||||
- title: "Impact of review rejection"
|
||||
id: "impact-of-review-rejection"
|
||||
- title: "Blocklisting"
|
||||
id: "blocklisting"
|
||||
- category: "Sign"
|
||||
pages:
|
||||
- title: "Signing and distribution overview"
|
||||
url: "/documentation/publish/signing-and-distribution-overview/"
|
||||
subpageitems:
|
||||
- title: "Signing your add-ons"
|
||||
id: "signing-your-addons"
|
||||
- title: "Distributing your add-on"
|
||||
id: "distributing-your-addon"
|
||||
- title: "More information about AMO"
|
||||
id: "about-amo"
|
||||
- title: "Package your extension"
|
||||
url: "/documentation/publish/package-your-extension/"
|
||||
subpageitems:
|
||||
- title: "Windows"
|
||||
id: "package-windows"
|
||||
- title: "Mac OSX"
|
||||
id: "package-mac"
|
||||
- title: "Linux / Mac OSX Terminal"
|
||||
id: "package-linux"
|
||||
- category: "Distribute"
|
||||
pages:
|
||||
- title: "Distribute pre-release versions"
|
||||
url: "/documentation/publish/distribute-pre-release-versions/"
|
||||
- title: "Submitting an add-on"
|
||||
url: "/documentation/publish/submitting-an-add-on/"
|
||||
subpageitems:
|
||||
- title: "Listing on AMO"
|
||||
id: "listing-on-amo"
|
||||
- title: "Self-distribution"
|
||||
id: "self-distribution"
|
||||
- title: "Get help"
|
||||
id: "get-help"
|
||||
- title: "Source code submission"
|
||||
url: "/documentation/publish/source-code-submission/"
|
||||
subpageitems:
|
||||
- title: "Provide your extension source code"
|
||||
id: "provide-your-extension-source-code"
|
||||
- title: "Use of obfuscated code"
|
||||
id: "use-of-obfuscated-code"
|
||||
- title: "Source code checklist"
|
||||
id: "source-code-checklist"
|
||||
- title: "Add-on ownership"
|
||||
url: "/documentation/publish/add-on-ownership/"
|
||||
subpageitems:
|
||||
- title: "Transfer ownership"
|
||||
id: "transfer-ownership"
|
||||
- title: "Code disputes"
|
||||
id: "code-disputes"
|
||||
- title: "Developer accounts"
|
||||
url: "/documentation/publish/developer-accounts/"
|
||||
subpageitems:
|
||||
- title: "Setting a display name"
|
||||
id: "setting-a-display-name"
|
||||
- title: "Blocked accounts"
|
||||
id: "blocked-accounts"
|
||||
- title: "Self-Distribution"
|
||||
url: "/documentation/publish/self-distribution/"
|
||||
subpageitems:
|
||||
- title: "Self-distribution options"
|
||||
id: "options"
|
||||
- title: "Sideloading"
|
||||
url: "/documentation/publish/distribute-sideloading/"
|
||||
subpageitems:
|
||||
- title: "Preparing your add-on"
|
||||
id: "preparing-your-addon"
|
||||
- title: "Install add-on from file"
|
||||
id: "install-addon-from-file"
|
||||
- title: "Installation using the standard extension folders"
|
||||
id: "standard-extension-folders"
|
||||
- title: "For desktop apps"
|
||||
url: "/documentation/publish/distribute-for-desktop-apps/"
|
||||
- category: "Promote"
|
||||
pages:
|
||||
- title: "Promoting your extension"
|
||||
url: "/documentation/publish/promoting-your-extension/"
|
||||
subpageitems:
|
||||
- title: "Promote your add-on from your website"
|
||||
id: "promote-your-addon"
|
||||
- title: "Friends, family, and colleagues"
|
||||
id: "friends-family-colleagues"
|
||||
- title: "Events and meetups"
|
||||
id: "events-and-meetups"
|
||||
- title: "Current users"
|
||||
id: "current-users"
|
||||
- title: "Social media"
|
||||
id: "social-media"
|
||||
- title: "Engage with your users"
|
||||
id: "engage-with-users"
|
||||
- title: "Create a forum, user group, or similar"
|
||||
id: "create-a-forum"
|
||||
- title: "Engage with bloggers and news media"
|
||||
id: "engage-with-media"
|
||||
- title: "Advertising"
|
||||
id: "advertising"
|
||||
- title: "Make money from browser extensions"
|
||||
url: "/documentation/publish/make-money-from-browser-extensions/"
|
||||
subpageitems:
|
||||
- title: "Will I ever be able to sell through AMO?"
|
||||
id: "sell-through-AMO"
|
||||
- title: "What can't you do"
|
||||
id: "what-cant-you-do"
|
||||
- title: "What can you do"
|
||||
id: "what-can-you-do"
|
||||
- title: "Unsolicited offers"
|
||||
id: "unsolicited-offers"
|
||||
- title: "How can I maximize my income?"
|
||||
id: "maximize-income"
|
||||
- title: "Recommended extensions"
|
||||
url: "/documentation/publish/recommended-extensions/"
|
||||
subpageitems:
|
||||
- title: "Overview"
|
||||
id: "overview"
|
||||
- title: "Criteria for Recommended extensions"
|
||||
id: "criteria"
|
||||
- title: "Developer partnership"
|
||||
id: "partnership"
|
||||
- title: "Selection process"
|
||||
id: "selection"
|
||||
- title: "Manage"
|
||||
url: "/documentation/manage/"
|
||||
subpageitems:
|
||||
- title: "Stay informed when Firefox changes"
|
||||
id: "stay-informed-when-firefox-changes"
|
||||
- title: "Publish extension updates"
|
||||
id: "publish-extension-updates"
|
||||
- title: "Manage authors of your extension"
|
||||
id: "manage-authors-of-your-extension"
|
||||
- title: "Promote your extension"
|
||||
id: "promote-your-extension"
|
||||
- title: "Removing your extension from distribution"
|
||||
id: "removing-your-extension-from-distribution"
|
||||
categories:
|
||||
- category: "Resources"
|
||||
pages:
|
||||
- title: "Updating your extension"
|
||||
url: "/documentation/manage/updating-your-extension/"
|
||||
subpageitems:
|
||||
- title: "Enabling updates to your extension"
|
||||
id: "enable-update"
|
||||
- title: "Manifest structure"
|
||||
id: "manifest-structure"
|
||||
- title: "Testing automatic updating"
|
||||
id: "testing-automatic-updating"
|
||||
- title: "Best practices for updating your extension"
|
||||
url: "/documentation/manage/best-practices-for-updating/"
|
||||
- title: "Resources for publishers"
|
||||
url: "/documentation/manage/resources-for-publishers/"
|
||||
- title: "Retiring your extension"
|
||||
url: "/documentation/manage/retiring-your-extension/"
|
||||
subpageitems:
|
||||
- title: "Reasons for withdrawing your extension"
|
||||
id: "reasons-for-withdrawing"
|
||||
- title: "Steps to retiring an extension"
|
||||
id: "steps-to-retiring-an-extension"
|
||||
- title: "Suggested retirement timetable"
|
||||
id: "suggested-retirement-timetable"
|
||||
- title: "Enterprise"
|
||||
url: "/documentation/enterprise/"
|
||||
subpageitems:
|
||||
- title: "Developing your enterprise extension"
|
||||
id: "developing-your-enterprise-extension"
|
||||
- title: "Distributing your enterprise extension"
|
||||
id: "distributing-your-enterprise-extension"
|
||||
categories:
|
||||
- category: "Enterprise support"
|
||||
pages:
|
||||
- title: "Manage add-ons for Firefox for Enterprise"
|
||||
url: "https://support.mozilla.org/products/firefox-enterprise/policies-customization-enterprise/manage-add-ons-enterprise"
|
||||
- title: "Install system add-ons for Firefox for Enterprise"
|
||||
url: "https://support.mozilla.org/kb/install-system-add-ons-firefox-enterprise"
|
||||
- category: "Enterprise resources"
|
||||
pages:
|
||||
- title: "Enterprise policies that impact extensions"
|
||||
url: "/documentation/enterprise/enterprise-policies-that-impact-extensions/"
|
||||
subpageitems:
|
||||
- title: "Relevant policies"
|
||||
id: "relevant-policies"
|
||||
- title: "Other relevant policies"
|
||||
id: "other-relevant-policies"
|
||||
- title: "Enterprise distribution"
|
||||
url: "/documentation/enterprise/enterprise-distribution/"
|
||||
subpageitems:
|
||||
- title: "Signed vs. unsigned extensions"
|
||||
id: "signed-vs-unsigned"
|
||||
- title: "Sideloading"
|
||||
id: "sideloading"
|
||||
- title: "Installation using the Windows registry"
|
||||
id: "installation-using-windows-registry"
|
||||
- title: "Firefox settings"
|
||||
id: "firefox-settings"
|
||||
- title: "Bundling add-ons with a custom Firefox"
|
||||
id: "bundling-add-ons-with-custom-Firefox"
|
||||
- title: "Themes"
|
||||
url: "/documentation/themes/"
|
||||
categories:
|
||||
- category: "Creating themes"
|
||||
pages:
|
||||
- title: "Theme concepts"
|
||||
url: "https://developer.mozilla.org/docs/Mozilla/Add-ons/Themes/Theme_concepts"
|
||||
- title: "Using the AMO theme generator"
|
||||
url: "/documentation/themes/using-the-amo-theme-generator/"
|
||||
subpageitems:
|
||||
- title: "Getting Started"
|
||||
id: "getting-started"
|
||||
- title: "Submitting your theme"
|
||||
id: "submitting-your-theme"
|
||||
- title: "Community"
|
||||
url: "/community/"
|
||||
subpageitems:
|
||||
- title: "Who is part of the community?"
|
||||
id: "who-is-part-of-the-community"
|
||||
- title: "Connect with the community"
|
||||
id: "connect-with-the-community"
|
||||
- title: "Get involved in the community"
|
||||
id: "get-involved-in-the-community"
|
||||
categories:
|
||||
- category: "Get in touch"
|
||||
pages:
|
||||
- title: "Community Forum"
|
||||
url: "https://discourse.mozilla.org/c/add-ons"
|
||||
- title: "Add-ons Blog"
|
||||
url: "https://blog.mozilla.org/addons/"
|
||||
- title: "Stack Overflow"
|
||||
url: "https://stackoverflow.com/tags/firefox-addon"
|
||||
- title: "Communication Calendar"
|
||||
url: "https://calendar.google.com/calendar?cid=bW96aWxsYS5jb21fb2ZqbGN0MDdrMTc4NHYxdTUxYnFrNDc2YmtAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ"
|
||||
- title: "Dev Mailing List"
|
||||
url: "https://mail.mozilla.org/listinfo/dev-addons"
|
||||
- category: "Contribute"
|
||||
pages:
|
||||
- title: "Contribution opportunities"
|
||||
url: "https://wiki.mozilla.org/Add-ons/Contribute"
|
||||
- title: "Onboard to the WebExtensions codebase"
|
||||
url: "https://wiki.mozilla.org/WebExtensions/Contribution_Onramp"
|
||||
- title: "Hacking guide for WebExtensions code contributions"
|
||||
url: "https://wiki.mozilla.org/WebExtensions/Hacking"
|
||||
- title: "WebExtensions Experiments"
|
||||
url: "https://webextensions-experiments.readthedocs.io/"
|
||||
- title: "Find or create a bug"
|
||||
url: "https://bugzilla.mozilla.org"
|
|
@ -1,15 +0,0 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{%- seo -%}
|
||||
<link rel="apple-touch-icon" href="{% asset "favicon.png" @path @optim %}">
|
||||
<link rel="shortcut icon" href="{% asset "favicon.ico" @path @optim %}">
|
||||
<link rel="stylesheet" href="{% asset "glance.scss" @path %}">
|
||||
{% comment %}
|
||||
{%- feed_meta -%}
|
||||
{% endcomment %}
|
||||
{%- if jekyll.environment == 'production' and site.google_analytics -%}
|
||||
{%- include google-analytics.html -%}
|
||||
{%- endif -%}
|
||||
</head>
|
|
@ -1,6 +0,0 @@
|
|||
<dt><button aria-controls="{{ include.id }}" aria-expanded="true">{{ include.title }}</button></dt>
|
||||
<dd aria-hidden="false" id="{{ include.id }}" markdown="1">
|
||||
|
||||
{{ include.content }}
|
||||
|
||||
</dd>
|
|
@ -1,3 +0,0 @@
|
|||
<div class="cell {{ include.fit }}">
|
||||
<p><img src="{% asset "{{ include.image }}" @path @optim %}" alt="{{ include.alt }}" width="{{ include.width }}"></p>
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
<div class="image-with-caption" markdown="1">
|
||||
<img src="{% asset "{{ include.source }}" @optim @path %}" alt="{{ include.alt }}" title="{{ include.caption }}"> {{ include.caption }}
|
||||
</div>
|
|
@ -1 +0,0 @@
|
|||
<p class="note{% if include.alert == true %} alert{% endif %}" markdown="1">{{ include.content }}</p>
|
|
@ -1,21 +0,0 @@
|
|||
<section class="overview-hero" style="background-image: url({% asset "{{ include.background }}" @optim @path %});">
|
||||
<div class="module">
|
||||
<article class="module-content grid-x grid-padding-x">
|
||||
<div class="cell small-12">
|
||||
<div class="overview-hero-description" markdown="1">
|
||||
|
||||
{{ include.content }}
|
||||
|
||||
</div>
|
||||
<div class="page-hero-cta">
|
||||
{% if include.cta1_label != nil and include.cta1_url != nil and include.cta1_label != empty and include.cta1_url != empty %}
|
||||
<a href="{{ include.cta1_url }}" class="button">{{ include.cta1_label }}</a>
|
||||
{% endif %}
|
||||
{% if include.cta2_label != nil and include.cta2_url != nil and include.cta2_label != empty and include.cta2_url != empty %}
|
||||
<a href="{{ include.cta2_url }}" class="button secondary">{{ include.cta2_label }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
|
@ -1,22 +0,0 @@
|
|||
<section class="page-hero">
|
||||
<div class="module">
|
||||
<article class="module-content grid-x grid-padding-x">
|
||||
<div class="cell small-12">
|
||||
<div class="page-hero-description" markdown="1">
|
||||
<p class="section-title"><small>{{ page.topic }}</small></p>
|
||||
|
||||
{{ include.content }}
|
||||
|
||||
</div>
|
||||
<div class="page-hero-cta">
|
||||
{% if include.cta1_label != nil and include.cta1_url != nil and include.cta1_label != empty and include.cta1_url != empty %}
|
||||
<a href="{{ include.cta1_url }}" class="button">{{ include.cta1_label }}</a>
|
||||
{% endif %}
|
||||
{% if include.cta2_label != nil and include.cta2_url != nil and include.cta2_label != empty and include.cta2_url != empty %}
|
||||
<a href="{{ include.cta2_url }}" class="button secondary">{{ include.cta2_label }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
|
@ -1,3 +0,0 @@
|
|||
<div class="video-popup">
|
||||
<p><a href="https://youtu.be/{{ include.youtube_id }}" data-youtube_id="{{ include.youtube_id }}" title="{{ include.title }}"><img src="{% asset "{{ include.image }}" @optim @path %}" alt="{{ include.alt }}"></a></p>
|
||||
</div>
|
|
@ -1,36 +0,0 @@
|
|||
<section class="module meta-data">
|
||||
<article class="module-content grid-x grid-padding-x">
|
||||
<div class="cell small-12">
|
||||
|
||||
{%- if page.tags and page.tags.size > 0 -%}
|
||||
<p><span class="meta-label tags">Tags:</span>
|
||||
{% for tag in page.tags %}
|
||||
{% capture tag_name %}{{ tag }}{% endcapture %}
|
||||
<a href="/tags/?q={{ tag_name | url_encode }}">{{ tag_name }}</a>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if page.contributors and page.contributors.size > 0 -%}
|
||||
<p><span class="meta-label contributors">Contributors:</span>
|
||||
{% for contributor in page.contributors %}
|
||||
<a href="https://github.com/{{ contributor }}/" title="View {{ contributor }} on Github">{{ contributor }}</a>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if page.date -%}
|
||||
<p><span class="meta-label date">Last update:</span>
|
||||
{%- if page.last_updated_by -%}
|
||||
<a href="https://github.com/{{ page.last_updated_by }}/" title="View {{ page.last_updated_by }} on Github" itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="p-author h-card" itemprop="name">{{ page.last_updated_by }}</span></a>
|
||||
{%- endif -%}
|
||||
<time class="dt-published" datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">
|
||||
{%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%}
|
||||
{{ page.date | date: date_format }}
|
||||
</time>
|
||||
</p>
|
||||
{%- endif -%}
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
|
@ -1,10 +0,0 @@
|
|||
<!-- Tile -->
|
||||
<a href="{{ next_page.permalink }}" class="cell auto tile tile-block-link">
|
||||
<div class="block-link">
|
||||
{% if next_page.topic %}
|
||||
<p>{{ next_page.topic }}</p>
|
||||
{% endif %}
|
||||
<h5>{{ next_page.title }}</h5>
|
||||
</div>
|
||||
</a>
|
||||
<!-- END: Tile -->
|
|
@ -1,162 +0,0 @@
|
|||
{% assign on_current_page = false %}
|
||||
{% assign max_steps = 3 %}
|
||||
|
||||
<section class="module up-next">
|
||||
<article class="module-content grid-x grid-padding-x">
|
||||
<div class="cell small-12">
|
||||
<h6>Up Next</h6>
|
||||
</div>
|
||||
|
||||
|
||||
{% for item in site.data.pages %}
|
||||
|
||||
{% if item.categories %}
|
||||
|
||||
{% if on_current_page == true %}
|
||||
|
||||
{% assign next_page_ar = site.pages | where: 'permalink', item.url %}
|
||||
{% for next_page in next_page_ar %}
|
||||
{% if is_next_range < max_steps && next_page.skip_index != false %}
|
||||
|
||||
{%- include up-next-tile.html -%}
|
||||
|
||||
{% endif %}
|
||||
{% assign is_next_range = is_next_range | plus: 1 %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if page.url == item.url %}
|
||||
{% assign on_current_page = true %}
|
||||
{% assign is_next_range = 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% for cat in item.categories %}
|
||||
|
||||
|
||||
{% if cat.pages %}
|
||||
|
||||
{% for pg in cat.pages %}
|
||||
|
||||
{% if on_current_page == true %}
|
||||
|
||||
{% assign next_page_ar = site.pages | where: 'permalink', pg.url %}
|
||||
{% for next_page in next_page_ar %}
|
||||
{% if is_next_range < max_steps && next_page.skip_index != false %}
|
||||
|
||||
{%- include up-next-tile.html -%}
|
||||
|
||||
{% endif %}
|
||||
{% assign is_next_range = is_next_range | plus: 1 %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if page.url == pg.url %}
|
||||
{% assign on_current_page = true %}
|
||||
{% assign is_next_range = 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% elsif item.subfolderitems %}
|
||||
|
||||
{% for subitem in item.subfolderitems %}
|
||||
|
||||
{% if subitem.categories %}
|
||||
|
||||
{% if on_current_page == true %}
|
||||
|
||||
{% assign next_page_ar = site.pages | where: 'permalink', subitem.url %}
|
||||
{% for next_page in next_page_ar %}
|
||||
{% if is_next_range < max_steps && next_page.skip_index != false %}
|
||||
|
||||
{%- include up-next-tile.html -%}
|
||||
|
||||
{% endif %}
|
||||
{% assign is_next_range = is_next_range | plus: 1 %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if page.url == subitem.url %}
|
||||
{% assign on_current_page = true %}
|
||||
{% assign is_next_range = 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% for cat in subitem.categories %}
|
||||
|
||||
{% if cat.pages %}
|
||||
{% for pg in cat.pages %}
|
||||
|
||||
{% if on_current_page == true %}
|
||||
|
||||
{% assign next_page_ar = site.pages | where: 'permalink', pg.url %}
|
||||
{% for next_page in next_page_ar %}
|
||||
{% if is_next_range < max_steps && next_page.skip_index != false %}
|
||||
|
||||
{%- include up-next-tile.html -%}
|
||||
|
||||
{% endif %}
|
||||
{% assign is_next_range = is_next_range | plus: 1 %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if page.url == pg.url %}
|
||||
{% assign on_current_page = true %}
|
||||
{% assign is_next_range = 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% else %}
|
||||
|
||||
{% if on_current_page == true %}
|
||||
|
||||
{% assign next_page_ar = site.pages | where: 'permalink', subitem.url %}
|
||||
{% for next_page in next_page_ar %}
|
||||
{% if is_next_range < max_steps && next_page.skip_index != false %}
|
||||
|
||||
{%- include up-next-tile.html -%}
|
||||
|
||||
{% endif %}
|
||||
{% assign is_next_range = is_next_range | plus: 1 %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if page.url == subitem.url %}
|
||||
{% assign on_current_page = true %}
|
||||
{% assign is_next_range = 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% if on_current_page == true %}
|
||||
|
||||
{% assign next_page_ar = site.pages | where: 'permalink', item.url %}
|
||||
{% for next_page in next_page_ar %}
|
||||
{% if is_next_range < max_steps && next_page.skip_index != false %}
|
||||
|
||||
{%- include up-next-tile.html -%}
|
||||
|
||||
{% endif %}
|
||||
{% assign is_next_range = is_next_range | plus: 1 %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if page.url == item.url %}
|
||||
{% assign on_current_page = true %}
|
||||
{% assign is_next_range = 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</article>
|
||||
</section>
|
|
@ -1,18 +0,0 @@
|
|||
---
|
||||
layout: full-width
|
||||
---
|
||||
|
||||
<div class="tag-results panel">
|
||||
<div class="grid-container grid-x grid-padding-x">
|
||||
<div class="cell small-12">
|
||||
|
||||
<div id="tag-list" data-message="results for tag: "></div>
|
||||
|
||||
<aside class="popular-searches">
|
||||
{{ content }}
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% asset "lunr.min.js" !type %}
|
|
@ -0,0 +1,369 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const chalk = require('chalk');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const postcss = require('postcss');
|
||||
const postcssURL = require('postcss-url');
|
||||
const posthtml = require('posthtml');
|
||||
const posthtmlBeautify = require('posthtml-beautify');
|
||||
const posthtmlURL = require('posthtml-urls');
|
||||
const posthtmlCSS = require('posthtml-postcss');
|
||||
const Terser = require('terser');
|
||||
const Url = require('url-parse');
|
||||
|
||||
const assetsDirPrefix = '/assets';
|
||||
// The dir that Eleventy builds to.
|
||||
const builtAssetDir = path.resolve(__dirname, '../build/');
|
||||
// Where we're writing to.
|
||||
const destAssetDir = path.resolve(__dirname, '../dist/');
|
||||
|
||||
// Binary assets are handled first
|
||||
const binaryAssetsRX = /\.(?:ico|jpe?g|png|tiff|webp|eot|gif|otf|ttf|woff2?)$/;
|
||||
|
||||
// Text-based assets consume other assets so they need to be revved after the images.
|
||||
const textAssetsRX = /\.(?:js|css|svg)$/;
|
||||
|
||||
// Assets that match these endings should not be hashed.
|
||||
const unHashableAssetsRX = /(?:robots\.txt|\.html|pages\.json)$/;
|
||||
|
||||
function getPathRelativeToCWD(assetPath) {
|
||||
return path.relative(process.cwd(), assetPath);
|
||||
}
|
||||
|
||||
// The asset map is the main data structure that holds the paths.
|
||||
async function getFileHash(path) {
|
||||
const shasum = crypto.createHash('sha256');
|
||||
const content = await fs.readFile(path, 'utf8');
|
||||
shasum.update(content);
|
||||
return shasum.digest('hex');
|
||||
}
|
||||
|
||||
function getContentHash(content) {
|
||||
const shasum = crypto.createHash('sha256');
|
||||
shasum.update(content);
|
||||
return shasum.digest('hex');
|
||||
}
|
||||
|
||||
function getHashedPath(keyPath, hash) {
|
||||
const ext = path.extname(keyPath);
|
||||
const shortHash = hash.substring(0, 8);
|
||||
return path.join(
|
||||
path.dirname(keyPath),
|
||||
`${path.basename(keyPath, ext)}.${shortHash}${ext}`
|
||||
);
|
||||
}
|
||||
|
||||
async function recursiveListDir(directoryName, mapObject) {
|
||||
let files = await fs.readdir(directoryName, { withFileTypes: true });
|
||||
for (let file of files) {
|
||||
let fullPath = path.join(directoryName, file.name);
|
||||
if (file.isDirectory()) {
|
||||
await recursiveListDir(fullPath, mapObject);
|
||||
} else {
|
||||
const filePath = path.relative(builtAssetDir, fullPath);
|
||||
if (!mapObject[filePath]) {
|
||||
mapObject[filePath] = { hashedPath: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapObject;
|
||||
}
|
||||
|
||||
function updateKeys(obj, key, fileHash, written) {
|
||||
const shortHash = fileHash.substring(0, 8);
|
||||
obj[key].hash = fileHash;
|
||||
obj[key].shortHash = shortHash;
|
||||
obj[key].hashedPath = getHashedPath(key, fileHash);
|
||||
// Store a bool as to whether this file has been already written out to the target dir.
|
||||
obj[key].written = !!written;
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
function spliceString(str, begin, end, replacement) {
|
||||
return str.substr(0, begin) + replacement + str.substr(end);
|
||||
}
|
||||
|
||||
async function updateHashMap(mapObject, rx, asyncFunc) {
|
||||
await Promise.all(
|
||||
Object.keys(mapObject).map(async (item, idx) => {
|
||||
if (item.match(rx)) {
|
||||
const origPath = path.join(builtAssetDir, item);
|
||||
if (asyncFunc) {
|
||||
await asyncFunc(origPath, item, mapObject);
|
||||
} else {
|
||||
const fileHash = await getFileHash(origPath);
|
||||
updateKeys(mapObject, item, fileHash);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
return mapObject;
|
||||
}
|
||||
|
||||
function trimLeadingSlash(str) {
|
||||
return str.replace(/^\/{1}/, '');
|
||||
}
|
||||
|
||||
function hasHashedReplacementURL(inputPath, assetMap) {
|
||||
const url = new Url(trimLeadingSlash(inputPath));
|
||||
if (
|
||||
assetMap.hasOwnProperty(url.pathname) &&
|
||||
assetMap[url.pathname].hashedPath &&
|
||||
!url.pathname.match(unHashableAssetsRX)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getReplacementURL(inputPath, assetMap) {
|
||||
const origPath = trimLeadingSlash(inputPath);
|
||||
const url = new Url(origPath);
|
||||
if (hasHashedReplacementURL(url.pathname, assetMap)) {
|
||||
return `/${assetMap[url.pathname].hashedPath}${url.query}${url.hash}`;
|
||||
}
|
||||
return `/${inputPath}`;
|
||||
}
|
||||
|
||||
async function rewriteBinaryFiles(origPath, key, assetMap) {
|
||||
const fileHash = await getFileHash(origPath);
|
||||
const hashedPath = getHashedPath(key, fileHash);
|
||||
const outputFile = path.join(destAssetDir, hashedPath);
|
||||
|
||||
try {
|
||||
await fs.ensureFile(outputFile);
|
||||
await fs.copy(origPath, outputFile, { replace: false });
|
||||
console.log(`Writing Binary file to ${getPathRelativeToCWD(outputFile)}`);
|
||||
updateKeys(assetMap, key, fileHash, true);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error writing binary file: ${error}`));
|
||||
}
|
||||
}
|
||||
|
||||
async function rewriteJS(origPath, key, assetMap) {
|
||||
let code = await fs.readFile(origPath, 'utf8');
|
||||
const ast = Terser.parse(code);
|
||||
const pathNodes = [];
|
||||
ast.walk(
|
||||
new Terser.TreeWalker(function (node) {
|
||||
if (node instanceof Terser.AST_String) {
|
||||
if (hasHashedReplacementURL(node.getValue(), assetMap)) {
|
||||
pathNodes.push(node);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
// now go through the nodes backwards and replace code
|
||||
const replacementsCount = pathNodes.length;
|
||||
if (replacementsCount) {
|
||||
console.log(
|
||||
`Re-writing ${replacementsCount} ${
|
||||
replacementsCount == 1 ? 'path' : 'paths'
|
||||
} in ${getPathRelativeToCWD(origPath)}`
|
||||
);
|
||||
}
|
||||
for (var i = pathNodes.length; --i >= 0; ) {
|
||||
const node = pathNodes[i];
|
||||
const start_pos = node.start.pos;
|
||||
const end_pos = node.end.endpos;
|
||||
const replacement = new Terser.AST_String({
|
||||
value: getReplacementURL(node.getValue(), assetMap),
|
||||
}).print_to_string({ beautify: true });
|
||||
code = spliceString(code, start_pos, end_pos, replacement);
|
||||
}
|
||||
|
||||
const minified = Terser.minify(code);
|
||||
|
||||
if (minified.error) {
|
||||
throw new Error(minified.error);
|
||||
}
|
||||
|
||||
const hash = getContentHash(minified.code);
|
||||
const hashedPath = getHashedPath(key, hash);
|
||||
const outputFile = path.join(destAssetDir, hashedPath);
|
||||
|
||||
try {
|
||||
await fs.ensureFile(outputFile);
|
||||
await fs.writeFile(outputFile, minified.code);
|
||||
console.log(`Writing minified JS to ${getPathRelativeToCWD(outputFile)}`);
|
||||
updateKeys(assetMap, key, hash, true);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error writing generated JS: ${error}`));
|
||||
}
|
||||
}
|
||||
|
||||
async function rewriteHTML(origPath, key, assetMap) {
|
||||
let text = await fs.readFile(origPath, 'utf8');
|
||||
let replacementsCount = 0;
|
||||
|
||||
const postcssOptions = { from: origPath };
|
||||
const postcssfilterType = /^text\/css$/;
|
||||
|
||||
const output = await posthtml()
|
||||
.use(
|
||||
posthtmlCSS(
|
||||
[
|
||||
postcssURL({
|
||||
url: (asset) => {
|
||||
if (hasHashedReplacementURL(asset.url, assetMap)) {
|
||||
replacementsCount++;
|
||||
return getReplacementURL(asset.url, assetMap);
|
||||
} else {
|
||||
return asset.url;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
postcssOptions,
|
||||
postcssfilterType
|
||||
)
|
||||
)
|
||||
.use(
|
||||
posthtmlURL({
|
||||
eachURL: (url, attr, element) => {
|
||||
if (hasHashedReplacementURL(url, assetMap)) {
|
||||
replacementsCount++;
|
||||
return getReplacementURL(url, assetMap);
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
.use(
|
||||
posthtmlBeautify({
|
||||
rules: {
|
||||
indent: 2,
|
||||
blankLines: false,
|
||||
},
|
||||
})
|
||||
)
|
||||
.process(text);
|
||||
|
||||
const hash = getContentHash(output.html);
|
||||
|
||||
if (replacementsCount) {
|
||||
console.log(
|
||||
`Re-writing ${replacementsCount} ${
|
||||
replacementsCount == 1 ? 'path' : 'paths'
|
||||
} in ${getPathRelativeToCWD(origPath)}`
|
||||
);
|
||||
}
|
||||
|
||||
let outputFile;
|
||||
const ext = path.extname(origPath);
|
||||
if (ext !== '.html') {
|
||||
outputFile = path.join(destAssetDir, getHashedPath(key, hash));
|
||||
} else {
|
||||
outputFile = path.join(
|
||||
destAssetDir,
|
||||
path.relative(builtAssetDir, origPath)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.ensureFile(outputFile);
|
||||
await fs.writeFile(outputFile, output.html);
|
||||
const fileType = ext.replace(/^\./, '');
|
||||
|
||||
console.log(`Writing ${fileType} to ${getPathRelativeToCWD(outputFile)}`);
|
||||
updateKeys(assetMap, key, hash, true);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error writing generated ${fileType}: ${error}`));
|
||||
}
|
||||
}
|
||||
|
||||
async function rewriteCSS(origPath, key, assetMap) {
|
||||
let css = await fs.readFile(origPath, 'utf8');
|
||||
let replacementsCount = 0;
|
||||
const output = postcss()
|
||||
.use(
|
||||
postcssURL({
|
||||
url: (asset) => {
|
||||
if (hasHashedReplacementURL(asset.url, assetMap)) {
|
||||
replacementsCount++;
|
||||
return getReplacementURL(asset.url, assetMap);
|
||||
} else {
|
||||
return asset.url;
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
.process(css, { from: origPath });
|
||||
|
||||
const hash = getContentHash(output.css);
|
||||
|
||||
if (replacementsCount) {
|
||||
console.log(
|
||||
`Re-writing ${replacementsCount} ${
|
||||
replacementsCount == 1 ? 'path' : 'paths'
|
||||
} in ${getPathRelativeToCWD(origPath)}`
|
||||
);
|
||||
}
|
||||
|
||||
const hashedPath = getHashedPath(key, hash);
|
||||
const outputFile = path.join(destAssetDir, hashedPath);
|
||||
|
||||
try {
|
||||
await fs.ensureFile(outputFile);
|
||||
await fs.writeFile(outputFile, output.css);
|
||||
console.log(`Writing CSS to ${getPathRelativeToCWD(outputFile)}`);
|
||||
updateKeys(assetMap, key, hash, true);
|
||||
} catch (error) {
|
||||
console.error(`Error writing generated CSS: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function copyFiles(assetMap) {
|
||||
await Promise.all(
|
||||
Object.keys(assetMap).map(async (pathKey, idx) => {
|
||||
const asset = assetMap[pathKey];
|
||||
if (asset && !asset.written) {
|
||||
const outputPath = asset.hashedPath ? asset.hashedPath : pathKey;
|
||||
const outputFile = path.join(destAssetDir, outputPath);
|
||||
const origFile = path.join(builtAssetDir, pathKey);
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`Copying file from ${getPathRelativeToCWD(
|
||||
origFile
|
||||
)} to ${getPathRelativeToCWD(outputFile)}`
|
||||
);
|
||||
await fs.copy(origFile, outputFile, { replace: false });
|
||||
asset.written = true;
|
||||
} catch (error) {
|
||||
console.error(`Error copying file: ${error}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function cacheBustAssets() {
|
||||
// Get all file paths in the map from the build dir first.
|
||||
const assetMap = await recursiveListDir(builtAssetDir, {});
|
||||
|
||||
// Update the assetMap with hashes for binary files.
|
||||
await updateHashMap(assetMap, binaryAssetsRX, rewriteBinaryFiles);
|
||||
|
||||
// Process SVG
|
||||
await updateHashMap(assetMap, /\.svg$/, rewriteHTML);
|
||||
|
||||
// Process JS files
|
||||
await updateHashMap(assetMap, /\.js$/, rewriteJS);
|
||||
|
||||
// Process CSS
|
||||
await updateHashMap(assetMap, /\.css$/, rewriteCSS);
|
||||
|
||||
// Process HTML
|
||||
await updateHashMap(assetMap, /\.html$/, rewriteHTML);
|
||||
|
||||
// Finally copy everything to a new directory, if the original path has a hashed entry write it to that path.
|
||||
await copyFiles(assetMap);
|
||||
}
|
||||
|
||||
cacheBustAssets().then(() => {
|
||||
console.log(chalk.green('TA-DA! 🥁'));
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
* This script concatenates all the JS files listed below.
|
||||
* Minification is run for production builds separately
|
||||
*
|
||||
*/
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const fs = require('fs-extra');
|
||||
const isProduction = process.env.ELEVENTY_ENV;
|
||||
|
||||
const inputFiles = [
|
||||
'node_modules/jquery/dist/jquery.js',
|
||||
'node_modules/dompurify/dist/purify.js',
|
||||
'node_modules/velocity-animate/velocity.js',
|
||||
'node_modules/velocity-ui-pack/velocity.ui.js',
|
||||
'node_modules/slick-carousel/slick/slick.js',
|
||||
'src/assets/js/tinypubsub.js',
|
||||
'src/assets/js/breakpoints.js',
|
||||
'src/assets/js/parallax.js',
|
||||
'src/assets/js/parallaxFG.js',
|
||||
'src/assets/js/inview.js',
|
||||
'src/assets/js/youtubeplayer.js',
|
||||
'src/assets/js/main.js',
|
||||
];
|
||||
|
||||
const outputFile = './build/assets/js/bundle.js';
|
||||
|
||||
const buildJS = async function () {
|
||||
const data = [];
|
||||
for (const file of inputFiles) {
|
||||
const content = await fs.readFile(file, 'utf8');
|
||||
data.push(content);
|
||||
}
|
||||
|
||||
// This is a hint to know if this is a first run, in which case
|
||||
// we don't need to tell browserSync to update.
|
||||
const fileExisted = await fs.pathExists(outputFile);
|
||||
|
||||
try {
|
||||
await fs.ensureFile(outputFile);
|
||||
await fs.writeFile(outputFile, data.join('\n'));
|
||||
} catch (error) {
|
||||
console.error(`Error writing generated JS: ${error}`);
|
||||
}
|
||||
|
||||
if (!isProduction && fileExisted) {
|
||||
try {
|
||||
// Tell browserSync to reload.
|
||||
await fetch(
|
||||
'http://localhost:8081/__browser_sync__?method=reload&args=bundle.js'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Couldn't communicate with browserSync!`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
buildJS();
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const fs = require('fs-extra');
|
||||
const util = require('util');
|
||||
const sass = require('node-sass');
|
||||
|
||||
const renderSass = util.promisify(sass.render);
|
||||
const inputFile = './src/assets/css/styles.scss';
|
||||
const outputFile = './build/assets/css/styles.css';
|
||||
const isProduction = process.env.ELEVENTY_ENV;
|
||||
|
||||
const buildSass = async function () {
|
||||
const { css } = await renderSass({
|
||||
file: inputFile,
|
||||
includePaths: [
|
||||
'node_modules/foundation-sites/scss/',
|
||||
'node_modules/slick-carousel/slick/',
|
||||
'node_modules/hamburgers/_sass/hamburgers',
|
||||
'node_modules/prismjs/themes',
|
||||
],
|
||||
outputStyle: isProduction ? 'compressed' : 'nested',
|
||||
});
|
||||
|
||||
// This is a hint to know if this is a first run, in which case
|
||||
// we don't need to tell browserSync to update.
|
||||
const fileExisted = await fs.pathExists(outputFile);
|
||||
|
||||
try {
|
||||
await fs.ensureFile(outputFile);
|
||||
await fs.writeFile(outputFile, css);
|
||||
} catch (error) {
|
||||
console.error(`Error writing generated CSS: ${error}`);
|
||||
}
|
||||
|
||||
if (!isProduction && fileExisted) {
|
||||
// Tell browserSync to reload.
|
||||
try {
|
||||
await fetch(
|
||||
'http://localhost:8081/__browser_sync__?method=reload&args=styles.css'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Couldn't communicate with browserSync!`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
buildSass();
|
|
@ -0,0 +1,76 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const { Liquid } = require('liquidjs');
|
||||
const md = require('markdown-it')({
|
||||
html: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
}).disable('code');
|
||||
const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight');
|
||||
const xmlFiltersPlugin = require('eleventy-xml-plugin');
|
||||
|
||||
const inputDir = path.relative(__dirname, 'src/content');
|
||||
const outputDir = path.relative(__dirname, 'build');
|
||||
|
||||
module.exports = function (eleventyConfig) {
|
||||
const liquidParser = new Liquid({
|
||||
root: ['./src/includes', './src/layouts'],
|
||||
extname: '.liquid',
|
||||
dynamicPartials: false,
|
||||
strictFilters: true,
|
||||
});
|
||||
|
||||
// Tell the config to not use gitignore for ignores.
|
||||
eleventyConfig.setUseGitIgnore(false);
|
||||
|
||||
eleventyConfig.setLibrary('liquid', liquidParser);
|
||||
// Markdown instance in general plus for filter in templates.
|
||||
eleventyConfig.setLibrary('md', md);
|
||||
eleventyConfig.addFilter('markdownify', function (value) {
|
||||
return md.render(value.toString());
|
||||
});
|
||||
|
||||
// Explicitly copy through the built files needed.
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
'./src/content/robots.txt': 'robots.txt',
|
||||
});
|
||||
eleventyConfig.addPassthroughCopy({ './src/assets/img/': 'assets/img/' });
|
||||
eleventyConfig.addPassthroughCopy({ './src/assets/fonts/': 'assets/fonts/' });
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
'./src/assets/js/basket-client.js': 'assets/js/basket-client.js',
|
||||
});
|
||||
eleventyConfig.addPassthroughCopy({
|
||||
'./node_modules/lunr/lunr.js': 'assets/js/lunr.js',
|
||||
});
|
||||
|
||||
eleventyConfig.setBrowserSyncConfig({
|
||||
callbacks: {
|
||||
ready: function (err, bs) {
|
||||
bs.addMiddleware('*', (req, res) => {
|
||||
const content_404 = fs.readFileSync('./build/404.html');
|
||||
// Provides the 404 content without redirect.
|
||||
res.write(content_404);
|
||||
// Add 404 http status code in request header.
|
||||
// res.writeHead(404, { "Content-Type": "text/html" });
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Plugins
|
||||
eleventyConfig.addPlugin(xmlFiltersPlugin);
|
||||
eleventyConfig.addPlugin(syntaxHighlight);
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: inputDir,
|
||||
output: outputDir,
|
||||
// The following are relative to the input dir.
|
||||
data: '../data/',
|
||||
includes: '../includes/',
|
||||
layouts: '../layouts/',
|
||||
},
|
||||
};
|
||||
};
|
50
package.json
|
@ -1,32 +1,59 @@
|
|||
{
|
||||
"name": "extension-workshop",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"description": "Firefox Extension Workshop",
|
||||
"main": "assets/scripts/scripts.js",
|
||||
"repository": "https://github.com/mozilla/extension-workshop",
|
||||
"author": "Lance Cummings <lance@glance.ca>",
|
||||
"license": "MPL-2.0",
|
||||
"private": true,
|
||||
"browserslist": [
|
||||
"> 0.5%",
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not dead"
|
||||
],
|
||||
"dependencies": {
|
||||
"dompurify": "2.0.12",
|
||||
"foundation-sites": "6.6.3",
|
||||
"hamburgers": "1.1.3",
|
||||
"jquery": "3.5.1",
|
||||
"liquidjs": "^9.14.0",
|
||||
"lunr": "2.3.8",
|
||||
"slick-carousel": "1.8.1",
|
||||
"velocity-animate": "1.5.2",
|
||||
"velocity-ui-pack": "1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^0.11.0",
|
||||
"@11ty/eleventy-plugin-syntaxhighlight": "^3.0.1",
|
||||
"chalk": "^4.1.0",
|
||||
"chokidar-cli": "^2.1.0",
|
||||
"clean-css": "^4.2.3",
|
||||
"eleventy-xml-plugin": "^0.1.0",
|
||||
"eslint": "7.2.0",
|
||||
"eslint-config-prettier": "6.11.0",
|
||||
"eslint-plugin-prettier": "3.1.3",
|
||||
"fs-extra": "^9.0.1",
|
||||
"markdown-it": "^11.0.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^7.0.32",
|
||||
"postcss-url": "^8.0.0",
|
||||
"posthtml": "^0.13.1",
|
||||
"posthtml-beautify": "^0.7.0",
|
||||
"posthtml-postcss": "^0.3.0",
|
||||
"posthtml-urls": "^1.0.0",
|
||||
"prettier": "2.0.5",
|
||||
"pretty-quick": "2.0.1",
|
||||
"prismjs": "^1.20.0",
|
||||
"rimraf": "3.0.2",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"stylelint": "13.6.1",
|
||||
"stylelint-config-standard": "20.0.0",
|
||||
"svgo": "1.3.2"
|
||||
"svgo": "1.3.2",
|
||||
"terser": "^4.8.0",
|
||||
"url-parse": "^1.4.7"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
|
@ -35,10 +62,17 @@
|
|||
"prettier-ci": "prettier -c '**'",
|
||||
"prettier-dev": "pretty-quick --branch master",
|
||||
"test": "npm run prettier-ci && npm run lint && npm run stylelint",
|
||||
"clean": "rimraf './.jekyll-cache/' && jekyll clean",
|
||||
"build": "JEKYLL_ENV=production SVGO_BIN='node_modules/svgo/bin/svgo' jekyll build",
|
||||
"start": "jekyll serve --unpublished --incremental --config _config.yml,_config_local.yml",
|
||||
"start-no-increment": "jekyll serve --unpublished --config _config.yml,_config_local.yml",
|
||||
"start-prodlike": "SVGO_BIN='node_modules/svgo/bin/svgo' jekyll serve"
|
||||
"clean": "rimraf './build/' './dist/'",
|
||||
"clean-dist": "rimraf './dist/'",
|
||||
"build": "eleventy --config=./eleventy.config.js && npm run sass:build && npm run uglify:build",
|
||||
"build:unpublished": "ELEVENTY_ENV=production BUILD_UNPUBLISHED=1 npm run build && bin/assets-pipeline",
|
||||
"build:production": "ELEVENTY_ENV=production npm run build && bin/assets-pipeline",
|
||||
"build:serve": "eleventy --serve --port=8081 --config=./eleventy.config.js",
|
||||
"build:debug": "DEBUG=Eleventy* eleventy --serve --port=8081 --config=./eleventy.config.js",
|
||||
"start": "npm run clean && npm-run-all -p build:serve sass:* uglify:*",
|
||||
"sass:build": "bin/build-styles",
|
||||
"sass:watch": "chokidar 'src/assets/css/*.scss' -c 'npm run sass:build'",
|
||||
"uglify:build": "bin/build-script",
|
||||
"uglify:watch": "chokidar 'src/assets/js/*.js' -c 'npm run uglify:build'"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
globals: {
|
||||
jQuery: 'readable',
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
plugins: ['prettier'],
|
||||
extends: ['prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
},
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always'],
|
||||
},
|
||||
};
|
|
@ -100,7 +100,7 @@ $sidebar-w: rem-calc(230);
|
|||
top: rem-calc(8);
|
||||
width: rem-calc(43);
|
||||
height: rem-calc(44);
|
||||
background: transparent asset_url('firefox-logo.png @optim') no-repeat
|
||||
background: transparent url('/assets/img/firefox-logo.png') no-repeat
|
||||
center/contain;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ Stylesheet: Main Stylesheet
|
|||
// SLICK SLIDESHOW
|
||||
// ------
|
||||
|
||||
@import 'slick';
|
||||
@import 'slick.scss';
|
||||
|
||||
// TYPOGRAPHY
|
||||
// -------------------------------------------------------------------------------------------------------------------
|
||||
|
@ -66,11 +66,20 @@ a:not(.button) {
|
|||
}
|
||||
}
|
||||
|
||||
a[target='_blank']::after {
|
||||
color: inherit;
|
||||
padding-left: 0.3em;
|
||||
display: inline-block;
|
||||
@include icon_font($char: $icon-newtab);
|
||||
.sidenav,
|
||||
main {
|
||||
a[href*="//"]:not([href*="extension-workshop.com"]),
|
||||
a[href*="//"]:not([href*="extensionworkshop.allizom.org"]),
|
||||
a[href*="//"]:not([href*="extensionworkshop-dev.allizom.org"]),
|
||||
a[href*="//"]:not([href*="localhost"]),
|
||||
a[target='_blank'] {
|
||||
&::after {
|
||||
color: inherit;
|
||||
padding: 0 0.25em;
|
||||
display: inline-block;
|
||||
@include icon_font($char: $icon-newtab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
|
@ -177,6 +186,10 @@ a:focus code {
|
|||
}
|
||||
}
|
||||
|
||||
.text-end {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
// Blockquote
|
||||
// ------
|
||||
|
||||
|
@ -243,6 +256,10 @@ code {
|
|||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
code[class*='language-'] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a code {
|
||||
color: #00b3f4;
|
||||
}
|
||||
|
@ -593,7 +610,7 @@ html {
|
|||
display: block;
|
||||
width: rem-calc(25);
|
||||
height: rem-calc(25);
|
||||
background: transparent asset_url('firefox-logo.png @optim') no-repeat
|
||||
background: transparent url('/assets/img/firefox-logo.png') no-repeat
|
||||
center/contain;
|
||||
}
|
||||
|
||||
|
@ -632,7 +649,7 @@ $hamburger-hover-opacity: 1;
|
|||
$hamburger-hover-transition-duration: 0.15s;
|
||||
$hamburger-hover-transition-timing-function: linear;
|
||||
|
||||
@import '../../node_modules/hamburgers/_sass/hamburgers/hamburgers.scss';
|
||||
@import 'hamburgers';
|
||||
|
||||
.site-header .hamburger {
|
||||
position: absolute;
|
||||
|
@ -1023,7 +1040,7 @@ $hamburger-hover-transition-timing-function: linear;
|
|||
text-decoration: none;
|
||||
text-indent: 120%;
|
||||
white-space: nowrap;
|
||||
background: #fff asset_url('moz-logo.svg @optim') no-repeat 0 0 /#{rem-calc(
|
||||
background: #fff url('/assets/img/moz-logo.svg') no-repeat 0 0 /#{rem-calc(
|
||||
100
|
||||
)};
|
||||
color: $white;
|
||||
|
@ -1521,8 +1538,8 @@ a.tile-block-link {
|
|||
}
|
||||
}
|
||||
|
||||
&[target='_blank']::after {
|
||||
display: none;
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@include breakpoint(large) {
|
||||
|
@ -1685,7 +1702,7 @@ a.tile-block-link {
|
|||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent asset_url('ajax-loader.svg @optim') no-repeat
|
||||
background: transparent url('/assets/img/ajax-loader.svg') no-repeat
|
||||
center/86%;
|
||||
}
|
||||
}
|
||||
|
@ -1737,8 +1754,8 @@ a.tile-block-link {
|
|||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent asset_url('ajax-loader.svg @optim')
|
||||
no-repeat center/86%;
|
||||
background: transparent url('/assets/img/ajax-loader.svg') no-repeat
|
||||
center/86%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1825,7 +1842,7 @@ a.tile-block-link {
|
|||
top: calc(50% - #{rem-calc(32)});
|
||||
width: rem-calc(64);
|
||||
height: rem-calc(64);
|
||||
background: transparent asset_url('ajax-loader.svg @optim') no-repeat
|
||||
background: transparent url('/assets/img/ajax-loader.svg') no-repeat
|
||||
center/100%;
|
||||
}
|
||||
}
|
||||
|
@ -2165,6 +2182,7 @@ span.example {
|
|||
border-left: #{rem-calc(2)} solid $warning-color;
|
||||
background-color: $warning-color-light;
|
||||
border-radius: $global-radius;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&::before {
|
||||
@include icon_font($char: $icon-info);
|
||||
|
@ -2175,6 +2193,10 @@ span.example {
|
|||
left: rem-calc(12);
|
||||
top: rem-calc(14);
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Alert
|
|
@ -25,32 +25,32 @@
|
|||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: asset_url('inter/Inter-Regular.woff2') format('woff2'),
|
||||
asset_url('inter/Inter-Regular.woff') format('woff');
|
||||
src: url('/assets/fonts/inter/Inter-Regular.woff2') format('woff2'),
|
||||
url('/assets/fonts/inter/Inter-Regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: asset_url('inter/Inter-Italic.woff2') format('woff2'),
|
||||
asset_url('inter/Inter-Italic.woff') format('woff');
|
||||
src: url('/assets/fonts/inter/Inter-Italic.woff2') format('woff2'),
|
||||
url('/assets/fonts/inter/Inter-Italic.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: asset_url('inter/Inter-SemiBold.woff2') format('woff2'),
|
||||
asset_url('inter/Inter-SemiBold.woff') format('woff');
|
||||
src: url('/assets/fonts/inter/Inter-SemiBold.woff2') format('woff2'),
|
||||
url('/assets/fonts/inter/Inter-SemiBold.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: asset_url('inter/Inter-SemiBoldItalic.woff2') format('woff2'),
|
||||
asset_url('inter/Inter-SemiBoldItalic.woff') format('woff');
|
||||
src: url('/assets/fonts/inter/Inter-SemiBoldItalic.woff2') format('woff2'),
|
||||
url('/assets/fonts/inter/Inter-SemiBoldItalic.woff') format('woff');
|
||||
}
|
||||
|
||||
// Element Shadows
|
||||
|
@ -115,12 +115,13 @@ $print-transparent-backgrounds: true;
|
|||
|
||||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src: asset_url('icomoon/fonts/icomoon.eot');
|
||||
src: asset_url('icomoon/fonts/icomoon.eot#iefix') format('embedded-opentype'),
|
||||
asset_url('icomoon/fonts/icomoon.woff2') format('woff2'),
|
||||
asset_url('icomoon/fonts/icomoon.ttf') format('truetype'),
|
||||
asset_url('icomoon/fonts/icomoon.woff') format('woff'),
|
||||
asset_url('icomoon/fonts/icomoon.svg#icomoon') format('svg');
|
||||
src: url('/assets/fonts/icomoon/fonts/icomoon.eot');
|
||||
src: url('/assets/fonts/icomoon/fonts/icomoon.eot#iefix')
|
||||
format('embedded-opentype'),
|
||||
url('/assets/fonts/icomoon/fonts/icomoon.woff2') format('woff2'),
|
||||
url('/assets/fonts/icomoon/fonts/icomoon.ttf') format('truetype'),
|
||||
url('/assets/fonts/icomoon/fonts/icomoon.woff') format('woff'),
|
||||
url('/assets/fonts/icomoon/fonts/icomoon.svg#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
|
@ -26,7 +26,7 @@ CSS file.
|
|||
@include foundation-visibility-classes;
|
||||
|
||||
// Import your custom styles
|
||||
@import 'codehighlighting';
|
||||
@import 'prism-okaidia';
|
||||
@import 'main';
|
||||
@import 'contentguidelines';
|
||||
@import 'fullwidth';
|
До Ширина: | Высота: | Размер: 19 KiB После Ширина: | Высота: | Размер: 19 KiB |
До Ширина: | Высота: | Размер: 6.8 KiB После Ширина: | Высота: | Размер: 6.8 KiB |
До Ширина: | Высота: | Размер: 186 KiB После Ширина: | Высота: | Размер: 186 KiB |
До Ширина: | Высота: | Размер: 62 KiB После Ширина: | Высота: | Размер: 62 KiB |
До Ширина: | Высота: | Размер: 2.7 KiB После Ширина: | Высота: | Размер: 2.7 KiB |
До Ширина: | Высота: | Размер: 5.3 KiB После Ширина: | Высота: | Размер: 5.3 KiB |
До Ширина: | Высота: | Размер: 7.6 KiB После Ширина: | Высота: | Размер: 7.6 KiB |
До Ширина: | Высота: | Размер: 5.7 KiB После Ширина: | Высота: | Размер: 5.7 KiB |
До Ширина: | Высота: | Размер: 6.1 KiB После Ширина: | Высота: | Размер: 6.1 KiB |
До Ширина: | Высота: | Размер: 5.3 KiB После Ширина: | Высота: | Размер: 5.3 KiB |
До Ширина: | Высота: | Размер: 14 KiB После Ширина: | Высота: | Размер: 14 KiB |
До Ширина: | Высота: | Размер: 1.6 KiB После Ширина: | Высота: | Размер: 1.6 KiB |
До Ширина: | Высота: | Размер: 66 KiB После Ширина: | Высота: | Размер: 66 KiB |
До Ширина: | Высота: | Размер: 98 KiB После Ширина: | Высота: | Размер: 98 KiB |
До Ширина: | Высота: | Размер: 6.1 KiB После Ширина: | Высота: | Размер: 6.1 KiB |
До Ширина: | Высота: | Размер: 73 KiB После Ширина: | Высота: | Размер: 73 KiB |
До Ширина: | Высота: | Размер: 62 KiB После Ширина: | Высота: | Размер: 62 KiB |
До Ширина: | Высота: | Размер: 189 KiB После Ширина: | Высота: | Размер: 189 KiB |
До Ширина: | Высота: | Размер: 90 KiB После Ширина: | Высота: | Размер: 90 KiB |
До Ширина: | Высота: | Размер: 28 KiB После Ширина: | Высота: | Размер: 28 KiB |
До Ширина: | Высота: | Размер: 22 KiB После Ширина: | Высота: | Размер: 22 KiB |
До Ширина: | Высота: | Размер: 62 KiB После Ширина: | Высота: | Размер: 62 KiB |
До Ширина: | Высота: | Размер: 127 KiB После Ширина: | Высота: | Размер: 127 KiB |
До Ширина: | Высота: | Размер: 22 KiB После Ширина: | Высота: | Размер: 22 KiB |
До Ширина: | Высота: | Размер: 119 KiB После Ширина: | Высота: | Размер: 119 KiB |
До Ширина: | Высота: | Размер: 54 KiB После Ширина: | Высота: | Размер: 54 KiB |
До Ширина: | Высота: | Размер: 60 KiB После Ширина: | Высота: | Размер: 60 KiB |
До Ширина: | Высота: | Размер: 95 KiB После Ширина: | Высота: | Размер: 95 KiB |
До Ширина: | Высота: | Размер: 47 KiB После Ширина: | Высота: | Размер: 47 KiB |
До Ширина: | Высота: | Размер: 70 KiB После Ширина: | Высота: | Размер: 70 KiB |
До Ширина: | Высота: | Размер: 22 KiB После Ширина: | Высота: | Размер: 22 KiB |
До Ширина: | Высота: | Размер: 78 KiB После Ширина: | Высота: | Размер: 78 KiB |
До Ширина: | Высота: | Размер: 100 KiB После Ширина: | Высота: | Размер: 100 KiB |
До Ширина: | Высота: | Размер: 24 KiB После Ширина: | Высота: | Размер: 24 KiB |
До Ширина: | Высота: | Размер: 106 KiB После Ширина: | Высота: | Размер: 106 KiB |
До Ширина: | Высота: | Размер: 65 KiB После Ширина: | Высота: | Размер: 65 KiB |
До Ширина: | Высота: | Размер: 59 KiB После Ширина: | Высота: | Размер: 59 KiB |
До Ширина: | Высота: | Размер: 58 KiB После Ширина: | Высота: | Размер: 58 KiB |
До Ширина: | Высота: | Размер: 35 KiB После Ширина: | Высота: | Размер: 35 KiB |
До Ширина: | Высота: | Размер: 33 KiB После Ширина: | Высота: | Размер: 33 KiB |
До Ширина: | Высота: | Размер: 111 KiB После Ширина: | Высота: | Размер: 111 KiB |
До Ширина: | Высота: | Размер: 48 KiB После Ширина: | Высота: | Размер: 48 KiB |
До Ширина: | Высота: | Размер: 40 KiB После Ширина: | Высота: | Размер: 40 KiB |