Added a dark mode color scheme.
[WebKit-https.git] / Websites / webkit.org / wp-content / themes / webkit / status.php
1 <?php
2 /**
3  * Template Name: Status Page
4  **/
5 ?>
6 <?php get_header(); ?>
7 <script>
8 function xhrPromise(url) {
9     return new Promise(function(resolve, reject) {
10         var xhrRequest = new XMLHttpRequest();
11         xhrRequest.open('GET', url, true);
12         xhrRequest.responseType = "json";
13
14         xhrRequest.onload = function() {
15             if (xhrRequest.status == 200) {
16                 if (xhrRequest.response) {
17                     resolve(xhrRequest.response);
18                 } else {
19                     reject({ request: xhrRequest, url:url});
20                 }
21             } else {
22                 reject({ request: xhrRequest, url:url});
23             }
24         };
25         xhrRequest.onerror = function() {
26             reject({ request: xhrRequest, url:url});
27         };
28         xhrRequest.send();
29     });
30 }
31 var origin = new URL("https://<?php echo strpos(WP_HOST, "webkit.org") !== false ? "svn.webkit.org" : WP_HOST; ?>/");
32 var loadJavaScriptCoreFeatures = xhrPromise(new URL("/repository/webkit/trunk/Source/JavaScriptCore/features.json", origin));
33 var loadWebCoreFeatures = xhrPromise(new URL("/repository/webkit/trunk/Source/WebCore/features.json", origin));
34 </script>
35
36 <style>
37
38 :root {
39     --feature-rule-color: hsl(0, 0%, 89.4%);
40     --status-color: hsl(0, 0%, 60%);
41     --supported-color: hsl(100, 100%, 30%);
42     --supported-in-preview-color: hsl(275.4, 77.7%, 35.1%);
43     --in-development-color: hsl(24.5, 91.3%, 50.6%);
44     --no-active-development-color: hsl(240, 60.6%, 59.2%);
45     --partially-supported-color: hsl(180, 25%, 43.9%);
46     --prototyping-color: hsl(211.3, 100%, 50%);
47     --under-consideration-color: hsl(5.9, 40.2%, 60%);
48 }
49
50 @media(prefers-color-scheme:dark) {
51     :root {
52         --feature-rule-color: hsl(0, 0%, 20%);
53         --status-color: hsl(0, 0%, 51%);
54         --supported-color: hsl(79.5, 45.3%, 52%);
55         --supported-in-preview-color: hsl(276.7, 36.3%, 51.4%);
56         --in-development-color: hsl(24.5, 91.3%, 50.6%);
57         --no-active-development-color: hsl(240, 60.6%, 59.2%);
58         --partially-supported-color: hsl(180, 30%, 52%);
59         --prototyping-color: hsl(211.3, 100%, 50%);
60         --under-consideration-color: hsl(0, 35%, 61%);
61     }
62 }
63
64 .page h1 {
65     font-size: 4.2rem;
66     font-weight: 500;
67     line-height: 6rem;
68     margin: 3rem auto;
69     width: 100%;
70     text-align: center;
71 }
72
73 .page h1 a {
74     color: inherit;
75 }
76
77 .feature-status-page {
78     padding-bottom: 3rem;
79 }
80
81 .feature-status-page p {
82     max-width: 920px;
83     margin: 0 auto 3rem;
84 }
85
86 .feature-filters {
87     background-color: hsl(0, 0%, 0%);
88     background-color: var(--figure-mattewhite-background-color);
89     width: 100vw;
90     left: 50%;
91     position: relative;
92     transform: translate(-50vw, 0);
93     box-sizing: border-box;
94     margin-bottom: 3rem;
95     border: 1px solid hsl(0, 0%, 90.6%);
96     border-color: var(--article-border-color);
97     border-left: none;
98     border-right: none;
99 }
100
101 .feature-filters form {
102     max-width: 920px;
103     margin: 0 auto 0;
104     position: relative;
105     top: 0;
106 }
107 /*.feature-filters form {
108     padding-top: 3rem;
109     padding-bottom: 3rem;
110 }*/
111
112 .feature-filters .search-input {
113     background-repeat: no-repeat;
114     background-position-x: 0.5rem;
115     background-position-y: 1rem;
116     background-size: 2rem;
117     padding: 1rem;
118     padding-left: 3rem;
119     padding-right: 8.5rem;
120     font-size: 2rem;
121     width: 100%;
122     margin-top: 0rem;
123     margin-bottom: 0rem;
124     box-sizing: border-box;
125     border-color: transparent;
126 }
127
128 .feature-filters .filters-toggle-button {
129     background-repeat: no-repeat;
130     background-size: 2rem;
131     background-position: right;
132     background-filter: lightness(2);
133     position: absolute;
134     padding-right: 2.5rem;
135     right: 1rem;
136     top: 1rem;
137     border: none;
138     color: hsl(240, 2.3%, 56.7%);
139 }
140
141 .feature-filters .filters-toggle-button:hover {
142     filter: brightness(0);
143 }
144
145 .feature-filters li {
146     display: inline-block;
147 }
148
149 .feature-status label,
150 .feature-filters label {
151     display: table-cell;
152     padding: 0.5rem 1rem;
153     border-style: solid;
154     border-width: 1px;
155     border-radius: 3px;
156     cursor: pointer;
157     float: right;
158     line-height: 1;
159     font-size: 1.6rem;
160 }
161
162 .status-filters label {
163     margin-left: 1rem;
164     margin-bottom: 1rem;
165 }
166
167 .feature-filters label {
168     float: none;
169     display: inline-block;
170 }
171
172 .status-filters {
173     list-style: none;
174     display: none;
175     text-align: center;
176     margin-top: 1rem;
177     margin-bottom: 0.5rem;
178 }
179
180 #feature-filters.opened {
181     margin-top: 1.5rem;
182 }
183
184 #feature-filters.opened .status-filters {
185     display: block;
186 }
187 #feature-filters.opened .search-input {
188     border-color: hsl(0, 0%, 83.9%);
189     border-color: var(--input-border-color);
190 }
191
192 .filter-toggle:checked + .filter-status {
193     color: hsl(240, 1.3%, 84.5%);
194     color: var(--text-color);
195 }
196
197 .feature-filters label > input {
198     position: relative;
199     top: -1px;
200 }
201
202 .filter-status,
203 .feature-status {
204     color: hsl(0, 0%, 60%);
205     color: var(--status-color);
206     border-color: hsl(0, 0%, 60%);
207     border-color: var(--status-color);
208 }
209
210 .feature-status a {
211     color: inherit;
212 }
213
214 .filter-status,
215 .status-marker {
216     border-color: hsl(0, 0%, 60%);
217     border-color: var(--status-color)
218 }
219 .filter-toggle:checked + .filter-status {
220     background-color: hsl(0, 0%, 60%);
221     background-color: var(--status-color);
222 }
223
224 /** Status color mapping **/
225 .supported {
226     color: hsl(100, 100%, 30%);
227     color: var(--supported-color);
228     border-color: hsl(100, 100%, 30%);
229     border-color: var(--supported-color);
230 }
231
232 .filter-toggle:checked + .supported {
233     background-color: hsl(100, 100%, 30%);
234     background-color: var(--supported-color);
235 }
236
237 .supported-in-preview {
238     color: hsl(275.4, 77.7%, 35.1%);
239     color: var(--supported-in-preview-color);
240     border-color: hsl(275.4, 77.7%, 35.1%);
241     border-color: var(--supported-in-preview-color);
242 }
243
244 .filter-toggle:checked + .supported-in-preview {
245     background-color: hsl(275.4, 77.7%, 35.1%);
246     background-color: var(--supported-in-preview-color);
247 }
248
249 .in-development {
250     color: hsl(24.5, 91.3%, 50.6%);
251     color: var(--in-development-color);
252     border-color: hsl(24.5, 91.3%, 50.6%);
253     border-color: var(--in-development-color);
254 }
255 .filter-toggle:checked + .in-development {
256     background-color: hsl(24.5, 91.3%, 50.6%);
257     background-color: var(--in-development-color);
258 }
259
260 .no-active-development {
261     color: hsl(240, 60.6%, 59.2%);
262     color: var(--no-active-development-color);
263     border-color: hsl(240, 60.6%, 59.2%);
264     border-color: var(--no-active-development-color);
265 }
266
267 .filter-toggle:checked + .no-active-development {
268     background-color: hsl(240, 60.6%, 59.2%);
269     background-color: var(--no-active-development-color);
270 }
271
272 .partially-supported  {
273     color: hsl(180, 25%, 43.9%);
274     color: var(--partially-supported-color);
275     border-color: hsl(180, 25%, 43.9%);
276     border-color: var(--partially-supported-color);
277 }
278
279 .filter-toggle:checked + .partially-supported {
280     background-color: hsl(180, 25%, 43.9%);
281     background-color: var(--partially-supported-color);
282 }
283
284 .prototyping {
285     color: hsl(211.3, 100%, 50%);
286     color: var(--prototyping-color);
287     border-color: hsl(211.3, 100%, 50%);
288     border-color: var(--prototyping-color);
289 }
290
291 .filter-toggle:checked + .prototyping {
292     background-color: hsl(211.3, 100%, 50%);
293     background-color: var(--prototyping-color);
294 }
295
296 .under-consideration {
297     color: hsl(5.9, 40.2%, 60%);
298     color: var(--under-consideration-color);
299     border-color: hsl(5.9, 40.2%, 60%);
300     border-color: var(--under-consideration-color);
301 }
302
303 .filter-toggle:checked + .under-consideration {
304     background-color: hsl(5.9, 40.2%, 60%);
305     background-color: var(--under-consideration-color);
306 }
307
308 .feature.is-hidden {
309     display: none;
310 }
311
312 .features,
313 .features-count {
314     max-width: 920px;
315     margin: 0 auto 3rem;
316 }
317
318 .features {
319     border-bottom: 1px solid hsl(0, 0%, 89.4%);
320     border-color: var(--feature-rule-color);
321 }
322
323 .feature-count {
324     text-align: right;
325     color: #999;
326 }
327
328 .feature {
329     border-color: transparent;
330     border-width: 1px;
331     border-style: solid;
332     border-top-color: hsl(0, 0%, 89.4%);
333     border-top-color: var(--feature-rule-color);
334     padding: 0.5rem;
335     line-height: 1.618;
336     transition: background-color 0.3s ease-out;
337 }
338
339 .feature-header {
340     font-weight: 400;
341     font-size: 2.5rem;
342     display: flex;
343 }
344
345 .feature-header h3 {
346     flex: 1;
347     flex-grow: 2;
348     padding-right: 1rem;
349     box-sizing: border-box;
350 }
351
352 .feature-header h3 a {
353     padding-right: 1rem;
354 }
355
356 .feature-header .feature-status {
357     flex: 2;
358     text-align: right;
359     font-size: 2rem;
360 }
361
362 .feature-container.status-marker {
363     border-left-width: 3px;
364     border-left-style: solid;
365     padding: 0.5rem 0 0.5rem 1rem;
366 }
367
368 .feature-header a[name] {
369     color: hsl(0, 0%, 26.7%);
370     color: var(--text-color-heading);
371 }
372
373 .feature-header .internal-reference {
374     display: inline-block;
375     font-size: 1.6rem;
376     font-weight: 600;
377     white-space: nowrap;
378 }
379
380 .feature-header .internal-reference a {
381     color: hsl(0, 0%, 33.3%);
382     color: var(--text-color-medium);
383 }
384
385 @media(prefers-color-scheme:dark) {
386     .feature-header:after {
387         filter: invert(1);
388     }
389
390     .search-input:hover,
391     .search-input:focus,
392     .feature-filters .filters-toggle-button:hover {
393         filter: brightness(2);
394     }
395 }
396
397 .feature.opened .feature-header:after {
398     -webkit-transform: rotateX(-180deg);
399     -moz-transform: rotateX(-180deg);
400     transform: rotateX(-180deg);
401     perspective: 600;
402 }
403
404 .feature-header:after {
405     position: relative;
406     width: 2rem;
407     height: 2rem;
408     right: 0;
409     top: 0.5rem;
410     margin-left: 1rem;
411     transition: transform 0.3s ease-out;
412 }
413
414 .feature-details {
415     display: none;
416     width: 50%;
417 }
418
419 .feature.opened {
420     background-color: hsl(0, 0%, 100%);
421     background-color: var(--figure-mattewhite-background-color);
422     border-left-color: hsl(0, 0%, 89.4%);
423     border-left-color: var(--feature-rule-color);
424     border-right-color: hsl(0, 0%, 89.4%);
425     border-right-color: var(--feature-rule-color);
426 }
427
428 .feature.opened .feature-details {
429     display: block;
430 }
431
432 .feature h4 {
433     font-weight: 600;
434     margin-top: 1rem;
435     margin-bottom: 0;
436     color: hsl(0, 0%, 33.3%);
437     color: var(--text-color-medium);
438 }
439
440 .feature .moreinfo {
441     list-style: none;
442     display: flex;
443     width: 100%;
444 }
445
446 .feature .moreinfo li {
447     flex-grow: 1;
448 }
449
450 .feature .moreinfo .contact {
451     text-align: right;
452 }
453
454 .feature .feature-desc {
455     color: hsl(0, 0%, 20%);
456     color: var(--text-color);
457 }
458
459 .feature .comment {
460     color: hsl(0, 0%, 33.3%);
461     color: var(--text-color-medium);
462     font-style: italic;
463 }
464
465 .sub-features {
466     font-size: 1.5rem;
467     color: hsl(0, 0%, 24%);
468     color: var(--text-color-light);
469 }
470
471 .sub-features ul {
472     list-style: none;
473     padding: 0;
474     margin: 0;
475 }
476
477 .sub-features li {
478     display: inline-block;
479     white-space: nowrap;
480 }
481
482 .sub-features li:after {
483     content: ", ";
484     white-space: pre;
485 }
486
487 .sub-features li:last-child:after {
488     content: "";
489 }
490
491 .pagination:after {
492     display: none;
493 }
494
495 .pagination,
496 .pagination + h1 {
497     margin-top: 0;
498 }
499
500 @media only screen and (max-width: 1180px) {
501     .feature-details {
502         width: 100%;
503     }
504
505     .feature-filters .filters-toggle-button {
506         right: 3rem;
507     }
508 }
509
510 @media only screen and (max-width: 508px) {
511     #feature-filters,
512     #feature-list {
513         width: 100%;
514     }
515
516     #feature-filters {
517         padding-left: 2rem;
518         padding-right: 2rem;
519     }
520
521     .feature-header h3 {
522         font-size: 2rem;
523         padding-right: 0.5rem;
524     }
525
526     .feature-status {
527         font-size: 1.6rem;
528         margin-top: 0.4rem;
529         float: left;
530     }
531
532     .feature-header:after {
533         width: 1rem;
534         height: 1rem;
535         background-size: 1rem;
536         top: 1rem;
537     }
538
539     .feature h3 {
540         font-size: 2rem;
541         padding-top: 4rem;
542     }
543
544     .feature-header .feature-status {
545         font-size: 1.6rem;
546         position: absolute;
547         text-align: left;
548     }
549
550     .feature .moreinfo {
551         flex-wrap: wrap;
552     }
553
554     .feature .moreinfo .contact {
555         text-align: left;
556     }
557
558     .status-filters {
559         flex-basis: 100%;
560     }
561
562     .status-filters label {
563         margin-left: 0;
564         margin-right: 1rem;
565     }
566 }
567
568 h3 a[name], .admin-bar h3 a[name] {
569     top: initial;
570     width: auto;
571     display: inline-block;
572     visibility: visible;
573 }
574
575
576 </style>
577         <?php if (have_posts()) : while (have_posts()) : the_post(); ?>
578
579         <div class="page feature-status-page" id="post-<?php the_ID(); ?>">
580             <div class="connected pagination">
581                 <?php wp_nav_menu( array('theme_location'  => 'feature-subnav') ); ?>
582             </div>
583
584             <h1><a href="<?php echo get_permalink() ?>" rel="bookmark" title="Permanent Link: <?php the_title(); ?>"><?php the_title(); ?></a></h1>
585
586             <section class="feature-filters">
587                 <form id="feature-filters" class="page-width">
588                     <input type="text" id="search" class="search-input" placeholder="Search features&hellip;" title="Filter the feature list." required><label class="filters-toggle-button">Filters</label>
589                     <ul id="status-filters" class="status-filters"></ul>
590                 </form>
591             </section>
592
593             <section id="feature-list">
594                 <div class="feature-count">
595                     <p><span id="feature-count"></span> <span id="feature-pluralize">features</span></p>
596                 </div>
597
598             </section>
599
600             <template id="success-template">
601                 <ul class="features" id="features-container"></ul>
602                 <p>Cannot find something? You can contact <a href="https://twitter.com/webkit">@webkit</a> on Twitter or contact the <a href="https://lists.webkit.org/mailman/listinfo/webkit-help">webkit-help</a> mailing list for questions.</p>
603                 <p>You can also <a href="/contributing-code/">contribute to features</a> directly, the entire project is Open Source. To report bugs on existing features or check existing bug reports, see <a href="https://bugs.webkit.org">https://bugs.webkit.org</a>.</p>
604             </template>
605             <template id="error-template">
606                 <p>Error: unable to load the features list (<span id="error-message"></span>).</p>
607                 <p>If this is not resolved soon, please contact <a href="https://twitter.com/webkit">@webkit</a> on Twitter or the <a href="https://lists.webkit.org/mailman/listinfo/webkit-help">webkit-help</a> mailing list.</p>
608             </template>
609         </div>
610
611     <?php endwhile; else: ?>
612
613         <p>No posts.</p>
614
615     <?php endif; ?>
616
617
618 <script>
619 function initializeStatusPage() {
620
621     const statusOrder = [
622         'under consideration',
623         'prototyping',
624         'in development',
625         'supported in preview',
626         'partially supported',
627         'supported',
628         'deprecated',
629         'removed',
630         'not considering'
631     ];
632
633     function sortAlphabetically(array)
634     {
635         array.sort(function(a, b){
636             var aName = a.name.toLowerCase();
637             var bName = b.name.toLowerCase();
638
639             var nameCompareResult = aName.localeCompare(bName);
640
641             if ( nameCompareResult )
642                 return nameCompareResult;
643
644             // Status sort
645             var aStatus = a.status != undefined ? a.status.status.toLowerCase() : '';
646             var bStatus = b.status != undefined ? b.status.status.toLowerCase() : '';
647
648             return aStatus.localeCompare(bStatus);
649         });
650     }
651
652     function createFeatureView(featureObject)
653     {
654
655         function createLinkWithHeading(elementName, heading, linkText, linkUrl) {
656             var container = document.createElement(elementName);
657             if (heading) {
658                 var h4 = document.createElement('h4');
659                 h4.textContent = heading;
660                 container.appendChild(h4);
661             }
662             var link = document.createElement("a");
663             link.textContent = linkText;
664             link.href = linkUrl;
665             if (linkText == linkUrl)
666                 link.textContent = link.hostname + "…";
667             container.appendChild(link);
668             return container;
669         }
670
671         function makeTwitterLink(twitterHandle) {
672             if (twitterHandle[0] == "@")
673                 twitterHandle = twitterHandle.substring(1);
674             return "https://twitter.com/" + twitterHandle;
675         }
676
677         var container = document.createElement('li');
678         var hasDocumentationLink = "documentation-url" in featureObject;
679         var hasReferenceLink = "url" in featureObject;
680         var hasContactObject = "contact" in featureObject;
681         var hasSpecificationObject = "specification" in featureObject;
682
683         container.addEventListener('click', function (e) {
684             if ( container.className.indexOf('opened') !== -1 ) {
685                 container.className = container.className.replace(' opened','');
686             } else container.className += " opened";
687         });
688
689         container.className = "feature";
690
691         var slug = canonicalizeIdentifier(featureObject.name);
692         if ("features" in featureObject) {
693             container.setAttribute("id", "specification-" + slug);
694         } else {
695             container.setAttribute("id", "feature-" + slug);
696         }
697
698         if (window.location.hash && window.location.hash == "#" + container.getAttribute('id')) {
699             container.className += " opened";
700         }
701
702         var featureContainer = document.createElement('div');
703         featureContainer.className = "feature-container status-marker";
704
705         var featureHeaderContainer = document.createElement('div');
706         featureHeaderContainer.className = "feature-header";
707         featureContainer.appendChild(featureHeaderContainer);
708
709         var titleElement = document.createElement("h3");
710         var anchorLinkElement = document.createElement("a");
711         anchorLinkElement.href = "#" + container.getAttribute("id");
712         anchorLinkElement.name = container.getAttribute("id");
713         anchorLinkElement.textContent = featureObject.name;
714         titleElement.appendChild(anchorLinkElement);
715
716         // Add sub-feature here
717         if (hasSpecificationObject) {
718             var specification = featureObject.specification;
719             var specSpan = createLinkWithHeading("h4", null, specification.name, "#specification-" + specification.name.toLowerCase().replace(/ /g, '-'));
720             specSpan.className = "internal-reference";
721             titleElement.appendChild(specSpan);
722         }
723
724         featureHeaderContainer.appendChild(titleElement);
725
726         if ("status" in featureObject) {
727             var statusContainer = document.createElement("div");
728             var statusClassName = canonicalizeIdentifier(featureObject.status.status);
729             featureContainer.className += " " + statusClassName;
730             statusContainer.className = "feature-status " + statusClassName;
731             var statusLabel = document.createElement("label");
732
733             if ("webkit-url" in featureObject) {
734                 var statusLink = document.createElement("a");
735                 statusLink.href = featureObject["webkit-url"];
736                 statusLink.textContent = featureObject.status.status;
737                 statusLabel.appendChild(statusLink);
738             } else {
739                 statusLabel.textContent = featureObject.status.status;
740             }
741
742             statusContainer.appendChild(statusLabel);
743             featureHeaderContainer.appendChild(statusContainer);
744         }
745
746         var featureDetails = document.createElement('div');
747         featureDetails.className = 'feature-details';
748
749         if ("description" in featureObject) {
750             var textDescription = document.createElement('p');
751             textDescription.className = "feature-desc";
752             textDescription.innerHTML = featureObject.description;
753             featureDetails.appendChild(textDescription);
754         }
755
756         if ("comment" in featureObject) {
757             var comment = document.createElement('p');
758             comment.className = 'comment';
759             comment.innerHTML = featureObject.comment;
760             featureDetails.appendChild(comment);
761         }
762
763         if ("features" in featureObject && featureObject.features.length) {
764             var internalLinkContainer = document.createElement("div");
765             internalLinkContainer.className = "internal-reference sub-features";
766             var internalHeading = document.createElement("h4");
767             internalHeading.textContent = "Includes";
768             internalLinkContainer.appendChild(internalHeading);
769
770             var list = document.createElement("ul");
771             for (var feature of featureObject.features) {
772                 var link = document.createElement("a");
773                 link.textContent = feature.name;
774                 link.href = "#feature-" + canonicalizeIdentifier(feature.name);
775
776                 var li = document.createElement("li");
777                 li.appendChild(link);
778                 list.appendChild(li);
779             }
780             internalLinkContainer.appendChild(list);
781             featureDetails.appendChild(internalLinkContainer);
782         }
783
784         if (hasDocumentationLink || hasReferenceLink || hasContactObject) {
785             var moreInfoList = document.createElement("ul");
786             moreInfoList.className = 'moreinfo';
787             if (hasDocumentationLink) {
788                 var url = featureObject["documentation-url"];
789                 moreInfoList.appendChild(createLinkWithHeading("li", "Documentation", url, url));
790             }
791
792             if (hasReferenceLink) {
793                 var url = featureObject.url;
794                 moreInfoList.appendChild(createLinkWithHeading("li", "Reference", url, url));
795             }
796
797             if (hasContactObject) {
798                 var li = document.createElement("li");
799                 li.className = "contact";
800                 var contactHeading = document.createElement("h4");
801                 contactHeading.textContent = "Contact";
802                 li.appendChild(contactHeading);
803
804                 if (featureObject.contact.twitter) {
805                     li.appendChild(createLinkWithHeading("span", null, featureObject.contact.twitter, makeTwitterLink(featureObject.contact.twitter)));
806                 }
807                 if (featureObject.contact.email) {
808                     if (featureObject.contact.twitter) {
809                         li.appendChild(document.createTextNode(" - "));
810                     }
811                     var emailText = featureObject.contact.email;
812                     if (featureObject.contact.name) {
813                         emailText = featureObject.contact.name;
814                     }
815                     li.appendChild(createLinkWithHeading("span", null, emailText, "mailto:" + featureObject.contact.email));
816                 }
817                 moreInfoList.appendChild(li);
818             }
819
820             featureDetails.appendChild(moreInfoList);
821         }
822
823         featureContainer.appendChild(featureDetails);
824         container.appendChild(featureContainer);
825
826         return container;
827     }
828
829     function canonicalizeIdentifier(identifier)
830     {
831         return identifier.toLocaleLowerCase().replace(/ /g, '-');
832     }
833
834
835     function renderFeaturesAndSpecifications(featureLikeObjects)
836     {
837         var featureContainer = document.getElementById('features-container');
838         for (var featureLikeObject of featureLikeObjects) {
839             featureContainer.appendChild(createFeatureView(featureLikeObject));
840         }
841     }
842
843     function initSearch(featuresArray)
844     {
845         var filtersForm = document.getElementById('feature-filters');
846         var filtersToggleButton = document.getElementsByClassName('filters-toggle-button')[0];
847         var statusContainer = document.getElementById('status-filters');
848         var inputField = document.getElementById('search');
849         var featuresEls = document.querySelectorAll('.features > li');
850         var statusFilters = {};
851
852         featuresArray.forEach(function(feature, i) {
853             feature.el = featuresEls[i];
854             feature.visible = true;
855
856             if (feature.status != undefined) {
857                 featureStatusKey = feature.status.status.toLocaleLowerCase();
858
859                 if (!statusFilters[featureStatusKey])
860                     statusFilters[featureStatusKey] = feature.status.status;
861
862                 if (statusOrder.indexOf(featureStatusKey) == -1)
863                     window.console.log('Status ' + featureStatusKey + ' is not one of the predefined status keys ', statusOrder);
864
865             }
866         });
867
868         var searchTerm = searchTermFromURL();
869         var selectedStatuses = statusesFromURL();
870
871         for (var key of statusOrder) {
872             if (statusFilters[key] == undefined)
873                 continue;
874
875             var statusLabel = statusFilters[key];
876             var statusId = canonicalizeIdentifier(statusLabel);
877             var entry = document.createElement("li");
878             var label = document.createElement("label");
879             var input = document.createElement("input");
880
881             input.setAttribute('type','checkbox');
882             input.setAttribute('value', key);
883             input.setAttribute('id', 'toggle-' + statusId);
884             input.className = 'filter-toggle';
885             input.addEventListener('change', function() { updateSearch(featuresArray); });
886
887             if (selectedStatuses.indexOf(statusId) != -1) {
888                 filtersForm.classList.add('opened');
889                 input.checked = true;
890             }
891
892             label.className = "filter-status " + statusId;
893             label.setAttribute('for', 'toggle-' + statusId);
894             label.appendChild(input);
895             label.appendChild(document.createTextNode(" " + statusLabel));
896
897             entry.appendChild(label);
898
899             statusContainer.appendChild(entry);
900         }
901
902         filtersToggleButton.addEventListener('click', function (e) {
903             filtersForm.classList.toggle('opened');
904         });
905
906         if (searchTerm.length) {
907             inputField.value = searchTerm;
908             inputField.placeholder = '';
909         }
910         inputField.addEventListener('input', function() { updateSearch(featuresArray); });
911
912
913         var inputs = [].slice.call(filtersForm.getElementsByTagName('input'));
914         inputs.forEach(function (input,i) {
915             input.addEventListener('click', function (e) {
916                 e.stopPropagation();
917             });
918         });
919
920         function search(ev)
921         {
922             var searchTerm = inputField.value.trim().toLowerCase();
923             var activeStatusFilters = [];
924             var checkboxes = [].slice.call(statusContainer.getElementsByTagName('input'));
925             checkboxes.forEach(function(checkbox,i) {
926                 if ( checkbox.checked )
927                     activeStatusFilters.push(checkbox.value);
928             });
929
930             searchFeatures(featuresArray, searchTerm, activeStatusFilters);
931         }
932     }
933
934     function getValuesOfCheckedItems(items)
935     {
936         var checkedValues = [];
937         items.forEach(function(item,i) {
938             if (item.checked)
939                 checkedValues.push(item.value);
940         });
941
942         return checkedValues;
943     }
944
945     function updateSearch(properties)
946     {
947         var inputField = document.getElementById('search');
948         var statusContainer = document.getElementById('status-filters');
949
950         var searchTerm = inputField.value.trim().toLowerCase();
951         var activeStatusFilters = getValuesOfCheckedItems([].slice.call(statusContainer.querySelectorAll('.filter-toggle')));
952
953         var numVisible = searchFeatures(properties, searchTerm, activeStatusFilters);
954         document.getElementById('feature-pluralize').textContent = numVisible == 1 ? 'feature' : 'features';
955         document.getElementById('feature-count').textContent = numVisible;
956
957         updateURL(searchTerm, activeStatusFilters);
958     }
959
960     function searchFeatures(features, searchTerm, statusFilters)
961     {
962         var visibleCount = 0;
963         features.forEach(function(featureObject) {
964             var matchesStatusSearch = isStatusFiltered(featureObject, statusFilters);
965
966             var visible = isSearchMatch(featureObject, searchTerm) && matchesStatusSearch;
967             if (visible && !featureObject.visible)
968                 featureObject.el.className = 'feature';
969             else if (!visible && featureObject.visible)
970                 featureObject.el.className = 'feature is-hidden';
971
972             if (visible) {
973                 // filterValues(featureObject, searchTerm);
974                 ++visibleCount;
975             }
976
977             featureObject.visible = visible;
978         });
979
980         return visibleCount;
981     }
982
983     function searchTermFromURL()
984     {
985         var search = window.location.search;
986         var searchRegExp = /\#.*search=([^&]+)/;
987
988         var result;
989         if (result = window.location.href.match(searchRegExp))
990             return decodeURIComponent(result[1]);
991
992         return '';
993     }
994
995     function statusesFromURL()
996     {
997         var search = window.location.search;
998         var statusRegExp = /\#.*status=([^&]+)/;
999
1000         var result;
1001         if (result = window.location.href.match(statusRegExp))
1002             return result[1].split(',');
1003
1004         return [];
1005     }
1006
1007     function isSearchMatch(feature, searchTerm)
1008     {
1009         if (feature.name.toLowerCase().indexOf(searchTerm) !== -1)
1010             return true;
1011         if ("keywords" in feature) {
1012             for (var keyword of feature.keywords) {
1013                 if (keyword.toLowerCase().indexOf(searchTerm) !== -1)
1014                     return true;
1015             }
1016         }
1017         return false;
1018     }
1019
1020     function isStatusFiltered(feature, activeFilters)
1021     {
1022         if (activeFilters.length == 0)
1023             return true;
1024         if (feature.status === undefined)
1025             return false;
1026         if (activeFilters.indexOf(feature.status.status.toLowerCase()) != -1)
1027             return true;
1028
1029         return false;
1030     }
1031
1032     function filterValues(featureObject, searchTerm, statusFilters)
1033     {
1034         for (var valueObj of featureObject.values) {
1035             if (!valueObj.el)
1036                 continue;
1037
1038             var visible = false;
1039             visible = valueObj.value.toLowerCase().indexOf(searchTerm) !== -1;
1040
1041             if (visible)
1042                 valueObj.el.classList.remove('hidden');
1043             else
1044                 valueObj.el.classList.add('hidden');
1045         }
1046     }
1047
1048     function displayFeatures(results)
1049     {
1050         var mainContent = document.getElementById("feature-list");
1051         var successSubtree = document.importNode(document.getElementById("success-template").content, true);
1052         mainContent.appendChild(successSubtree);
1053
1054         var allSpecifications = [];
1055         for (var i in results) {
1056             allSpecifications = allSpecifications.concat(results[i].specification);
1057         }
1058         var specificationsByName = {}
1059         for (var specification of allSpecifications) {
1060             specification.features = [];
1061             specification.isSpecification = true;
1062             specificationsByName[specification.name] = specification;
1063         }
1064
1065         var allFeatures = [];
1066         for (var i in results) {
1067             allFeatures = allFeatures.concat(results[i].features);
1068         }
1069         var featuresByName = {};
1070         for (var feature of allFeatures) {
1071             if ('specification' in feature) {
1072                 var featureSpecification = feature.specification;
1073                 var specificationObject = specificationsByName[featureSpecification];
1074                 if (specificationObject != undefined) {
1075                     specificationObject.features.push(feature);
1076                     feature.specification = specificationObject;
1077                 } else {
1078                     feature.specification = {
1079                         name: featureSpecification
1080                     };
1081                 }
1082             }
1083             feature.isSpecification = false;
1084             featuresByName[feature.name] = feature;
1085         }
1086
1087         var everythingToShow = allFeatures.concat(allSpecifications);
1088
1089         sortAlphabetically(everythingToShow);
1090
1091         renderFeaturesAndSpecifications(everythingToShow);
1092
1093         initSearch(everythingToShow);
1094
1095         updateSearch(everythingToShow);
1096
1097         if (window.location.hash.length) {
1098             var hash = window.location.hash;
1099             window.location.hash = ""; // Change hash so navigation takes place
1100             window.location.hash = hash;
1101         }
1102     }
1103
1104     function displayError(error)
1105     {
1106         var mainContent = document.getElementById("feature-list");
1107         var successSubtree = document.importNode(document.getElementById("error-template").content, true);
1108
1109         var errorMessage = "Unable to load " + error.url;
1110
1111         if (error.request.status !== 200) {
1112             errorMessage += ", status: " + error.request.status + " - " + error.request.statusText;
1113         } else if (!error.response) {
1114             errorMessage += ", the JSON file cannot be processed.";
1115         }
1116
1117         successSubtree.querySelector("#error-message").textContent = errorMessage;
1118
1119         mainContent.appendChild(successSubtree);
1120     }
1121
1122     function updateURL(searchTerm, activeStatusFilters)
1123     {
1124         var searchString = '';
1125
1126         function appendDelimiter()
1127         {
1128             searchString += searchString.length ? '&' : '?';
1129         }
1130
1131         if (searchTerm.length > 0) {
1132             appendDelimiter();
1133             searchString += 'search=' + encodeURIComponent(searchTerm);
1134         }
1135
1136         if (activeStatusFilters.length) {
1137             appendDelimiter();
1138             searchString += 'status=' + activeStatusFilters.join(',');
1139         }
1140
1141         if (searchString.length) {
1142             var current = window.location.href;
1143             window.location.href = current.replace(/\??#(.*)$/, '') + '#' + searchString;
1144         }
1145
1146     }
1147
1148
1149     Promise.all([loadJavaScriptCoreFeatures, loadWebCoreFeatures]).then(displayFeatures).catch(displayError);
1150 }
1151
1152 document.addEventListener("DOMContentLoaded", initializeStatusPage);
1153 </script>
1154
1155 <?php get_footer(); ?>