├── .eleventy.js
├── .eleventyignore
├── .gitignore
├── 404.md
├── LICENSE
├── README.md
├── _data
└── metadata.json
├── _includes
├── assets
│ ├── css
│ │ └── inline.css
│ ├── js
│ │ └── inline.js
│ └── svg
│ │ └── logo.svg
├── components
│ ├── app.html
│ ├── body-close.html
│ ├── devs.njk
│ ├── footer.html
│ ├── form.njk
│ ├── head.njk
│ ├── meta.njk
│ └── nav.njk
├── layouts
│ ├── base.njk
│ ├── contact.njk
│ ├── dev.njk
│ ├── devs.njk
│ ├── home.njk
│ ├── offline.njk
│ ├── page.njk
│ └── submitted.njk
└── macros
│ ├── form.njk
│ ├── site.njk
│ └── ui.njk
├── admin
├── config.yml
├── index.html
└── preview-templates
│ ├── index.js
│ ├── page.js
│ └── post.js
├── devs
├── devs.json
└── tagged.njk
├── manifest.json
├── netlify.toml
├── package.json
├── pages
├── about.md
├── contact.md
├── home.md
├── offline.md
├── pages.json
├── submitted.md
├── thanks.md
└── wants.md
├── static
├── img
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── droid.svg
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── flying-car.svg
│ ├── hologram-dash.svg
│ ├── hologram-folder.svg
│ ├── hologram.svg
│ ├── icon.png
│ ├── icon.svg
│ ├── jetpack.svg
│ ├── logo.svg
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ ├── ms-icon-70x70.png
│ ├── netlify.svg
│ ├── og-icon.png
│ ├── repeating-pattern.png
│ ├── repeating-pattern.svg
│ └── sonic-screwdriver.svg
└── js
│ ├── indieconfig.js
│ ├── sharing.js
│ └── webaction.js
└── sw.js
/.eleventy.js:
--------------------------------------------------------------------------------
1 | const { DateTime } = require("luxon");
2 | const CleanCSS = require("clean-css");
3 | const UglifyJS = require("uglify-es");
4 | const sanitizeHTML = require('sanitize-html')
5 | const htmlmin = require("html-minifier");
6 | const widont = require("widont");
7 | const md = require("markdown-it")({
8 | html: true,
9 | linkify: true,
10 | typographer: true,
11 | });
12 | const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
13 | const inclusiveLangPlugin = require("@11ty/eleventy-plugin-inclusive-language");
14 |
15 | module.exports = function(eleventyConfig) {
16 |
17 | // Date formatting (human readable)
18 | eleventyConfig.addFilter("readableDate", date => {
19 | return DateTime.fromJSDate(date).toFormat("dd LLL yyyy");
20 | });
21 |
22 | // Date formatting (machine readable)
23 | eleventyConfig.addFilter("machineDate", date => {
24 | return DateTime.fromJSDate(date).toISO();
25 | });
26 |
27 | // Markdownify
28 | eleventyConfig.addFilter("markdownify", text => {
29 | return md.renderInline( text );
30 | });
31 |
32 | // unSluggify
33 | eleventyConfig.addFilter("unslug", text => {
34 | text = text.charAt(0).toUpperCase() + text.slice(1);
35 | return text.replace(/-/g, ' ');
36 | });
37 |
38 | // Name List
39 | eleventyConfig.addShortcode("NameList", names => {
40 | let strings = [],
41 | string = "",
42 | count = names.length;
43 | while ( count-- )
44 | {
45 | let url = names[count].url || `https://twitter.com/${names[count].twitter}/`;
46 | strings.unshift( `${names[count].name} ` );
47 | }
48 | count = strings.length;
49 | if ( count > 2 )
50 | {
51 | strings[count-1] = "and " + strings[count-1];
52 | string = strings.join(", ");
53 | }
54 | else if ( count == 2 )
55 | {
56 | string = `${strings[0]} and ${strings[1]}`;
57 | }
58 | else
59 | {
60 | string = strings[0];
61 | }
62 | return `${string}`;
63 | });
64 |
65 | // Fix proper nouns
66 | eleventyConfig.addFilter("fixNames", text => {
67 | let test = text.toLowerCase(),
68 | acronyms = [ "html", "css", "svg" ],
69 | camel_case = [ "JavaScript" ],
70 | i, proper_name;
71 |
72 | if ( acronyms.indexOf( test ) > -1 )
73 | {
74 | return text.toUpperCase();
75 | }
76 | else
77 | {
78 | for ( i in camel_case )
79 | {
80 | proper_name = camel_case[i];
81 | if ( proper_name.toLowerCase() == test )
82 | {
83 | return proper_name;
84 | }
85 | }
86 | }
87 | return text;
88 | });
89 |
90 | // Widont
91 | eleventyConfig.addFilter("widont", function(text) {
92 | return `${widont( text )}`;
93 | });
94 |
95 |
96 | // Minify CSS
97 | eleventyConfig.addFilter("cssmin", function(code) {
98 | return new CleanCSS({}).minify(code).styles;
99 | });
100 |
101 | // Minify JS
102 | eleventyConfig.addFilter("jsmin", function(code) {
103 | let minified = UglifyJS.minify(code);
104 | if (minified.error) {
105 | console.log("UglifyJS error: ", minified.error);
106 | return code;
107 | }
108 | return minified.code;
109 | });
110 |
111 | // Minify HTML output
112 | eleventyConfig.addTransform("htmlmin", function(content, outputPath) {
113 | if (outputPath.indexOf(".html") > -1) {
114 | let minified = htmlmin.minify(content, {
115 | useShortDoctype: true,
116 | removeComments: true,
117 | collapseWhitespace: true,
118 | collapseBooleanAttributes: true
119 | });
120 | minified = minified.replace('\u00a0', '\u00a0 ');
121 | return minified;
122 | }
123 | return content;
124 | });
125 |
126 | // Minify JS output
127 | eleventyConfig.addTransform("jsmin", function(content, outputPath) {
128 | if (outputPath.indexOf(".js") > -1) {
129 | let minified = UglifyJS.minify(content);
130 | return minified;
131 | }
132 | return content;
133 | });
134 |
135 | // limit filter
136 | eleventyConfig.addNunjucksFilter("limit", function(array, limit) {
137 | return array.slice(0, limit);
138 | });
139 |
140 | eleventyConfig.addCollection("devs", collection => {
141 | // get unsorted items
142 | return collection.getAll().filter( item => {
143 | return item.inputPath.indexOf("devs/") > -1;
144 | });
145 | });
146 | eleventyConfig.addFilter("extractID", url => {
147 | url = url.split("/");
148 | return url[2];
149 | });
150 |
151 | eleventyConfig.addCollection("tags", function(collection) {
152 | // get unsorted items
153 | var tags = [];
154 | collection.getAll()
155 | .map( item => {
156 | if ( item.inputPath.indexOf("wants/") > -1 )
157 | {
158 | item.data.tags.map( tag => {
159 | if ( tags.indexOf( tag ) < 0 )
160 | {
161 | tags.push( tag );
162 | }
163 | });
164 | }
165 | });
166 | return tags.sort();
167 | });
168 |
169 | eleventyConfig.addFilter("toString", function(collection, separator, props) {
170 | var ret = [],
171 | i = collection.length;
172 | while ( i-- )
173 | {
174 | let str = [],
175 | j = props.length;
176 | while ( j-- )
177 | {
178 | let text = collection[i].data[props[j]];
179 | if ( props[j].indexOf("date") > -1 )
180 | {
181 | text = new Date( text );
182 | text = DateTime.fromJSDate(text).toFormat("dd LLL yyyy");
183 | }
184 | str.unshift( text );
185 | }
186 | ret.unshift( str.join( separator ) );
187 | }
188 | return ret;
189 | });
190 |
191 | eleventyConfig.addFilter("getDirectory", function(url) {
192 | url = url.split('/');
193 | return url[1];
194 | });
195 |
196 | // Don't process folders with static assets e.g. images
197 | eleventyConfig.addPassthroughCopy("sw.js");
198 | eleventyConfig.addPassthroughCopy("static/img");
199 | eleventyConfig.addPassthroughCopy("static/js");
200 | eleventyConfig.addPassthroughCopy("manifest.json");
201 | eleventyConfig.addPassthroughCopy("admin");
202 | // eleventyConfig.addPassthroughCopy("_includes/assets/");
203 |
204 | /* Markdown Plugins */
205 | let markdownIt = require("markdown-it");
206 | let markdownItAnchor = require("markdown-it-anchor");
207 | let options = {
208 | html: true,
209 | breaks: true,
210 | linkify: true
211 | };
212 | let opts = {
213 | permalink: false
214 | };
215 |
216 | eleventyConfig.setLibrary("md", markdownIt(options)
217 | .use(markdownItAnchor, opts)
218 | );
219 |
220 | eleventyConfig.addPlugin(syntaxHighlight);
221 |
222 | //eleventyConfig.addPlugin(inclusiveLangPlugin);
223 |
224 | return {
225 | templateFormats: ["md", "njk", "html", "liquid"],
226 |
227 | // If your site lives in a different subdirectory, change this.
228 | // Leading or trailing slashes are all normalized away, so don’t worry about it.
229 | // If you don’t have a subdirectory, use "" or "/" (they do the same thing)
230 | // This is only used for URLs (it does not affect your file structure)
231 | pathPrefix: "/",
232 |
233 | markdownTemplateEngine: "liquid",
234 | htmlTemplateEngine: "njk",
235 | dataTemplateEngine: "njk",
236 | passthroughFileCopy: true,
237 | dir: {
238 | input: ".",
239 | includes: "_includes",
240 | data: "_data",
241 | output: "_site"
242 | }
243 | };
244 | };
245 |
--------------------------------------------------------------------------------
/.eleventyignore:
--------------------------------------------------------------------------------
1 | README.md
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _site/
2 | node_modules/
3 | package-lock.json
4 | .env
--------------------------------------------------------------------------------
/404.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 404
3 | layout: layouts/page.njk
4 | permalink: /404.html
5 | ---
6 | Uh oh, the resource you requested isn't here. Sorry.
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Aaron Gustafson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # devsofcolour
2 | A searchable and sortable database to find devs of colour of all skill levels, languages, frameworks.
3 |
4 | # How to build locally
5 | to build locally:
6 | * first make sure you have [node](https://nodejs.org) installed (this has been tested with node version v10.15.3 and npm version 6.4.1)
7 | * While in the `devsofcolour` directory in your terminal of choice, run `npm install` to install the repos packaged dependencies
8 | * once that's complete, run `npm start`
9 | * your terminal prompt should provide you with a few different local addresses to access both the live build of the website as well as the CMS interface for managing content
10 | * If you run into any issues or have concerns please use the [issues tab](https://github.com/tatianamac/devsofcolour/issues) on the github repo.
--------------------------------------------------------------------------------
/_data/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Devs of Colour",
3 | "url": "https://devsofcolour.org",
4 | "domain": "devsofcolour.org",
5 | "author": {
6 | "name": "Tatiana Mac",
7 | "email": "??",
8 | "github": "https://github.com/tatianamac"
9 | }
10 | }
--------------------------------------------------------------------------------
/_includes/assets/css/inline.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Devs of Colour
4 |
5 | colors:
6 | - Green: #3b9559
7 | - Dark Green: #005e22
8 | - Mint: #cef6da;
9 | - White: #fff
10 | - Yellow: #ffffeb
11 | - Black: #003d16
12 |
13 | */
14 |
15 | *, *:before, *:after {
16 | box-sizing: border-box;
17 | transition: color .5s, background-color .5s;
18 | }
19 |
20 | html,
21 | body {
22 | color: #003d16;
23 |
24 | font-family: Poppins, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
25 | font-size: 1em;
26 |
27 | font-smooth: always;
28 | -webkit-font-smoothing: subpixel-antialiased;
29 | -moz-osx-font-smoothing: grayscale;
30 |
31 | line-height: 1.5;
32 |
33 | padding: 0;
34 | margin: 0;
35 |
36 | display: flex;
37 | min-height: 100vh;
38 | flex-direction: column;
39 | }
40 |
41 | h1, h2, h3, h4, h5, h6 {
42 | font-weight: 400;
43 | margin: 0;
44 | }
45 | h1, h2 {
46 | line-height: 1.1;
47 | }
48 | h3, h4 {
49 | line-height: 1.3;
50 | }
51 | h5 {
52 | line-height: 1.4;
53 | }
54 | h1 { font-size: 3.75rem; }
55 | h2 { font-size: 2.875rem; }
56 | h3 { font-size: 2.125rem; }
57 | h4 { font-size: 1.75rem; }
58 | h5 { font-size: 1.25rem; }
59 | h6 { font-size: 1rem; }
60 |
61 | p,
62 | pre,
63 | code {
64 | line-height: 1.5;
65 | }
66 |
67 | code {
68 | background: #CFF7D9;
69 | padding: 0 .25em;
70 | border: 1px solid #3D9558;
71 | border-radius: .15em;
72 | font-size: 1.25em;
73 | color: #005e22;
74 | }
75 |
76 | a[href],
77 | a[href]:visited {
78 | color: #005e22;
79 | }
80 |
81 | main {
82 | background: rgba(255, 255, 255, 0.8);
83 | padding: 2em 2em;
84 | margin: 0 auto;
85 | flex: 1;
86 | width: 100%;
87 | }
88 | @media screen and (min-width: 80ch) {
89 | main {
90 | max-width: 80ch;
91 | }
92 | }
93 |
94 | main * + * {
95 | margin-top: 1rem;
96 | }
97 |
98 | main :first-child,
99 | main > article :first-child {
100 | margin-top: 0;
101 | }
102 |
103 | pre {
104 | font-size: 14px;
105 | direction: ltr;
106 | text-align: left;
107 | white-space: pre-wrap;
108 | }
109 |
110 | pre code {
111 | display: block;
112 | padding: .5em 1em;
113 | }
114 |
115 | small {
116 | display: block;
117 | font-size: .875rem;
118 | line-height: 1.25;
119 | margin-top: .5rem;
120 | }
121 |
122 | /* Header */
123 | header {
124 | margin: 0 auto;
125 | width: 100%;
126 | max-width: 80ch;
127 | }
128 | #logo {
129 | display: block;
130 | padding: 2em 1em 0;
131 | margin: 0 auto;
132 | width: 100%;
133 | max-width: 555px; /* logo width */
134 | }
135 | .logo-container {
136 | display: block;
137 | margin: 0 auto;
138 | width: 100%;
139 | height: 0;
140 | padding: 35.675675% 0 0;
141 | font-weight: bold;
142 | font-style: normal;
143 | text-decoration: none;
144 | position: relative;
145 | }
146 | .logo-container svg {
147 | display: block;
148 | width: 100%;
149 | height: auto;
150 | position: absolute;
151 | top: 0;
152 | left: 0;
153 | bottom: 0;
154 | right: 0;
155 | }
156 |
157 |
158 |
159 | /* Header Nav */
160 | nav ul {
161 | font-size: 1.5rem;
162 | list-style: none;
163 | margin: 2rem auto 0;
164 | padding: 0;
165 | text-align: center;
166 | }
167 | nav a {
168 | display: block;
169 | padding: .25em 1em 1px;
170 | margin: 0;
171 |
172 | font-weight: 600;
173 | text-decoration: none;
174 | text-transform: uppercase;
175 | letter-spacing: 0.02em;
176 | }
177 | [aria-current] {
178 | background: rgba(255, 255, 255, 0.8);
179 | }
180 | @media screen and (min-width: 36em) {
181 | nav ul {
182 | display: flex;
183 | justify-content: center;
184 | }
185 | nav li {
186 | position: relative;
187 | }
188 | nav li::before,
189 | nav li::after,
190 | nav a::before {
191 | background: rgba(255, 255, 255, 0);
192 | content: "";
193 | position: absolute;
194 | animation-fill-mode: forwards;
195 | }
196 | nav li:hover::before,
197 | nav li:hover::after,
198 | nav a:hover::before {
199 | background: rgba(255, 255, 255, 1);
200 | }
201 | nav li::before {
202 | bottom: 0;
203 | left: 0;
204 | width: 1px;
205 | height: 0;
206 | transition: height .25s;
207 | }
208 | nav li::after {
209 | top: 0;
210 | left: 0;
211 | width: 0;
212 | height: 1px;
213 | transition: width .5s .25s;
214 | }
215 | nav li a::before {
216 | top: 1px;
217 | right: 0;
218 | width: 1px;
219 | height: 0;
220 | transition: height .25s .75s;
221 | }
222 | nav li:hover::before {
223 | height: calc( 100% - 1px );
224 | }
225 | nav li:hover::after {
226 | width: 100%;
227 | }
228 | nav a:hover::before {
229 | height: calc( 100% - 1px );
230 | }
231 | }
232 |
233 | /* sharing */
234 | .share dt {
235 | font-weight: bold;
236 | }
237 | .share dd {
238 | margin: .25em 0 0;
239 | padding: 0;
240 | }
241 | .share-link {
242 | border-radius: 6px;
243 | display: inline-block;
244 | margin: 0 .25em;
245 | padding: 4px 8px;
246 | transition: filter .5s;
247 | }
248 | .share-link:hover,
249 | .share-link:focus,
250 | .share-link:active {
251 | filter: brightness(75%) contrast(2.5);
252 | }
253 | .share-link__icon {
254 | vertical-align: top;
255 | display: inline-block;
256 | }
257 | .share-link__icon path {
258 | fill: white;
259 | }
260 | .share-link__text {
261 | position: absolute !important;
262 | height: 1px;
263 | width: 1px;
264 | overflow: hidden;
265 | clip: rect(1px, 1px, 1px, 1px);
266 | }
267 | .share-link--twitter {
268 | background: #26c4f1;
269 | }
270 | .share-link--twitter .share-link__icon {
271 | width: 22px;
272 | height: 22px;
273 | margin-top: 3px;
274 | }
275 | .share-link--linkedin {
276 | background: #007bb6;
277 | }
278 | .share-link--linkedin .share-link__icon {
279 | width: 23px;
280 | height: 23px;
281 | margin-top: 3px;
282 | }
283 | .share-link--facebook {
284 | background: #306199;
285 | }
286 | .share-link--facebook .share-link__icon {
287 | width: 20px;
288 | height: 20px;
289 | margin-top: 4px;
290 | }
291 |
292 | /* Tags & votes */
293 | .tags > * {
294 | display: inline;
295 | margin: 0;
296 | padding: 0;
297 | }
298 | .tags dt {
299 | font-weight: bold;
300 | }
301 | .tags dt::after {
302 | content: ": ";
303 | }
304 |
305 | /* Footer */
306 | footer {
307 | background: #005e22;
308 | color: #fff;
309 | }
310 | footer a[href], footer a[href]:visited {
311 | color: #fff;
312 | }
313 | footer p {
314 | max-width: 80ch;
315 | margin: 0 auto;
316 | padding: 2em;
317 | }
318 | footer small + small {
319 | margin-top: .75rem;
320 | }
321 |
322 |
323 | /* Sections */
324 | section {
325 | margin-top: 3rem;
326 | }
327 |
328 | /* Post Tags */
329 | a[rel="tag"],
330 | a[rel="tag"]:visited {
331 | display: inline-block;
332 | vertical-align: text-top;
333 | text-transform: uppercase;
334 | letter-spacing: .1em;
335 | font-size: .625em;
336 | padding: 0 .5em;
337 | line-height: 2em;
338 | height: 2em;
339 | border: 1px solid var(--white);
340 | background-color: var(--white);
341 | color: var(--blue);
342 | border-radius: .25em;
343 | text-decoration: none;
344 | margin: 0 .5em .5em 0;
345 | }
346 |
347 | a[rel="tag"]:hover {
348 | border: 1px solid var(--blue);
349 | background-color: var(--blue);
350 | color: var(--white);
351 | }
352 |
353 | a[rel="tag"]:last-child {
354 | margin-right: 0;
355 | }
356 |
357 | /* Pagination */
358 | .pagination {
359 | margin-top: 3rem;
360 | padding-top: 1rem;
361 | border-top: 1px solid #005e22;
362 |
363 | display: grid;
364 | grid-template: "prev page next";
365 | grid-gap: 1rem;
366 | align-items: center;
367 | grid-auto-columns: 1fr;
368 | }
369 | .pagination strong {
370 | grid-area: page;
371 | justify-self: center;
372 | }
373 | .pagination a {
374 | padding: 0;
375 | }
376 | .pagination a[rel=prev] {
377 | grid-area: prev;
378 | justify-self: start;
379 | }
380 | .pagination a[rel=next] {
381 | grid-area: next;
382 | justify-self: end;
383 | }
384 |
385 | /* Form */
386 | .questions {
387 | margin: 0;
388 | padding: 0;
389 | list-style: none;
390 | perspective: 1000px;
391 | }
392 | .question {
393 | margin-bottom: 1.5rem;
394 | transition: transform .15s;
395 | transform-origin: 50% 50%;
396 | }
397 | .question:focus-within {
398 | transform: scale(1.02);
399 | }
400 | .question__addendum,
401 | .question__description {
402 | display: block;
403 | margin-top: .3rem;
404 | }
405 | .question__description {
406 | font-size: .875rem;
407 | line-height: 1.25;
408 | }
409 | label {
410 | display: block;
411 | font-weight: 600;
412 | }
413 | form br {
414 | display: none;
415 | }
416 | input,
417 | textarea,
418 | select {
419 | background-color: #fff;
420 | font-size: 1rem;
421 | font-family: inherit;
422 | border: 1px solid #003d16;
423 | margin-top: .15rem;
424 | padding: .25em .5em;
425 | width: 100%;
426 | }
427 | input:focus,
428 | select:focus,
429 | textarea:focus {
430 | background-color: #ffffeb;
431 | }
432 | label input {
433 | width: auto;
434 | }
435 |
436 | .question--confirm {
437 | font-weight: normal;
438 | }
439 | .question--confirm input {
440 | width: auto;
441 | vertical-align: middle;
442 | /* hacky */
443 | position: relative;
444 | top: -.1em;
445 | }
446 |
447 |
448 | button,
449 | .button {
450 | cursor: pointer;
451 | display: inline-block;
452 | background-color: #005e22;
453 | color: #fff;
454 | border: 0;
455 | border-radius: .5rem;
456 | font-size: 1rem;
457 | font-family: inherit;
458 | padding: .5em 3em;
459 | transform: translateX(0) translateY(0);
460 | box-shadow: none;
461 | transition: transform .25s, box-shadow .25s, background-color .5s;
462 | }
463 | a[href].button,
464 | a[href].button:visited {
465 | color: #fff;
466 | text-decoration: none;
467 | margin-top: .25rem;
468 | padding: .5em 1em;
469 | }
470 | .button .button__icon {
471 | width: auto;
472 | height: 22px;
473 | transform: translate(-2px, 4px);
474 | }
475 | .button__icon path {
476 | fill: white;
477 | }
478 | button:hover,
479 | button:focus,
480 | .button:hover,
481 | .button:focus {
482 | background-color: #005e22;
483 | background-color: #005e22;
484 | transform: translateX(-2px) translateY(-2px);
485 | box-shadow: 3px 3px 0 #CFF7D9, 6px 6px 0 #3D9558;
486 | outline: 0;
487 | }
488 | button:hover:active,
489 | button:focus:active,
490 | .button:hover:active,
491 | .button:focus:active {
492 | transform: translateX(0) translateY(0);
493 | box-shadow: none;
494 | transition: background-color .5s;
495 | }
496 |
497 | @media screen and (min-width:768px) {
498 | :root {
499 | font-size: 1.1rem;
500 | }
501 | }
502 |
503 | .devs {
504 | list-style: none;
505 | padding: 0;
506 | margin-left: 0;
507 | }
508 | .dev + .dev {
509 | margin-top: 2rem;
510 | border-top: 1px solid #005e22;
511 | padding-top: 1rem;
512 | }
513 | .dev__meta {
514 | font-style: italic;
515 | font-size: .875rem;
516 | }
517 | .dev__title {
518 | font-size: 2.125rem;
519 | }
520 | h1.dev__title {
521 | font-size: 2.875rem;
522 | }
523 |
524 |
525 | [hidden] {
526 | display: none;
527 | }
528 |
529 | hr {
530 | background: #005e22;
531 | border: 0;
532 | margin-top: 2rem;
533 | height: 1px;
534 | }
535 |
536 | @media only screen and (max-width: 40em) {
537 | b.shy::after {
538 | content: " ";
539 | }
540 | }
541 |
--------------------------------------------------------------------------------
/_includes/assets/js/inline.js:
--------------------------------------------------------------------------------
1 | if (window.netlifyIdentity) {
2 | window.netlifyIdentity.on("init", user => {
3 | if (!user) {
4 | window.netlifyIdentity.on("login", () => {
5 | document.location.href = "/admin/";
6 | });
7 | }
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/_includes/assets/svg/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/_includes/components/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/_includes/components/body-close.html:
--------------------------------------------------------------------------------
1 | {% if javascript.body %}
2 | {% for js in javascript.body %}
3 |
4 | {% endfor %}
5 | {% endif %}
6 |
--------------------------------------------------------------------------------
/_includes/components/devs.njk:
--------------------------------------------------------------------------------
1 | {% from "macros/site.njk" import dev %}
2 |
3 |
4 | {% for d in devs %}
5 | {{ dev( 'li', '', d.url, 'h2', d.data.title, d.data.submitter ) }}
6 | {% endfor %}
7 |
8 |
--------------------------------------------------------------------------------
/_includes/components/footer.html:
--------------------------------------------------------------------------------
1 |
2 | Devs of Colour is run by a bunch of awesome people who ❤️ the web!
3 | This site’s contents are available under the Creative Commons Attribution-ShareAlike 4.0 International License . Its source code is freely shared under the MIT license .
4 | Edits welcome on GitHub . Hosted on Netlify .
5 | You can also follow us on Twitter for more.
6 |
7 |
--------------------------------------------------------------------------------
/_includes/components/form.njk:
--------------------------------------------------------------------------------
1 | {% from "macros/form.njk" import label, field, confirm, select, textarea, button %}
2 |
3 |
36 |
37 |
38 |
39 |
40 | {{ confirm("Store my contact info for future submissions", "save", { description: "Checking this will trigger your name, email, and privacy preference to be saved locally within this browser when you submit the form. We can then pre-fill this information for you whenever this form is shown." } ) }}
41 |
42 |
43 |
44 |
103 |
--------------------------------------------------------------------------------
/_includes/components/head.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% include "components/meta.njk" %}
5 |
6 |
7 |
8 |
9 | {% set css %}
10 | {% include "assets/css/inline.css" %}
11 | {% endset %}
12 |
13 |
14 | {# commenting out the netlify stuff
15 |
16 |
17 | {% set js %}
18 | {% include "assets/js/inline.js" %}
19 | {% endset %}
20 |
21 | #}
22 |
23 | {% include "components/app.html" %}
24 |
25 |
26 |
27 |
28 |
29 | {# do we want webmentions?
30 |
31 |
32 | #}
33 |
34 | {% if javascript.head %}
35 | {% for js in javascript.head %}
36 |
37 | {% endfor %}
38 | {% endif %}
39 |
40 |
--------------------------------------------------------------------------------
/_includes/components/meta.njk:
--------------------------------------------------------------------------------
1 | {% set page_title %}{{ title or renderData.title or metadata.title }}{% endset %}
2 | {% set page_description %}{{ description or pkg.description }}{% endset %}
3 | {% set canonical %}{{ pkg.homepage }}{{ page.url }}{% endset %}
4 | {% set og_image %}{{ pkg.homepage }}/static/img/og-icon.png{% endset %}
5 |
6 | {{ page_title }}{% if page_title != pkg.title %} ↬ {{ pkg.title }}{% endif %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
46 |
--------------------------------------------------------------------------------
/_includes/components/nav.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% set current_directory = page.url | getDirectory %}
4 | {% for nav in collections.nav | sort(attribute="date") %}
5 | {% set nav_directory = nav.url | getDirectory %}
6 |
7 | {{ nav.data.navtitle }}
8 |
9 | {% endfor %}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/_includes/layouts/base.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% include "components/head.njk" %}
5 |
6 | {% if body_class %}
7 |
8 | {% else %}
9 |
10 | {% endif %}
11 |
12 |
20 |
21 |
22 | {{ layoutContent | safe }}
23 |
24 |
25 |
26 | {% include "components/footer.html" %}
27 |
28 |
29 | {% include "components/body-close.html" %}
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/_includes/layouts/contact.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/base.njk
3 | section: contact
4 | ---
5 | {{ title | widont }}
6 |
7 | {{ layoutContent | safe }}
8 |
9 | {% from "macros/form.njk" import label, field, textarea, button %}
10 |
11 |
--------------------------------------------------------------------------------
/_includes/layouts/dev.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/base.njk
3 | section: devs
4 | body_class: "dev h-entry"
5 | javascript:
6 | head:
7 | - /static/js/indieconfig.js
8 | - /static/js/webaction.js
9 | body:
10 | - /static/js/sharing.js
11 | ---
12 |
13 | {% set absoluteUrl %}{{ metadata.url }}{{ page.url | url }}{% endset %}
14 | {% set id %}{{ page.url | extractID }}{% endset %}
15 |
16 | {{ title | markdownify | widont | safe }}
17 |
18 |
19 | {{ layoutContent | safe }}
20 |
21 |
22 |
23 | Do you want this too? Share it!
24 |
25 |
29 |
30 |
31 | Post on Facebook
32 |
33 |
34 |
35 | Share on LinkedIn
36 |
37 |
38 |
39 |
40 |
41 | Tagged
42 |
43 | {% for tag in tags %}
44 | {{ tag | unslug | fixNames }}
45 | {% endfor %}
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/_includes/layouts/devs.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/base.njk
3 | section: wants
4 | permalink: "/wants/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}{% else %}index{% endif %}.html"
5 | pagination:
6 | data: collections.devs
7 | size: 10
8 | alias: devs
9 | ---
10 |
11 | {% from "macros/ui.njk" import paginate %}
12 |
13 | {{ title | widont }}
14 |
15 | {{ layoutContent | safe }}
16 |
17 | {% if devs.length > 0 %}
18 | {% include "components/devs.njk" %}
19 | {{ paginate( "Page", pagination ) }}
20 | {% else %}
21 | No one has submitted a profile yet. Wanna be the first?
22 | {% endif %}
23 |
--------------------------------------------------------------------------------
/_includes/layouts/home.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/base.njk
3 | section: home
4 | ---
5 |
6 | {{ layoutContent | safe }}
7 |
--------------------------------------------------------------------------------
/_includes/layouts/offline.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/base.njk
3 | section: offline
4 | ---
5 |
6 | {{ title | widont }}
7 |
8 | {{ layoutContent | safe }}
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
31 |
32 |
--------------------------------------------------------------------------------
/_includes/layouts/page.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/base.njk
3 | section: page
4 | ---
5 |
6 | {{ title | widont }}
7 |
8 | {{ layoutContent | safe }}
9 |
--------------------------------------------------------------------------------
/_includes/layouts/submitted.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/base.njk
3 | ---
4 |
5 | {{ title | widont }}
6 |
7 | {{ layoutContent | safe }}
8 |
--------------------------------------------------------------------------------
/_includes/macros/form.njk:
--------------------------------------------------------------------------------
1 | {# ===================
2 | Forms
3 | =================== #}
4 |
5 | {% macro label( text, name ) %}
6 | {{ text | widont }}
7 | {% endmacro %}
8 |
9 | {% macro field( type, name, data ) %}
10 |
11 |
24 | {% if data.description %}
25 |
26 | {{ data.description | widont }}
27 | {% endif %}
28 | {% endmacro %}
29 |
30 | {% macro confirm( text, name, data ) %}
31 |
32 |
40 | {{ text }}
41 |
42 | {% if data.description %}
43 |
44 | {{ data.description | widont }}
45 | {% endif %}
46 | {% endmacro %}
47 |
48 | {% macro select( name, options, data ) %}
49 |
50 |
56 | {% for option in data.options_before %}
57 | {{ option }}
58 | {% endfor %}
59 | {% for option in options %}
60 | {{ option }}
61 | {% endfor %}
62 | {% for option in data.options_after %}
63 | {{ option }}
64 | {% endfor %}
65 |
66 | {% if data.description %}
67 |
68 | {{ data.description | widont }}
69 | {% endif %}
70 | {% endmacro %}
71 |
72 | {% macro textarea( name, data ) %}
73 |
74 |
84 | {% if data.description %}
85 | {{ data.description | widont }}
86 | {% endif %}
87 | {% endmacro %}
88 |
89 | {% macro radios( label, name, options ) %}
90 |
91 | {{ label }}
92 |
118 | {% if data.description %}
119 | {{ data.description | widont }}
120 | {% endif %}
121 |
122 | {% endmacro %}
123 |
124 | {% macro checkboxes( label, name, options ) %}
125 |
126 | {{ label }}
127 |
153 | {% if data.description %}
154 | {{ data.description | widont }}
155 | {% endif %}
156 |
157 | {% endmacro %}
158 |
159 | {% macro button( text ) %}
160 | {{ text }}
161 | {% endmacro %}
162 |
163 |
164 |
165 | {# ===================
166 | UI
167 | =================== #}
168 | {% macro paginate( what, pagination ) %}
169 |
174 | {% endmacro %}
--------------------------------------------------------------------------------
/_includes/macros/site.njk:
--------------------------------------------------------------------------------
1 | {# ========================
2 | Site-specific Macros
3 | ======================== #}
4 |
5 | {% macro dev( block_tag, additional_classes, url, title_tag, title, submitter ) %}
6 | <{{ block_tag }} class="want {{ additional_classes }}">
7 | <{{ title_tag }}>{{ title | markdownify | widont | safe }} {{ title_tag }}>
8 | Submitted by {{ submitter }}
9 | {{ block_tag }}>
10 | {% endmacro %}
--------------------------------------------------------------------------------
/_includes/macros/ui.njk:
--------------------------------------------------------------------------------
1 | {# ===================
2 | UI
3 | =================== #}
4 |
5 | {% macro paginate( what, pagination ) %}
6 |
11 | {% endmacro %}
--------------------------------------------------------------------------------
/admin/config.yml:
--------------------------------------------------------------------------------
1 | backend:
2 | name: github
3 | repo: WebWeWant/webwewant.fyi
4 | branch: master # Branch to update (optional; defaults to master)
5 |
6 | # Uncomment below to enable drafts
7 | # publish_mode: editorial_workflow
8 |
9 | media_folder: "static/img" # Media files will be stored in the repo under images/uploads
10 |
11 | collections:
12 | # Wants
13 | - name: "devs"
14 | label: "Dev"
15 | folder: "devs"
16 | create: true
17 | slug: "{{number}}"
18 | fields:
19 | - { label: "Title", name: "title", widget: "string" }
20 | - { label: "Number", name: "number", widget: "number" }
21 | - { label: "Tags", name: "tags", widget: "list" }
22 | - { label: "Description", name: "body", widget: "markdown" }
23 | # Pages
24 | - name: "pages"
25 | label: "Page"
26 | folder: "pages"
27 | slug: "{{slug}}"
28 | fields:
29 | - { label: "Title", name: "title", widget: "string" }
30 | - { label: "Permalink", name: "permalink", widget: "string" }
31 | - { label: "Navigation Title", name: "navtitle", widget: "string" }
32 | - { label: "Tags", name: "tags", widget: "hidden", default: "nav" }
33 | - { label: "Body", name: "body", widget: "markdown" }
34 |
--------------------------------------------------------------------------------
/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Netlify CMS
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/admin/preview-templates/index.js:
--------------------------------------------------------------------------------
1 | import Post from "/admin/preview-templates/post.js";
2 | import Page from "/admin/preview-templates/page.js";
3 |
4 | // Register the Post component as the preview for entries in the blog collection
5 | CMS.registerPreviewTemplate("blog", Post);
6 | CMS.registerPreviewTemplate("pages", Page);
7 |
8 | CMS.registerPreviewStyle("/_includes/assets/css/inline.css");
9 | // Register any CSS file on the home page as a preview style
10 | fetch("/")
11 | .then(response => response.text())
12 | .then(html => {
13 | const f = document.createElement("html");
14 | f.innerHTML = html;
15 | Array.from(f.getElementsByTagName("link")).forEach(tag => {
16 | if (tag.rel == "stylesheet" && !tag.media) {
17 | CMS.registerPreviewStyle(tag.href);
18 | }
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/admin/preview-templates/page.js:
--------------------------------------------------------------------------------
1 | import htm from "https://unpkg.com/htm?module";
2 |
3 | const html = htm.bind(h);
4 |
5 | // Preview component for a Page
6 | const Page = createClass({
7 | render() {
8 | const entry = this.props.entry;
9 |
10 | return html`
11 |
12 | ${entry.getIn(["data", "title"], null)}
13 |
14 | ${this.props.widgetFor("body")}
15 |
16 | `;
17 | }
18 | });
19 |
20 | export default Page;
21 |
--------------------------------------------------------------------------------
/admin/preview-templates/post.js:
--------------------------------------------------------------------------------
1 | import htm from "https://unpkg.com/htm?module";
2 | import format from "https://unpkg.com/date-fns@2.0.0-alpha.2/esm/format/index.js?module";
3 |
4 | const html = htm.bind(h);
5 |
6 | // Preview component for a Post
7 | const Post = createClass({
8 | render() {
9 | const entry = this.props.entry;
10 |
11 | return html`
12 |
13 |
14 | ${entry.getIn(["data", "title"], null)}
15 |
16 |
17 | ${
19 | format(
20 | entry.getIn(["data", "date"], new Date()),
21 | "DD MMM, yyyy"
22 | )
23 | }
25 | ${" by Author"}
26 |
27 |
28 |
29 | ${entry.getIn(["data", "summary"], "")}
30 |
31 | ${this.props.widgetFor("body")}
32 |
33 | ${
34 | entry.getIn(["data", "tags"], []).map(
35 | tag =>
36 | html`
37 | ${tag}
38 | `
39 | )
40 | }
41 |
42 |
43 |
44 | `;
45 | }
46 | });
47 |
48 | export default Post;
49 |
--------------------------------------------------------------------------------
/devs/devs.json:
--------------------------------------------------------------------------------
1 | {
2 | "layout": "layouts/dev.njk"
3 | }
--------------------------------------------------------------------------------
/devs/tagged.njk:
--------------------------------------------------------------------------------
1 | ---
2 | pagination:
3 | data: collections
4 | size: 1
5 | alias: tag
6 | filter:
7 | - nav
8 | - all
9 | - wants
10 | - posts
11 | - tags
12 | addAllPagesToCollections: true
13 | layout: layouts/base.njk
14 | renderData:
15 | title: Devs Tagged “{{ tag | unslug | fixNames }}”
16 | permalink: /devs/tagged/{{ tag }}/
17 | ---
18 |
19 | Tagged “{{ tag | unslug | fixNames }}”
20 |
21 | {% set devs = collections[ tag ] %}
22 | {% include "components/devs.njk" %}
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "The Web We Want",
3 | "short_name": "WWW.fyi",
4 | "description": "If you build websites, you inevitably run into problems. Maybe there’s no way to achieve an aspect of your design using CSS. Or maybe there’s a device feature you really wish you could tap into using JavaScript. Or perhaps the in-browser DevTools don’t give you a key insight you need to do your job. We want to hear about it!",
5 | "background_color": "#cef6da",
6 | "display": "minimal-ui",
7 | "start_url": "/",
8 | "icons": [{
9 | "src": "/static/img/icon.svg",
10 | "type": "image/svg+xml"
11 | },
12 | {
13 | "src": "/static/img/android-icon-36x36.png",
14 | "sizes": "36x36",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "/static/img/android-icon-48x48.png",
19 | "sizes": "48x48",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "/static/img/android-icon-72x72.png",
24 | "sizes": "72x72",
25 | "type": "image/png"
26 | },
27 | {
28 | "src": "/static/img/android-icon-96x96.png",
29 | "sizes": "96x96",
30 | "type": "image/png"
31 | },
32 | {
33 | "src": "/static/img/android-icon-144x144.png",
34 | "sizes": "144x144",
35 | "type": "image/png"
36 | },
37 | {
38 | "src": "/static/img/android-icon-192x192.png",
39 | "sizes": "192x192",
40 | "type": "image/png"
41 | }
42 | ],
43 | "shortcuts": [{
44 | "name": "Home",
45 | "description": "Go to the Homepage",
46 | "url": "/"
47 | }, {
48 | "name": "Share Your Want",
49 | "description": "Whart do you want to see on the web?",
50 | "url": "/#submit"
51 | },
52 | {
53 | "name": "Wants",
54 | "description": "See a list of all wants",
55 | "url": "/wants/"
56 | },
57 | {
58 | "name": "Events",
59 | "description": "See what events we’ll be discussing wants at",
60 | "url": "/events/"
61 | }
62 | ]
63 | }
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish = "_site"
3 | command = "eleventy"
4 |
5 | [[headers]]
6 | for = "/*"
7 | [headers.values]
8 | X-Content-Type-Options = "nosniff"
9 |
10 | [[headers]]
11 | for = "/"
12 | [headers.values]
13 | X-Frame-Options = "DENY"
14 | X-XSS-Protection = "1; mode=block"
15 | Cache-Control = "no-cache"
16 |
17 | [[headers]]
18 | for = "/*.html"
19 | [headers.values]
20 | Content-Type = "text/html; charset=utf-8"
21 | X-Frame-Options = "DENY"
22 | X-XSS-Protection = "1; mode=block"
23 | Cache-Control = "no-cache"
24 |
25 | [[headers]]
26 | for = "/site.webmanifest"
27 | [headers.values]
28 | Content-Type = "application/manifest+json; charset=utf-8"
29 | Cache-Control = "no-cache"
30 |
31 | [[headers]]
32 | for = "/*.png"
33 | [headers.values]
34 | Cache-Control = "max-age=31536000, immutable"
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devsofcolour",
3 | "title": "Devs of Colour",
4 | "description": "Database of developers of colour of all skill levels, languages, backgrounds.",
5 | "version": "1.0.0",
6 | "scripts": {
7 | "start": "npx eleventy --serve",
8 | "build": "npx eleventy",
9 | "watch": "npx eleventy --watch",
10 | "debug": "DEBUG=* npx eleventy"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/tatianamac/devsofcolour"
15 | },
16 | "author": {
17 | "name": "Aaron Gustafson",
18 | "email": "aaron@easy-designs.net",
19 | "url": "https://www.aaron-gustafson.com/"
20 | },
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/tatianamac/devsofcolour/issues"
24 | },
25 | "homepage": "https://devsofcolour.org",
26 | "devDependencies": {
27 | "@11ty/eleventy": "^0.8.3",
28 | "@11ty/eleventy-plugin-inclusive-language": "^1.0.0",
29 | "dotenv": "^6.2.0",
30 | "lodash": "^4.17.15",
31 | "luxon": "^1.17.1",
32 | "markdown-it": "^9.0.1",
33 | "markdown-it-anchor": "^5.2.4",
34 | "node-fetch": "^2.3.0",
35 | "sanitize-html": "^1.20.0",
36 | "widont": "^0.3.3"
37 | },
38 | "dependencies": {
39 | "@11ty/eleventy-plugin-syntaxhighlight": "^2.0.3",
40 | "clean-css": "^4.2.1",
41 | "html-minifier": "^4.0.0",
42 | "uglify-es": "^3.3.9"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pages/about.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: About us
3 | permalink: /about/
4 | navtitle: About
5 | tags:
6 | - nav
7 | ---
8 |
9 | Something goes here…
10 |
11 | ## Team
12 |
13 | * [Tatiana Mac](https://twitter.com/TatianaTMac) — Project Founder
14 | * [Aaron Gustafson](https://twitter.com/aarongustafson)
15 |
16 | Interested in getting involved? [Reach out!](/contact)
--------------------------------------------------------------------------------
/pages/contact.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/contact.njk
3 | title: Contact Us
4 | description: Have a question? Need help with something? Drop us a line.
5 | date: 2019-01-04T00:00:00.000Z
6 | permalink: /contact/
7 | ---
8 |
9 | Have a question? Need help with something? Drop us a line below.
10 |
11 | *Please note that all fields are required, but we **will not** retain any of the information you submit beyond the time it takes us to address your message.*
--------------------------------------------------------------------------------
/pages/home.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/home.njk
3 | title: Devs of Colour
4 | description: Database of developers of colour of all skill levels, languages, backgrounds.
5 | date: 2019-01-01T00:00:00.000Z
6 | permalink: /
7 | navtitle: Home
8 | tags:
9 | - nav
10 | ---
11 |
12 | Content to come
13 |
--------------------------------------------------------------------------------
/pages/offline.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/offline.njk
3 | title: You’re offline
4 | date: 2019-01-04T00:00:00.000Z
5 | permalink: /offline/
6 | ---
7 |
8 | You appear to be offline. Let us see if we can help.
--------------------------------------------------------------------------------
/pages/pages.json:
--------------------------------------------------------------------------------
1 | {
2 | "layout": "layouts/page.njk"
3 | }
4 |
--------------------------------------------------------------------------------
/pages/submitted.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/submitted.njk
3 | title: Profile Received!
4 | date: 2019-01-04T00:00:00.000Z
5 | permalink: /submitted/
6 | ---
7 |
8 | Thanks!
--------------------------------------------------------------------------------
/pages/thanks.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Message received!
3 | date: 2019-01-04T00:00:00.000Z
4 | permalink: /thanks/
5 | ---
6 |
7 | We got your message and will be in touch shortly if you’ve requested a response.
8 |
--------------------------------------------------------------------------------
/pages/wants.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: layouts/devs.njk
3 | title: The Devs of Colour
4 | description: Get to know the developers of colour.
5 | date: 2019-01-02T00:00:00.000Z
6 | navtitle: Devs
7 | tags:
8 | - nav
9 | ---
10 |
11 | Get to know the {{ collections.devs.length }} devs who are a part of our database. Want to join them? [Submit your profile](/submit)!
12 |
--------------------------------------------------------------------------------
/static/img/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/android-icon-144x144.png
--------------------------------------------------------------------------------
/static/img/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/android-icon-192x192.png
--------------------------------------------------------------------------------
/static/img/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/android-icon-36x36.png
--------------------------------------------------------------------------------
/static/img/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/android-icon-48x48.png
--------------------------------------------------------------------------------
/static/img/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/android-icon-72x72.png
--------------------------------------------------------------------------------
/static/img/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/android-icon-96x96.png
--------------------------------------------------------------------------------
/static/img/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-114x114.png
--------------------------------------------------------------------------------
/static/img/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-120x120.png
--------------------------------------------------------------------------------
/static/img/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-144x144.png
--------------------------------------------------------------------------------
/static/img/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-152x152.png
--------------------------------------------------------------------------------
/static/img/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-180x180.png
--------------------------------------------------------------------------------
/static/img/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-57x57.png
--------------------------------------------------------------------------------
/static/img/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-60x60.png
--------------------------------------------------------------------------------
/static/img/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-72x72.png
--------------------------------------------------------------------------------
/static/img/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-76x76.png
--------------------------------------------------------------------------------
/static/img/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/static/img/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/apple-icon.png
--------------------------------------------------------------------------------
/static/img/droid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/img/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/favicon-16x16.png
--------------------------------------------------------------------------------
/static/img/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/favicon-32x32.png
--------------------------------------------------------------------------------
/static/img/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/favicon-96x96.png
--------------------------------------------------------------------------------
/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/favicon.ico
--------------------------------------------------------------------------------
/static/img/flying-car.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/img/hologram-dash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/img/hologram-folder.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/icon.png
--------------------------------------------------------------------------------
/static/img/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/img/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/ms-icon-144x144.png
--------------------------------------------------------------------------------
/static/img/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/ms-icon-150x150.png
--------------------------------------------------------------------------------
/static/img/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/ms-icon-310x310.png
--------------------------------------------------------------------------------
/static/img/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/ms-icon-70x70.png
--------------------------------------------------------------------------------
/static/img/netlify.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/static/img/og-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/og-icon.png
--------------------------------------------------------------------------------
/static/img/repeating-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/selfdefined/devsofcolour/d558c7af6bff65e3b258f5913d585d767a4c5b5f/static/img/repeating-pattern.png
--------------------------------------------------------------------------------
/static/js/indieconfig.js:
--------------------------------------------------------------------------------
1 | window.loadIndieConfig = (function () {
2 | 'use strict';
3 |
4 | // Indie-Config Loading script
5 | // by Pelle Wessman, voxpelli.com
6 | // MIT-licensed
7 | // http://indiewebcamp.com/indie-config
8 |
9 | var config, configFrame, configTimeout,
10 | callbacks = [],
11 | handleConfig, parseConfig;
12 |
13 | // When the configuration has been loaded – deregister all loading mechanics and call all callbacks
14 | handleConfig = function () {
15 | config = config || {};
16 |
17 | configFrame.parentNode.removeChild(configFrame);
18 | configFrame = undefined;
19 |
20 | window.removeEventListener('message', parseConfig);
21 |
22 | clearTimeout(configTimeout);
23 |
24 | while (callbacks[0]) {
25 | callbacks.shift()(config);
26 | }
27 | };
28 |
29 | // When we receive a message, check if the source is right and try to parse it
30 | parseConfig = function (message) {
31 | var correctSource = (configFrame && message.source === configFrame.contentWindow);
32 |
33 | if (correctSource && config === undefined) {
34 | try {
35 | config = JSON.parse(message.data);
36 | } catch (ignore) {}
37 |
38 | handleConfig();
39 | }
40 | };
41 |
42 | if (!window.navigator.registerProtocolHandler) {
43 | config = {};
44 | }
45 |
46 | return function (callback) {
47 | // If the config is already loaded, call callback right away
48 | if (config) {
49 | callback(config);
50 | return;
51 | }
52 |
53 | // Otherwise add the callback to the queue
54 | callbacks.push(callback);
55 |
56 | // Are we already trying to load the Indie-Config, then wait
57 | if (configFrame) {
58 | return;
59 | }
60 |
61 | // Create the iframe that will load the Indie-Config
62 | configFrame = document.createElement('iframe');
63 | configFrame.src = 'web+action:load';
64 | document.getElementsByTagName('body')[0].appendChild(configFrame);
65 | configFrame.style.display = 'none';
66 |
67 | // Listen for messages so we will catch the Indie-Config message
68 | window.addEventListener('message', parseConfig);
69 |
70 | // And if no such Indie-Config message has been loaded in a while, abort the loading
71 | configTimeout = setTimeout(handleConfig, 3000);
72 | };
73 | }());
--------------------------------------------------------------------------------
/static/js/sharing.js:
--------------------------------------------------------------------------------
1 | /* ! Sharing popup */
2 | (function( window, document ){
3 | 'use strict';
4 |
5 | // Filter older browsers
6 | if ( ! ( 'querySelectorAll' in document ) )
7 | {
8 | return;
9 | }
10 |
11 | // gather the links container
12 | var share_links = document.querySelectorAll('.share, .button--vote'),
13 | watcher_count = share_links.length,
14 | w_threshold = 540,
15 | h_threshold = 340;
16 |
17 | // event handler
18 | function click(e)
19 | {
20 | var target = e.target;
21 |
22 | // target must be an anchor and the inner width threshold must be met
23 | if ( ( target.matches( 'a *' ) || target.matches( '.button' ) ) &&
24 | window.innerWidth >= w_threshold &&
25 | window.innerHeight >= h_threshold )
26 | {
27 | // prevent the default link click
28 | e.preventDefault();
29 |
30 | while ( target.nodeName.toLowerCase() != 'a' )
31 | {
32 | target = target.parentNode;
33 | }
34 |
35 | // open the link in a popup
36 | window.open( target.href, 'share-this', 'height=300,width=500,status=no,toolbar=no' );
37 |
38 | // return
39 | return false;
40 | }
41 | }
42 |
43 | // watcher
44 | while ( watcher_count-- )
45 | {
46 | share_links[watcher_count].addEventListener( 'click', click, false );
47 | }
48 |
49 | }( this, this.document ));
--------------------------------------------------------------------------------
/static/js/webaction.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | var loadingClassRegexp = /(^|\s)indieconfig-loading(\s|$)/;
5 |
6 | var doTheAction = function (indieConfig) {
7 | var href, action, anchors;
8 |
9 | // Don't block the tag anymore as the queued action is now handled
10 | this.className = this.className.replace(loadingClassRegexp, ' ');
11 |
12 | // Pick the correct endpoint for the correct action
13 | action = this.getAttribute('do');
14 |
15 | if ((action === 'reply' || action === 'post') && indieConfig.reply) {
16 | href = indieConfig.reply;
17 | } else if (action === 'like' && indieConfig.like) {
18 | href = indieConfig.like;
19 | } else if (action === 'repost' && indieConfig.repost) {
20 | href = indieConfig.repost;
21 | } else if (action === 'tip' && indieConfig.tip) {
22 | href = indieConfig.tip;
23 | }
24 |
25 | // If no endpoint is found, try the URL of the first a-tag within it
26 | if (!href) {
27 | anchors = this.getElementsByTagName('a');
28 | if (anchors[0]) {
29 | href = anchors[0].href;
30 | }
31 | }
32 |
33 | // We have found an endpoint!
34 | if (href) {
35 | //Resolve a relative target
36 | var target = document.createElement('a');
37 | target.href = this.getAttribute('with');
38 | target = target.href;
39 |
40 | // Insert the target into the endpoint
41 | href = href.replace('{url}', encodeURIComponent(target || window.location.href));
42 |
43 | // And redirect to it
44 | window.location = href;
45 | }
46 | };
47 |
48 | // Event handler for a click on an indie-action tag
49 | var handleTheAction = function (e) {
50 | // Prevent the default of eg. any a-tag fallback within the indie-action tag
51 | e.preventDefault();
52 |
53 | // Make sure this tag hasn't already been queued for the indieconfig-load
54 | if (window.loadIndieConfig && !loadingClassRegexp.test(this.className)) {
55 | this.className += ' indieconfig-loading';
56 | // Set "doTheAction" to be called when the indie-config has been loaded
57 | window.loadIndieConfig(doTheAction.bind(this));
58 | }
59 | };
60 |
61 | // Used to activate all webactions in a context
62 | var activateWebActionElements = function (context) {
63 | var actions = context.querySelectorAll('indie-action'),
64 | i,
65 | length = actions.length;
66 |
67 | for (i = 0; i < length; i++) {
68 | actions[i].addEventListener('click', handleTheAction);
69 | }
70 | };
71 |
72 | window.activateWebActionElements = activateWebActionElements;
73 |
74 | // Once the page is loaded add click event listeners to all indie-action tags
75 | if (document.body) {
76 | activateWebActionElements(document);
77 | } else {
78 | window.addEventListener('DOMContentLoaded', function () {
79 | activateWebActionElements(document);
80 | });
81 | }
82 | }());
--------------------------------------------------------------------------------
/sw.js:
--------------------------------------------------------------------------------
1 | const version = "v1:", // be sure to update
2 |
3 | // Stuff to load on install
4 | offline_page = "/offline/",
5 | preinstall = [
6 | // images
7 | "/static/img/favicon-16x16.png",
8 | "/static/img/favicon-32x32.png",
9 | "/static/img/favicon-96x96.png",
10 | // CSS
11 | // JavaScript
12 | // Offline
13 | offline_page
14 | ],
15 |
16 | // caches
17 | sw_caches = {
18 | static: {
19 | name: `${version}static`
20 | },
21 | pages: {
22 | name: `${version}pages`,
23 | limit: 5
24 | },
25 | other: {
26 | name: `${version}other`,
27 | limit: 5
28 | }
29 | },
30 |
31 | // Never cache
32 | ignore = [
33 | 'thanks',
34 | 'submitted',
35 | 'chrome-extension'
36 | ],
37 |
38 | // How to decide what gets cached and
39 | // what might not be left out
40 | high_priority = [
41 | /webwewant\.fyi/,
42 | /fonts\.googleapis\.com/
43 | ],
44 |
45 | fetch_config = {
46 | images: {
47 | mode: 'no-cors'
48 | }
49 | };
50 |
51 | let slow_connection = false,
52 | save_data;
53 |
54 | if ( 'connection' in navigator )
55 | {
56 | slow_connection = ( navigator.connection.downlink < .5 );
57 | save_data = navigator.connection.saveData;
58 | }
59 | self.addEventListener( "activate", event => {
60 |
61 | // console.log('WORKER: activate event in progress.');
62 |
63 | // clean up stale caches
64 | event.waitUntil(
65 | caches.keys()
66 | .then( keys => {
67 | return Promise.all(
68 | keys
69 | .filter( key => {
70 | return ! key.startsWith( version );
71 | })
72 | .map( key => {
73 | return caches.delete( key );
74 | })
75 | ); // end promise
76 | }) // end then
77 | ); // end event
78 |
79 |
80 | });
81 |
82 | addEventListener("message", messageEvent => {
83 | if (messageEvent.data == "clean up")
84 | {
85 | for ( let key in sw_caches )
86 | {
87 | if ( sw_caches[key].limit != undefined )
88 | {
89 | trimCache( sw_caches[key].name, sw_caches[key].limit );
90 | }
91 | }
92 | }
93 | });
94 |
95 | function trimCache( cache_name, limit )
96 | {
97 | caches.open( cache_name )
98 | .then( cache => {
99 | cache.keys()
100 | .then( items => {
101 | if ( items.length > limit ) {
102 | cache.delete( items[0] )
103 | .then(
104 | trimCache( cache_name, limit)
105 | ); // end delete
106 | } // end if
107 | }); // end keys
108 | }); // end open
109 | }
110 | self.addEventListener( "fetch", event => {
111 |
112 | // console.log( "WORKER: fetch event in progress." );
113 |
114 | const request = event.request,
115 | url = request.url;
116 |
117 | if ( request.method !== "GET" || shouldBeIgnored( url ) )
118 | {
119 | // console.log( "ignoring " + url );
120 | return;
121 | }
122 |
123 | if ( save_data == undefined )
124 | {
125 | save_data = request.headers.get("save-data");
126 | }
127 |
128 | // console.log(request.url, request.headers);
129 |
130 | // JSON & such
131 | if ( /\.json$/.test( url ) ||
132 | /jsonp\=/.test( url ) )
133 | {
134 | event.respondWith(
135 | caches.match( request )
136 | .then( cached_result => {
137 | // cached first
138 | if ( cached_result )
139 | {
140 | // Update the cache in the background, but only if we’re not trying to save data
141 | if ( ! save_data && ! slow_connection )
142 | {
143 | event.waitUntil(
144 | refreshCachedCopy( request, sw_caches.other.name )
145 | );
146 | }
147 | return cached_result;
148 | }
149 | // fallback to network
150 | return fetch( request )
151 | .then( response => {
152 | const copy = response.clone();
153 | event.waitUntil(
154 | saveToCache( sw_caches.other.name, request, copy )
155 | );
156 | return response;
157 | })
158 | // fallback to offline page
159 | .catch(
160 | respondWithServerOffline
161 | );
162 | })
163 | );
164 | }
165 |
166 | // JavaScript
167 | else if ( /\.js$/.test( url ) && isHighPriority( url ) )
168 | {
169 | event.respondWith(
170 | caches.match( request )
171 | .then( cached_result => {
172 | // cached first
173 | if ( cached_result )
174 | {
175 | // Update the cache in the background, but only if we’re not trying to save data
176 | if ( ! save_data && ! slow_connection )
177 | {
178 | event.waitUntil(
179 | refreshCachedCopy( request, sw_caches.static.name )
180 | );
181 | }
182 | return cached_result;
183 | }
184 | // fallback to network
185 | return fetch( request )
186 | .then( response => {
187 | const copy = response.clone();
188 | event.waitUntil(
189 | saveToCache( sw_caches.static.name, request, copy )
190 | );
191 | return response;
192 | })
193 | // fallback to offline page
194 | .catch(
195 | respondWithServerOffline
196 | );
197 | })
198 | );
199 | }
200 |
201 | // Wants
202 | else if ( /wants/.test( url ) )
203 | {
204 | event.respondWith(
205 | fetch( request )
206 | .then( response => {
207 | const copy = response.clone();
208 | event.waitUntil(
209 | saveToCache( sw_caches.pages.name, request, copy )
210 | ); // end waitUntil
211 | return response;
212 | })
213 | // fallback to offline page
214 | .catch(
215 | caches.match( request )
216 | .then( cached_result => {
217 | return cached_result;
218 | })
219 | .catch(
220 | respondWithOfflinePage
221 | )
222 | )
223 | );
224 | }
225 |
226 | // Other HTML
227 | else if ( request.headers.get("Accept").includes("text/html") ||
228 | requestIsLikelyForHTML( url ) )
229 | {
230 | event.respondWith(
231 | // check the cache first
232 | caches.match( request )
233 | .then( cached_result => {
234 | if ( cached_result )
235 | {
236 | // Update the cache in the background, but only if we’re not trying to save data
237 | if ( ! save_data && ! slow_connection )
238 | {
239 | event.waitUntil(
240 | refreshCachedCopy( request, sw_caches.pages.name )
241 | );
242 | }
243 | return cached_result;
244 | }
245 | // fallback to network, but cache the result
246 | return fetch( request )
247 | .then( response => {
248 | const copy = response.clone();
249 | event.waitUntil(
250 | saveToCache( sw_caches.pages.name, request, copy )
251 | ); // end waitUntil
252 | return response;
253 | })
254 | // fallback to offline page
255 | .catch(
256 | respondWithOfflinePage
257 | );
258 | })
259 | );
260 | }
261 |
262 | // images - cache first, then determine if we should request form the network & cache, fallbacks
263 | else if ( request.headers.get("Accept").includes("image") )
264 | {
265 | event.respondWith(
266 | // check the cache first
267 | caches.match( request )
268 | .then( cached_result => {
269 | if ( cached_result )
270 | {
271 | return cached_result;
272 | }
273 |
274 | // high priority imagery
275 | if ( isHighPriority( url ) )
276 | {
277 | return fetch( request, fetch_config.images )
278 | .then( response => {
279 | const copy = response.clone();
280 | event.waitUntil(
281 | saveToCache( sw_caches.static.name, request, copy )
282 | ); // end waitUntil
283 | return response;
284 | });
285 | }
286 | // all others
287 | else
288 | {
289 | // console.log('other images', url);
290 | // save data?
291 | if ( save_data || slow_connection )
292 | {
293 | return new Response( "", {
294 | status: 408,
295 | statusText: "This request was ignored to save data."
296 | });
297 | }
298 |
299 | // normal operation
300 | else
301 | {
302 | // console.log('fetching');
303 | return fetch( request, fetch_config.images )
304 | .then( response => {
305 | const copy = response.clone();
306 | event.waitUntil(
307 | saveToCache( sw_caches.static.name, request, copy )
308 | );
309 | return response;
310 | });
311 | }
312 | }
313 | })
314 | );
315 | }
316 |
317 | // everything else - cache first, then network
318 | else
319 | {
320 | event.respondWith(
321 | // check the cache first
322 | caches.match( request )
323 | .then( cached_result => {
324 | if ( cached_result )
325 | {
326 | return cached_result;
327 | }
328 |
329 | // save data?
330 | if ( save_data || slow_connection )
331 | {
332 | return new Response( "", {
333 | status: 408,
334 | statusText: "This request was ignored to save data."
335 | });
336 | }
337 |
338 | // normal operation
339 | else
340 | {
341 | return fetch( request )
342 | .then( response => {
343 | const copy = response.clone();
344 | if ( isHighPriority( url ) )
345 | {
346 | event.waitUntil(
347 | saveToCache( sw_caches.static.name, request, copy )
348 | );
349 | }
350 | else
351 | {
352 | event.waitUntil(
353 | saveToCache( sw_caches.other.name, request, copy )
354 | );
355 | }
356 | return response;
357 | })
358 | // fallback to offline image
359 | .catch(
360 | respondWithServerOffline
361 | );
362 | }
363 | })
364 | );
365 | }
366 |
367 | });
368 |
369 | self.addEventListener( "install", function( event ){
370 |
371 | // console.log( "WORKER: install event in progress." );
372 |
373 | // immediately take over
374 | self.skipWaiting();
375 |
376 | event.waitUntil(
377 | caches.open( sw_caches.static.name )
378 | .then(function( cache ){
379 | return cache.addAll( preinstall );
380 | })
381 | );
382 |
383 | });
384 |
385 | function saveToCache( cache_name, request, response )
386 | {
387 | // console.log( 'saving a copy of', request.url );
388 | caches.open( cache_name )
389 | .then( cache => {
390 | return cache.put( request, response );
391 | });
392 | }
393 |
394 | function refreshCachedCopy( the_request, cache_name )
395 | {
396 | fetch( the_request )
397 | .then( the_response => {
398 | caches.open( cache_name )
399 | .then( the_cache => {
400 | return the_cache.put( the_request, the_response );
401 | });
402 | });
403 | }
404 |
405 | function shouldBeIgnored( url )
406 | {
407 | let i = ignore.length;
408 | while( i-- )
409 | {
410 | if ( url.indexOf( ignore[i] ) > -1 )
411 | {
412 | // console.log( "found", ignore[i], 'in', url );
413 | return true;
414 | }
415 | }
416 | return false;
417 | }
418 |
419 | function isHighPriority( url )
420 | {
421 | let i = high_priority.length;
422 | while ( i-- )
423 | {
424 | if ( high_priority[i].test( url ) )
425 | {
426 | return true;
427 | }
428 | }
429 | return false;
430 | }
431 |
432 | function respondWithOfflinePage()
433 | {
434 | return caches.match( offline_page )
435 | .catch(
436 | respondWithServerOffline
437 | );
438 | }
439 |
440 | function respondWithServerOffline(){
441 | return new Response( "", {
442 | status: 408,
443 | statusText: "The server appears to be offline."
444 | });
445 | }
446 |
447 | function requestIsLikelyForHTML( url )
448 | {
449 | const final_segment = url.split("/").pop();
450 | if ( final_segment == "" ||
451 | /.+\.html$/.test( final_segment ) ||
452 | ! (/\..+$/.test( final_segment ) ) )
453 | {
454 | return true;
455 | }
456 | return false;
457 | }
--------------------------------------------------------------------------------