├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── apiary.js ├── examples ├── group.html ├── highlight.css └── user.html ├── logo.svg └── w3c.json /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | Please be nice. 3 | 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright © 2015–2017 [World Wide Web Consortium](http://www.w3.org/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “software”), to deal in 6 | the software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the software, and to permit persons to whom the software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the software. 10 | 11 | **The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness 12 | for a particular purpose and noninfringement. 13 | In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, 14 | arising from, out of or in connection with the software or the use or other dealings in the software.** 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apiary 2 | 3 | ![Logo](https://w3c.github.io/apiary/logo.svg) 4 | 5 | Apiary is a simple JavaScript library to leverage the W3C API. 6 | This library is intended to be used from W3C pages: group pages, personal pages, etc. 7 | With Apiary, you can inject data that is retrieved using the W3C API, in a declarative way using *placeholders*. 8 | Examples are also provided. 9 | 10 | Refer to [the W3C API](https://github.com/w3c/w3c-api) and its documentation for details 11 | \[[overview](https://w3c.github.io/w3c-api/); [reference](https://api.w3.org/doc)\]. 12 | 13 | ## Live examples 14 | 15 | * [Group page](https://w3c.github.io/apiary/examples/group.html) 16 | * [User page](https://w3c.github.io/apiary/examples/user.html) 17 | 18 | ## Getting started 19 | 20 | ### Include the library 21 | 22 | Include [Apiary](apiary.js) in your page: 23 | ```html 24 | 25 | ``` 26 | 27 | ### Specify an entity ID 28 | 29 | Specify the ID of the *entity* you want, adding a `data-apiary-*` attribute to a container element, eg: 30 | ```html 31 |
32 | ``` 33 | 34 | ### Add placeholders 35 | 36 | Finally, add *placeholders* wherever you want real data about that *entity*, eg: 37 | ```html 38 | The chairs of this group are: . 39 | ``` 40 | 41 | ## Reference 42 | 43 | The container element should have *one* of these *data-apiary-** attributes, and its value should be a valid ID: 44 | * `data-apiary-group` 45 | * `data-apiary-user` (use the **user hash**) 46 | 47 | A placeholder is any element with a `data-apiary` attribute. 48 | Bear in mind that a new chunk of DOM will be inserted there; whatever that placeholder contains will be lost. 49 | We recommend that you have something in there giving users a hint that data is being loaded dynamically. 50 | For example: 51 | ```html 52 |
[Loading…]
53 | ``` 54 | 55 | For consistency (and to adhere to the [POLA](https://en.wikipedia.org/wiki/Principle_of_least_astonishment)), 56 | the suffix part of these placeholders is equal to [the object keys returned by the API](https://api.w3.org/doc). 57 | 58 | The additional class `apiary` in the example files is ignored by Apiary itself but you may wish to include it anyway to all placeholders in your documents for easier CSS styling. 59 | 60 | ## Credits 61 | 62 | Copyright © 2023 [World Wide Web Consortium](https://www.w3.org/) 63 | 64 | This project is licensed [under the terms of the MIT license](LICENSE.md). 65 | -------------------------------------------------------------------------------- /apiary.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Simple JavaScript library to leverage the W3C API. 5 | * 6 | * @namespace Apiary 7 | */ 8 | (function(window) { 9 | 10 | // Pseudo-constants: 11 | var VERSION = '2.0.2'; 12 | var BASE_URL = 'https://api.w3.org/'; 13 | var USER_PROFILE_URL = 'https://www.w3.org/users/'; 14 | var APIARY_ATTRIBUTE = 'data-apiary'; 15 | var APIARY_SELECTOR = `[${APIARY_ATTRIBUTE}]`; 16 | var TYPE_GROUP_PAGE = 1; 17 | var TYPE_USER_PAGE = 2; 18 | var MODE_DEBUG = 'debug'; 19 | var MODE_PRODUCTION = 'production'; 20 | var PHOTO_VALUE = { 21 | large: 2, 22 | thumbnail: 1, 23 | tiny: 0 24 | }; 25 | 26 | // “Global” variables: 27 | 28 | /** 29 | * Type of page; one of TYPE_GROUP_PAGE or TYPE_USER_PAGE. 30 | * 31 | * @alias type 32 | * @memberOf Apiary 33 | */ 34 | var type; 35 | 36 | /** 37 | * ID of the entity being used on the page. 38 | * 39 | * @alias id 40 | * @memberOf Apiary 41 | */ 42 | var id; 43 | 44 | /** 45 | * “Mode” (either “debug” or “production”; the latter by default). 46 | * 47 | * @alias mode 48 | * @memberOf Apiary 49 | */ 50 | var mode = MODE_PRODUCTION; 51 | 52 | /** 53 | * Dictionary of placeholders found on the page, and all DOM elements associated to each one of them. 54 | * 55 | * @alias placeholders 56 | * @memberOf Apiary 57 | */ 58 | var placeholders = {}; 59 | 60 | /** 61 | * Simple cache of HTTP calls to the API, to avoid redundancy and save on requests. 62 | * 63 | * @alias cache 64 | * @memberOf Apiary 65 | */ 66 | var cache = {}; 67 | 68 | /** 69 | * Main function, invoked once after the document is completely loaded. 70 | * 71 | * @alias process 72 | * @memberOf Apiary 73 | */ 74 | var process = function() { 75 | if (window.removeEventListener) 76 | window.removeEventListener('load', process); 77 | else if (window.detachEvent) 78 | window.detachEvent('onload', process); 79 | console.log(`Apiary version ${VERSION}`); 80 | inferTypeAndId(); 81 | if (type && id) { 82 | findPlaceholders(); 83 | getDataForType(); 84 | } else { 85 | window.alert('Apiary ' + VERSION + '\n' + 86 | 'ERROR: could not get all necessary metadata.\n' + 87 | 'type: “' + type + '”\n' + 88 | 'id: “' + id + '”'); 89 | } 90 | }; 91 | 92 | /** 93 | * Infer the type of page (group, user…) and the ID of the corresponding entity. 94 | * 95 | * After this function is done, variables type and id should have their right values set. 96 | * 97 | * @alias inferTypeAndId 98 | * @memberOf Apiary 99 | */ 100 | var inferTypeAndId = function() { 101 | if (document.querySelectorAll('[data-apiary-group]').length > 0) { 102 | type = TYPE_GROUP_PAGE; 103 | id = document.querySelectorAll('[data-apiary-group]')[0].getAttribute('data-apiary-group'); 104 | } else if (document.querySelectorAll('[data-apiary-user]').length > 0) { 105 | type = TYPE_USER_PAGE; 106 | id = document.querySelectorAll('[data-apiary-user]')[0].getAttribute('data-apiary-user'); 107 | } 108 | if (1 === document.querySelectorAll('html[data-apiary-mode]').length) { 109 | mode = document.querySelectorAll('html[data-apiary-mode]')[0].getAttribute('data-apiary-mode'); 110 | } 111 | }; 112 | 113 | /** 114 | * Traverse the DOM in search of all elements with class apiary-*. 115 | * 116 | * After this function is done, placeholders should be an object containing all keys found in the DOM; 117 | * and for every key, an array of all elements mentioning that key. 118 | * 119 | * @example 120 | * { 121 | * name: [ 122 | * element, 123 | * <h1> element 124 | * ], 125 | * lead: [<div> element], 126 | * groups: [<div> element] 127 | * } 128 | * 129 | * @alias findPlaceholders 130 | * @memberOf Apiary 131 | */ 132 | var findPlaceholders = function() { 133 | var candidates = document.querySelectorAll(APIARY_SELECTOR); 134 | var expression; 135 | for (var c = 0; c < candidates.length; c ++) { 136 | expression = candidates[c].getAttribute(APIARY_ATTRIBUTE); 137 | if (!placeholders[expression]) 138 | placeholders[expression] = []; 139 | placeholders[expression].push(candidates[c]); 140 | } 141 | if (MODE_DEBUG === mode) 142 | console.debug(`placeholders:\n${JSON.stringify(placeholders)}`); 143 | }; 144 | 145 | /** 146 | * Get basic data for a particular entity from the W3C API, given a type of item and its value. 147 | * 148 | * @alias getDataForType 149 | * @memberOf Apiary 150 | */ 151 | var getDataForType = function() { 152 | if (Object.keys(placeholders).length > 0) { 153 | if (TYPE_GROUP_PAGE === type) { 154 | get(BASE_URL + 'groups/' + id); 155 | } else if (TYPE_USER_PAGE === type) { 156 | get(BASE_URL + 'users/' + id); 157 | } 158 | } 159 | }; 160 | 161 | /** 162 | * Crawl the API dynamically, traversing segments in placeholders. 163 | * 164 | * @param {Object} json JSON coming from an API call. 165 | * 166 | * @alias crawl 167 | * @memberOf Apiary 168 | */ 169 | var crawl = function(json) { 170 | var i, keys, key, prefix, rest; 171 | keys = Object.keys(placeholders); 172 | for (key in keys) { 173 | i = keys[key]; 174 | if (json.hasOwnProperty(i)) { 175 | if ('object' === typeof json[i] && 1 === Object.keys(json[i]).length && json[i].hasOwnProperty('href')) { 176 | get(json[i].href); 177 | } else { 178 | injectValues(i, json[i]); 179 | } 180 | } else if (i.indexOf(' ') > -1) { 181 | prefix = i.substr(0, i.indexOf(' ')); 182 | rest = i.substr(i.indexOf(' ') + 1); 183 | if (MODE_DEBUG === mode) 184 | console.debug(i, prefix, rest); 185 | if (json.hasOwnProperty(prefix)) { 186 | if ('object' === typeof json[prefix] && 1 === Object.keys(json[prefix]).length && json[prefix].hasOwnProperty('href')) 187 | get(json[prefix].href); 188 | else 189 | injectValues(i, json[prefix], rest); 190 | } 191 | } 192 | } 193 | }; 194 | 195 | var interpolateString = function(expression) { 196 | const expregex = /\$\{([^\}]+)\}/g; 197 | var exp, result = expression; 198 | while (exp = expregex.exec(expression)) 199 | if (this.hasOwnProperty(exp[1])) 200 | result = result.replace(exp[0], this[exp[1]]); 201 | return result; 202 | }; 203 | 204 | var escapeHTML = function(string) { 205 | const tags = /<[^>]*>/g, 206 | ampersands = /&/g, 207 | dquotes = /'/g, 208 | squotes = /"/g; 209 | return string.replace(tags, '').replace(ampersands, '&').replace(dquotes, '"').replace(squotes, '''); 210 | }; 211 | 212 | var formatEntity = function(entity, expression) { 213 | var result; 214 | // @TODO: get rid of these special checks when there's a smarter algorithm for hyperlinks. 215 | if (expression) { 216 | result = '<li>' + interpolateString.call(entity, expression) + '</li>'; 217 | } else if (entity.hasOwnProperty('_links') && entity._links.hasOwnProperty('homepage') && 218 | entity._links.homepage.hasOwnProperty('href') && entity.hasOwnProperty('name')) { 219 | // It's a group. 220 | result = '<li><a href="' + entity._links.homepage.href + '">' + entity.name + '</a></li>'; 221 | } else if (entity.hasOwnProperty('discr') && 'user' === entity.discr && 222 | entity.hasOwnProperty('id') && entity.hasOwnProperty('name')) { 223 | // It's a user. 224 | result = '<li><a href="' + USER_PROFILE_URL + entity.id + '">' + (entity['work-title'] ? entity['work-title'] + ' ' : '') + entity.name + '</a></li>'; 225 | } else if (entity.hasOwnProperty('shortlink') && entity.hasOwnProperty('title')) { 226 | // Spec: 227 | result = '<li><a href="' + entity.shortlink; 228 | result += (entity.description ? '" title="' + escapeHTML(entity.description) : ''); 229 | result += '">' + entity.title; 230 | result += (entity.shortname ? ' (<code>' + entity.shortname + '</code>)' : ''); 231 | result += '</a></li>'; 232 | } else if (entity.hasOwnProperty('name')) { 233 | result = '<li>' + entity.name + '</li>'; 234 | } else if (entity.hasOwnProperty('title')) { 235 | result = '<li>' + entity.title + '</li>'; 236 | } else if (entity.hasOwnProperty('href') && entity.hasOwnProperty('title')) { 237 | result = '<li><a href="' + entity.href + '">' + entity.title + '</a></li>'; 238 | } else if (entity.hasOwnProperty('href') && entity.hasOwnProperty('name')) { 239 | result = '<a href="' + entity.href + '">' + entity.name + '</a>'; 240 | } 241 | return result; 242 | }; 243 | 244 | /* 245 | * Function: discr="function", name, staging, is-closed, _links.{lead.title, homepage.href?}. 246 | * User: discr="user", family, given, id, name, work-title?, _links.self.href. 247 | * Participation: created, individual, _links.(group.(href, title), self.href, user.(href, title)?, organization.(href, title)?). 248 | * Group: discr="w3cgroup", description, id, name, type. 249 | * Affiliation: discr="affiliation"|"organization", id, name. 250 | * Spec: shortname, title, description?. 251 | * Version: {_embedded.versions}[date, status, _links.self.href]. 252 | * Service: link, type, shortdesc?. 253 | * Charter: start, end, initial-end. 254 | * 255 | * f=all: functions. 256 | * g=all: groups. 257 | * s=all: specs. 258 | * a=all: affiliations. 259 | * f=6823109: users, services. 260 | * g=68239: users x 3, services, specs, charters, participations. 261 | * g=46300&c=155: (none). 262 | * s=dwbp: versions. 263 | * s=2dcontext&v=20110525: groups, users, versions x 2. 264 | * u=ggdj8tciu9kwwc4o4ww888ggkwok0c8: participations, groups, specs, affiliations. 265 | * x=1913: groups. 266 | * p=1503: users. 267 | * a=52794: users, groups. 268 | */ 269 | 270 | /** 271 | * @TODO 272 | */ 273 | 274 | var renderItem = function(entity, type) { 275 | if (!entity) 276 | return window.alert('Error: tried to render an undefined item') 277 | var result; 278 | if ('function' === entity.discr) { 279 | // Function: 280 | result = `<li class="list-group-item"> 281 | <a href="${buildLink(entity._links.self.href)}"> 282 | ${entity.name}<span class="suffix">, led by ${entity._links.lead.title}</span> 283 | </a> 284 | </li>`; 285 | } else if ('user' === entity.discr) { 286 | // User: 287 | var prefix = entity['work-title'] ? `<span class="suffix">, ${entity['work-title']}` : ''; 288 | result = `<li class="list-group-item">\ 289 | <a href="${buildLink(entity._links.self.href)}">\ 290 | ${entity.name}${prefix}\ 291 | </a>\ 292 | </li>`; 293 | } else if (entity.hasOwnProperty('created') && entity.hasOwnProperty('individual')) { 294 | // Participation: 295 | var label; 296 | if (TYPE_GROUP === type) { 297 | // We're interested in organisations and users: 298 | if (entity.individual) 299 | // Person: 300 | label = `${entity._links.user.title} <span class="suffix">(individual)</span>`; 301 | else 302 | // Organisation: 303 | label = `${entity._links.organization.title} <span class="suffix">(organization)</span>`; 304 | } else 305 | // TYPE_USER === type || TYPE_AFFILIATION === type; we're interested in groups: 306 | label = entity._links.group.title; 307 | result = `<li class="list-group-item">\ 308 | <a href="${buildLink(entity._links.self.href)}">\ 309 | ${label}\ 310 | </a>\ 311 | </li>`; 312 | } else if ('w3cgroup' === entity.discr) { 313 | // Group: 314 | var descr = entity.description ? ` title="${escapeHTML(entity.description)}"` : '', 315 | type = ''; 316 | result = `<li class="list-group-item">\ 317 | <a${descr} href="${buildLink(entity.id, 'group')}">\ 318 | ${entity.name}${type}\ 319 | </a>\ 320 | </li>`; 321 | } else if (entity.hasOwnProperty('discr') && ('affiliation' === entity.discr || 'organization' === entity.discr)) { 322 | // Affiliation: 323 | result = `<li class="list-group-item">\ 324 | <a href="${buildLink(entity.id, 'affiliation')}">\ 325 | ${entity.name}\ 326 | </a>\ 327 | </li>`; 328 | } else if (entity.hasOwnProperty('shortname') && entity.hasOwnProperty('title')) { 329 | // Spec: 330 | var descr = entity.description ? ` title="${escapeHTML(entity.description)}"` : ''; 331 | result = `<li class="list-group-item">\ 332 | <a${descr} href="${buildLink(entity.shortname, 'spec')}">\ 333 | ${entity.title} <span class="suffix">(<code>${entity.shortname}</code>)</suffix>\ 334 | </a>\ 335 | </li>`; 336 | } else if (entity.hasOwnProperty('date') && entity.hasOwnProperty('status')) { 337 | // Version: 338 | result = `<li class="list-group-item">\ 339 | <a href="${buildLink(entity._links.self.href)}">\ 340 | ${entity.date} <span class="suffix">(${entity.status})</suffix>\ 341 | </a>\ 342 | </li>`; 343 | } else if (entity.hasOwnProperty('link') && entity.hasOwnProperty('type')) { 344 | // Service: 345 | if ('lists' === entity.type && entity.hasOwnProperty('shortdesc')) { 346 | // Mailing list: 347 | result = `<li class="list-group-item"> 348 | <a href="${buildLink(entity._links.self.href)}"> 349 | <code>${entity.shortdesc}</code> 350 | <span class="suffix">(mailing list)</span> 351 | </a> 352 | </li>`; 353 | } else if ('blog' === entity.type && entity.hasOwnProperty('shortdesc')) { 354 | // Blog: 355 | result = `<li class="list-group-item"> 356 | <a href="${buildLink(entity._links.self.href)}"> 357 | ${entity.shortdesc} 358 | <span class="suffix">(blog)</span> 359 | </a> 360 | </li>`; 361 | } else if ('tracker' === entity.type || 'repository' === entity.type || 'wiki' === entity.type || 'chat' === entity.type) { 362 | // Tracker, repo, wiki or chat: 363 | result = `<li class="list-group-item"> 364 | <a href="${buildLink(entity._links.self.href)}"> 365 | <code>${normaliseURI(entity.link)}</code> 366 | <span class="suffix">(${entity.type})</span> 367 | </a> 368 | </li>`; 369 | } else if ('rss' === entity.type) { 370 | // RSS: 371 | result = `<li class="list-group-item"> 372 | <a href="${buildLink(entity._links.self.href)}"> 373 | <code>${normaliseURI(entity.link)}</code> 374 | <span class="suffix">(RSS)</span> 375 | </a> 376 | </li>`; 377 | } else { 378 | result = `<li class="list-group-item">[Unknown type of service]</li>\n`; 379 | } 380 | } else if (entity.hasOwnProperty('start') && entity.hasOwnProperty('end')) { 381 | // Charter: 382 | result = `<li class="list-group-item">\ 383 | <a href="${buildLink(entity._links.self.href)}">\ 384 | ${entity.start} – ${entity.end}\ 385 | </a>\ 386 | </li>`; 387 | } else 388 | return '<li class="list-group-item">[Type of item not supported yet]</li>\n'; 389 | return result; 390 | }; 391 | 392 | /** 393 | * Inject values retrieved from the API into the relevant elements of the DOM. 394 | * 395 | * @param {String} key ID of the placeholder. 396 | * @param {Object} value actual value for that piece of data. 397 | * @param {Array} expression list of fields to use (optional). 398 | * 399 | * @alias injectValues 400 | * @memberOf Apiary 401 | */ 402 | var injectValues = function(key, value, expression) { 403 | if (MODE_DEBUG === mode) 404 | console.debug(`injectValues:\n${JSON.stringify(key)}\n${JSON.stringify(value)}\n${JSON.stringify(expression)}`); 405 | var chunk; 406 | if ('string' === typeof value || 'number' === typeof value) { 407 | chunk = String(value); 408 | } else if (value instanceof Array) { 409 | chunk = getLargestPhoto(value); 410 | if (!chunk) { 411 | chunk = '<ul>'; 412 | for (var i = 0; i < value.length; i ++) 413 | chunk += formatEntity(value[i], expression); 414 | chunk += '</ul>'; 415 | } 416 | } else if ('object' === typeof value) { 417 | chunk = formatEntity(value, expression); 418 | } 419 | for (var i in placeholders[key]) { 420 | placeholders[key][i].innerHTML = chunk; 421 | placeholders[key][i].classList.add('apiary-done'); 422 | } 423 | delete placeholders[key]; 424 | }; 425 | 426 | /** 427 | * GET data from the API, using the API key, and process the flattened version. 428 | * 429 | * @param {String} url target URL, including base URL and parameters, but not an API key. 430 | * 431 | * @alias get 432 | * @memberOf Apiary 433 | */ 434 | var get = function(url) { 435 | if (MODE_DEBUG === mode) 436 | console.debug('get:\n' + JSON.stringify(url)); 437 | var newUrl = url; 438 | if (-1 === newUrl.indexOf('?')) { 439 | newUrl += '?embed=true'; 440 | } else { 441 | newUrl += '&embed=true'; 442 | } 443 | if (cache.hasOwnProperty(newUrl)) { 444 | crawl(cache[newUrl]); 445 | } else { 446 | var xhr = new XMLHttpRequest(); 447 | xhr.open('GET', newUrl); 448 | xhr.addEventListener('loadend', function(event) { 449 | var result = JSON.parse(xhr.response); 450 | var i, j; 451 | for (i of ['_links', '_embedded']) { 452 | if (result.hasOwnProperty(i)) { 453 | for (j in result[i]) { 454 | if (result[i].hasOwnProperty(j)) { 455 | result[j] = result[i][j]; 456 | } 457 | } 458 | delete result[i]; 459 | } 460 | } 461 | cache[newUrl] = result; 462 | crawl(result); 463 | }); 464 | xhr.send(); 465 | } 466 | }; 467 | 468 | /** 469 | * Find the largest photo available from an array of them, and return an IMG element. 470 | * 471 | * @param {Array} data list of photos provided. 472 | * @returns {String} chunk of text corresponding to a new <code><img></code> node with the photo. 473 | * 474 | * @alias getLargestPhoto 475 | * @memberOf Apiary 476 | */ 477 | var getLargestPhoto = function(data) { 478 | var largest, result; 479 | if (data && data.length > 0) { 480 | for (var i = 0; i < data.length; i ++) { 481 | if (data[i].href && data[i].name && (!largest || PHOTO_VALUE[data[i].name] > PHOTO_VALUE[largest.name])) { 482 | largest = data[i]; 483 | } 484 | } 485 | if (largest) { 486 | result = '<img alt="Portrait" src="' + largest.href + '">'; 487 | } 488 | } 489 | return result; 490 | }; 491 | 492 | // Process stuff! 493 | if (window.addEventListener) 494 | window.addEventListener('load', process); 495 | else if (window.attachEvent) 496 | window.attachEvent('onload', process); 497 | 498 | })(window); 499 | -------------------------------------------------------------------------------- /examples/group.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | 3 | <html lang="en-GB" data-apiary-mode="debug"> 4 | 5 | <head> 6 | <meta charset="utf-8"> 7 | <title data-apiary="name">[Loading…] 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | [Loading…] 21 |
22 | [Loading…] 23 |

24 |

25 | Chartered 26 |

27 |

[Loading…]

28 |

Chairs

29 |
[Loading…]
30 |

Team contacts

31 |
[Loading…]
32 |

Specs

33 |

Hover over specs to view descriptions.

34 |
[Loading…]
35 |

Members

36 |
[Loading…]
37 |

Participants

38 |
[Loading…]
39 |
40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/highlight.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | box-sizing: border-box; 4 | margin: 0; 5 | border: 0; 6 | padding: 0; 7 | font-family: 'Lucida Sans Unicode', 'Lucida Grande', 'Trebuchet MS', Helvetica, Verdana, Geneva, Calibri, sans-serif; 8 | } 9 | 10 | body > div > * { 11 | margin: 0; 12 | border: 0; 13 | padding: 0.25em 8rem; 14 | } 15 | 16 | code { 17 | font-family: monospace; 18 | } 19 | 20 | [data-apiary] { 21 | background-color: #ffe0e0; 22 | } 23 | 24 | .apiary-done { 25 | background-color: #e0ffe0; 26 | } 27 | -------------------------------------------------------------------------------- /examples/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [Loading…] 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 | [Loading…], 21 | [Loading…] 22 | [Loading…] 23 |

24 |
[Loading…]
25 |

Specs contributed to

26 |

Hover over specs to view descriptions.

27 |
[Loading…]
28 |

Member of groups

29 |
[Loading…]
30 |
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 42 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 85 | 101 | 117 | 133 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "contacts": ["deniak", "jean-gui", "vivienlacourba"], 3 | "repo-type": "tool" 4 | } 5 | --------------------------------------------------------------------------------