7f8b4f90b18fd0674f4d9aa51e438ca187221ee8
[WebKit-https.git] / Websites / webkit.org / wp-content / themes / webkit / css-status.php
1 <?php
2 /**
3  * Template Name: CSS 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 || xhrRequest.status == 0) {
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
32 var origin = new URL("https://<?php echo strpos(WP_HOST, "webkit.org") !== false ? "svn.webkit.org" : WP_HOST; ?>/");
33 var loadCSSProperties = xhrPromise(new URL("/repository/webkit/trunk/Source/WebCore/css/CSSProperties.json", origin));
34
35 </script>
36
37 <style>
38
39 .feature-status-page {
40     animation: none !important; /* This animation can trigger a hit-testing bug, so remove it for now */
41 }
42
43 .page {
44     display: -webkit-flex;
45     display: flex;
46     flex-direction: column;
47     -webkit-justify-content: space-between;
48     justify-content: space-between;
49     box-sizing: border-box;
50     width: 100%;
51 }
52
53 section.side-by-side {
54     display: flex;
55     display: -webkit-flex;
56     flex: 1;
57     -webkit-flex: 1;
58 }
59
60 sidebar {
61     flex: 0 400px;
62     margin-right: 3rem;
63     font-size: 2rem;
64     margin-left: 1rem;
65 }
66
67 section.primary {
68     flex: 1;
69     -webkit-flex: 1;
70 }
71
72 .sticky {
73     position: -webkit-sticky;
74     top: 0;
75 }
76
77 .page h1 {
78     font-size: 4.2rem;
79     font-weight: 200;
80     line-height: 6rem;
81     color: black;
82     text-align: left;
83     margin: 3rem auto;
84     width: 100%;
85 }
86
87 .page h1 a {
88     color: #444;
89 }
90
91 .page h2 {
92     font-weight: 200;
93     font-size: 3rem;
94 }
95
96 .page h3 {
97     font-weight: 400;
98     font-size: 2.2rem;
99 }
100
101 .property-count {
102     text-align: right;
103     color: #999;
104 }
105
106 .property-count p {
107     margin: 0;
108 }
109
110 #property-list {
111 /*    word-wrap: break-word;*/
112 }
113
114 .property-header > h3:first-of-type {
115     -webkit-flex-grow: 1;
116     flex-grow: 1;
117     margin: 0;
118 }
119
120 ul.properties {
121     padding: 0;
122 }
123
124 .properties .property {
125     position: relative;
126     display: block;
127     background-color: #f9f9f9;
128     border: 1px solid #ddd;
129     border-radius: 3px;
130     padding: 1em;
131     margin: 1em 0 !important;
132     max-height: intrinsic;
133     min-height: 3rem;
134     overflow-y: hidden;
135     cursor: pointer;
136     transition: background-color 0.3s ease-in;
137 }
138
139 .properties .property:hover {
140     background-color: white;
141 }
142
143 .property.opened {
144     background-color: white;
145     max-height: 120rem;
146 }
147
148 .property-description .toggleable {
149     display: none;
150 }
151
152 .property.opened .property-description .toggleable {
153     display: block;
154     margin-top: 1rem;
155 }
156
157 .comment {
158     font-size: smaller;
159 }
160
161 .more-info {
162     margin-top: 0.5em;
163     font-size: smaller;
164 }
165
166 .sub-features {
167     font-size: 1.5rem;
168     color: #555;
169 }
170
171 .sub-features ul {
172     list-style: none;
173     display: inline-block;
174     padding: 0;
175     margin: 0;
176 }
177
178 .sub-features li {
179     display: inline;
180 }
181
182 .sub-features li:after {
183     content: ", ";
184 }
185
186 .sub-features li:last-child:after {
187     content: "";
188 }
189
190 ul.values {
191     margin-left: 3em;
192     margin-bottom: 0.5em;
193     cursor: default;
194 }
195
196 .values li.hidden {
197     color: #444;
198 }
199
200 .property-header {
201     position: relative;
202     padding-right: 3rem;
203     display: -webkit-flex;
204     display: flex;
205     -webkit-flex-direction: row;
206     flex-direction: row;
207 }
208
209 .property-header .toggle {
210     display: inline-block;
211     background: url('images/menu-down.svg') no-repeat 50%;
212     background-size: 2rem;
213     border: none;
214     width: 2rem;
215     height: 2rem;
216     position: absolute;
217     right: 0;
218     top: 0.5rem;
219     transition: transform 0.3s ease-out;
220 }
221
222 .property.opened .property-header .toggle {
223     -webkit-transform: rotateX(-180deg);
224     -moz-transform: rotateX(-180deg);
225     transform: rotateX(-180deg);
226 }
227
228 .property-header h3 .spec-label {
229     color: #999;
230     text-decoration: none;
231     font-weight: 200;
232 }
233
234 .property-header h3 .spec-label a {
235     color: #999;
236     text-decoration: none;
237     font-weight: 200;
238 }
239
240 .spec-label::before {
241     content: ' — ';
242 }
243 .property-header h3 a {
244     color: #444;
245 }
246
247 .property-alias {
248     font-size: smaller;
249     color: #999;
250 }
251
252 .property-header p {
253     margin-top: 0.5rem;
254     margin-bottom: 0.5rem;
255 }
256
257 .value-alias {
258     color: #999;
259 }
260
261 .value-status {
262     color: #999;
263 }
264
265 .property.is-hidden {
266     display: none;
267 }
268
269 ul.property-details {
270     margin: 0;
271 }
272 .property-statusItem {
273     margin-right: 0.5em;
274 }
275
276 .property-status {
277     display: inline-block;
278     position: relative;
279     font-size: 2rem;
280     min-width: 4em;
281     text-align: right;
282 }
283
284 .property-status,
285 .property-status a {
286     color: #999;
287 }
288
289 .property .status-marker {
290     width: 0;
291     height: 0;
292     position: absolute;
293     top: 0;
294     left: 0;
295     border-style: solid;
296     border-width: 20px 20px 0 0;
297     border-color: transparent transparent transparent transparent;
298 }
299
300 #status-filters .supported,
301 .property-status.supported,
302 .property-status.supported a {
303     color: #339900;
304 }
305
306 .status-marker.supported {
307     border-color: #339900 transparent transparent transparent;
308 }
309
310 #status-filters .in-development,
311 .property-status.in-development,
312 .property-status.in-development a {
313     color: #f46c0e;
314 }
315
316 .status-marker.in-development {
317     border-color: #f46c0e transparent transparent transparent;
318 }
319
320 #status-filters .no-active-development,
321 .property-status.no-active-development,
322 .property-status.no-active-development a {
323     color: #5858D6;
324 }
325
326 .status-marker.no-active-development {
327     border-color: #5858D6 transparent transparent transparent;
328 }
329
330 #status-filters .partial-support,
331 .property-status.partial-support,
332 .property-status.partial-support a {
333     color: #548c8c;
334 }
335
336 .status-marker.partial-support {
337     border-color: #548c8c transparent transparent transparent;
338 }
339
340 #status-filters .prototyping,
341 .property-status.prototyping,
342 .property-status.prototyping a {
343     color: #007AFF;
344 }
345
346 .status-marker.prototyping {
347     border-color: #007AFF transparent transparent transparent;
348 }
349
350 #status-filters .experimental,
351 .property-status.experimental,
352 .property-status.experimental a {
353     color: #007AFF;
354 }
355
356 .status-marker.experimental {
357     border-color: #007AFF transparent transparent transparent;
358 }
359
360 #status-filters .under-consideration,
361 .property-status.under-consideration,
362 .property-status.under-consideration a {
363     color: #cc9d00;
364 }
365
366 .status-marker.under-consideration {
367     border-color: #FFC500 transparent transparent transparent;
368 }
369
370 #status-filters .removed,
371 .property-status.removed,
372 .property-status.removed a {
373     color: #7F7F7F;
374 }
375
376 .status-marker.removed {
377     border-color: #7F7F7F transparent transparent transparent;
378 }
379
380 #status-filters .non-standard,
381 .property-status.non-standard,
382 .property-status.non-standard a {
383     color: #8000FF;
384 }
385
386 .status-marker.non-standard {
387     border-color: #8000FF transparent transparent transparent;
388 }
389
390 #status-filters .not-considering,
391 .property-status.not-considering,
392 .property-status.not-considering a {
393     color: #7F7F7F;
394 }
395
396 .status-marker.not-considering {
397     border-color: #7F7F7F transparent transparent transparent;
398 }
399
400 #status-filters .not-implemented,
401 .property-status.not-implemented,
402 .property-status.not-implemented a {
403     color: #4C4C4C;
404 }
405
406 .status-marker.not-implemented {
407     border-color: #4C4C4C transparent transparent transparent;
408 }
409
410 #status-filters .obsolete,
411 .property-status.obsolete,
412 .property-status.obsolete a {
413     color: #804000;
414 }
415
416 .status-marker.obsolete {
417     border-color: #804000 transparent transparent transparent;
418 }
419
420 .property-filters {
421     position: relative;
422     top: 0;
423     margin-top: 0.5em;
424 }
425
426 #search {
427     font-size: 2rem;
428     padding: 1rem;
429     border-radius: 3px;
430     border: 1px solid #cccccc;
431     width: 100%;
432     margin-top: 1.5rem;
433     box-sizing: border-box;
434 }
435
436 .property-filters ul {
437     margin-top: 0.5rem;
438     margin-bottom: 1.5rem;
439 }
440
441 .property-filters ul li {
442     margin-bottom: 0.5rem;
443 }
444
445 .property-filters label > input {
446     position: relative;
447     top: -3px;
448 }
449
450 .prefixes {
451     display: none;
452 }
453
454 #specifications {
455     display: block;
456     font-size: 1.6rem;
457     border: 1px solid silver;
458     width: calc(100% - 2rem);
459     height: 3rem;
460     margin: 1rem 2rem;
461 }
462
463 h3 a[name], .admin-bar h3 a[name] {
464     top: initial;
465     width: auto;
466     display: inline-block;
467     visibility: visible; /* Override visibility:hidden from themes/webkit/style.css */
468 }
469
470 @media only screen and (max-width: 508px) {
471     #property-filters,
472     #property-list {
473         width: 100%;
474     }
475
476     #property-filters {
477         border: 1px solid #ddd;
478         border-radius: 3px;
479         background: #f6f6f6;
480         padding: 1rem;
481         box-sizing: border-box;
482         margin-right: 0;
483         margin-bottom: 3rem;
484     }
485
486     .property-header h3 {
487         font-size: 2rem;
488     }
489
490     .property-status {
491         font-size: 1.6rem;
492         margin-top: 0.4rem;
493     }
494 }
495 </style>
496     <?php if (have_posts()) : while (have_posts()) : the_post(); ?>
497
498         <div class="page feature-status-page" id="post-<?php the_ID(); ?>">
499             <?php echo str_repeat('&nbsp;', 200);?>
500             <h1><a href="<?php echo get_permalink() ?>" rel="bookmark" title="Permanent Link: <?php the_title(); ?>"><?php the_title(); ?></a></h1>
501
502             <section class="side-by-side">
503                 <sidebar>
504                     <section class="sticky">
505                         <form id="property-filters" class="property-filters">
506                             <h2>Filters</h2>
507                             <input type="text" id="search" placeholder="Search filter&hellip;" title="Filter the property list." required>
508                             <h2>Filter by Status</h2>
509                             <ul id="status-filters">
510                             </ul>
511                         </form>
512
513                         <div class="prefixes">
514                             <h2>Filter by Prefix</h2>
515                             <ul id="prefix-filters">
516                             </ul>
517                         </div>
518                     </section>
519                 </sidebar>
520
521                 <section class="primary">
522                     <div id="property-list">
523                         <div class="property-count">
524                             <p><span id="property-count"></span> <span id="property-pluralize">properties</span></p>
525                         </div>
526                     </div>
527
528                     <template id="success-template">
529                         <ul class="properties" id="properties-container"></ul>
530
531                         <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>
532                         <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>
533                     </template>
534
535                     <template id="error-template">
536                         <p>Error: unable to load the features list (<span id="error-message"></span>).</p>
537                         <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>
538                     </template>
539
540                 </section>
541             </section>
542         </div>
543
544     <?php //comments_template(); ?>
545
546     <?php endwhile; else: ?>
547
548         <p>No posts.</p>
549
550     <?php endif; ?>
551
552
553 <script>
554 function initializeStatusPage() {
555
556     const statusOrder = [
557         'supported',
558         'in-development',
559         'under-consideration',
560         'experimental',
561         'non-standard',
562         'not-considering',
563         'not-implemented',
564         'obsolete',
565         'removed',
566     ];
567
568     const readableStatus = {
569         'supported': 'Supported',
570         'in-development': 'In Development',
571         'under-consideration': 'Under Consideration',
572         'experimental': 'Experimental',
573         'non-standard': 'Non-standard',
574         'not-considering': 'Not considering',
575         'not-implemented': 'Not implemented',
576         'obsolete': 'Obsolete',
577         'removed': 'Removed',
578     };
579
580     function sortAlphabetically(array)
581     {
582         function replaceDashPrefix(name)
583         {
584             if (name[0] == '-')
585                 return 'Z' + name.slice(1);
586             return name;
587         }
588
589         array.sort(function(a, b) {
590             // Sort the prefixed properties to the end.
591             var aName = replaceDashPrefix(a.name.toLowerCase());
592             var bName = replaceDashPrefix(b.name.toLowerCase());
593
594             var nameCompareResult = aName.localeCompare(bName);
595
596             if (nameCompareResult)
597                 return nameCompareResult;
598
599             // Status sort
600             var aStatus = a.status != undefined ? a.status.status.toLowerCase() : '';
601             var bStatus = b.status != undefined ? b.status.status.toLowerCase() : '';
602
603             return aStatus.localeCompare(bStatus);
604         });
605     }
606
607     function propertyNameAliases(propertyObject)
608     {
609         if ('codegen-properties' in propertyObject && 'aliases' in propertyObject['codegen-properties'])
610             return propertyObject['codegen-properties'].aliases;
611
612         return [];
613     }
614
615     function propertyLonghands(propertyObject)
616     {
617         if ('codegen-properties' in propertyObject && 'longhands' in propertyObject['codegen-properties'])
618             return propertyObject['codegen-properties'].longhands;
619
620         return [];
621     }
622
623     const prefixRegexp = /^(-webkit-|-epub-|-apple-)(.+)$/;
624
625     function createPropertyView(categoryObject, propertyObject)
626     {
627         function createLinkWithHeading(elementName, heading, linkText, linkUrl)
628         {
629             var container = document.createElement(elementName);
630             if (heading) {
631                 container.textContent = heading + ": ";
632             }
633             var link = document.createElement("a");
634             link.textContent = linkText;
635             link.href = linkUrl;
636             container.appendChild(link);
637             return container;
638         }
639
640         function appendValueWithLink(container, value, link)
641         {
642             if (link) {
643                 var anchor = document.createElement('a');
644                 anchor.href = link;
645                 anchor.textContent = value;
646                 container.appendChild(anchor);
647                 return;
648             }
649
650             container.textContent = value;
651         }
652
653         function makeTwitterLink(twitterHandle)
654         {
655             if (twitterHandle[0] == "@")
656                 twitterHandle = twitterHandle.substring(1);
657             return "https://twitter.com/" + twitterHandle;
658         }
659
660         var hasSpecificationObject = "specification" in propertyObject;
661         var specificationObject = propertyObject.specification;
662
663         var container = document.createElement('li');
664
665         container.className = "property";
666
667         var slug = propertyObject.name.toLowerCase().replace(/ /g, '-');
668         container.setAttribute("id", "property-" + slug);
669
670         var cornerStatus = document.createElement('div');
671         cornerStatus.className = "status-marker ";
672         container.appendChild(cornerStatus);
673
674         var descriptionContainer = document.createElement('div');
675         descriptionContainer.className = "property-description";
676
677         var featureHeaderContainer = document.createElement('div');
678         featureHeaderContainer.className = "property-header";
679         descriptionContainer.appendChild(featureHeaderContainer);
680
681         var titleElement = document.createElement("h3");
682         var anchorLinkElement = document.createElement("a");
683         anchorLinkElement.href = "#" + container.getAttribute("id");
684         anchorLinkElement.name = container.getAttribute("id");
685         anchorLinkElement.textContent = propertyObject.name;
686         titleElement.appendChild(anchorLinkElement);
687
688         if (categoryObject) {
689             if (specificationObject && ("url" in specificationObject || "obsolete-url" in specificationObject)) {
690                 var url = ("url" in specificationObject) ? specificationObject.url : specificationObject['obsolete-url'];
691                 var specSpan = createLinkWithHeading("span", null, categoryObject.shortname, specificationObject.url);
692                 specSpan.className = 'spec-label';
693                 titleElement.appendChild(specSpan);
694             } else {
695                 var labelSpan = document.createElement("span");
696                 labelSpan.className = 'spec-label';
697                 labelSpan.textContent = categoryObject.shortname;
698                 titleElement.appendChild(labelSpan);
699             }
700         }
701
702         featureHeaderContainer.appendChild(titleElement);
703
704         var toggledContentContainer = document.createElement('div');
705         toggledContentContainer.className = "toggleable";
706         descriptionContainer.appendChild(toggledContentContainer);
707
708         var aliases = propertyNameAliases(propertyObject);
709         if (aliases.length) {
710             var propertyAliasDiv = document.createElement('div');
711             propertyAliasDiv.className = 'property-alias';
712             propertyAliasDiv.textContent = 'Also supported as: ' + aliases.join(', ');
713             toggledContentContainer.appendChild(propertyAliasDiv);
714         }
715
716         var longhands = propertyLonghands(propertyObject);
717         if (longhands.length) {
718             var longhandsDiv = document.createElement('div');
719             longhandsDiv.className = 'longhands';
720
721             longhandsDiv.textContent = 'Shorthand for ';
722
723             for (var i in longhands) {
724                 if (i > 0)
725                     longhandsDiv.appendChild(document.createTextNode(', '));
726                 var longhand = longhands[i];
727                 var longhandLink = document.createElement("a");
728                 longhandLink.href = "#property-" + longhand;
729                 longhandLink.textContent = longhand;
730                 longhandsDiv.appendChild(longhandLink);
731             }
732
733             toggledContentContainer.appendChild(longhandsDiv);
734         }
735
736         function collapsePrefixedValues(values)
737         {
738             var remainingValues = [];
739             var prefixMap = {};
740
741             for (var valueObj of values) {
742                 var valueName = valueObj.value;
743
744                 var result = prefixRegexp.exec(valueName);
745                 if (result) {
746                     var unprefixed = result[2];
747                     var unprefixedValue = findValueByName(values, unprefixed);
748                     if (unprefixedValue) {
749                         (prefixMap[unprefixedValue.value] = prefixMap[unprefixedValue.value] || []).push(valueName);
750                         continue;
751                     }
752                 }
753
754                 remainingValues.push(valueObj);
755             }
756
757             for (var prefixed in prefixMap) {
758                 var unprefixedValue = findValueByName(remainingValues, prefixed);
759                 unprefixedValue.aliases = prefixMap[prefixed];
760             }
761
762             return remainingValues;
763         }
764
765         if (propertyObject.values.length) {
766             var valuesHeader = document.createElement("h4");
767             valuesHeader.textContent = 'Supported Values:';
768             toggledContentContainer.appendChild(valuesHeader);
769
770             var valuesList = document.createElement("ul");
771             valuesList.className = 'values';
772
773             var values = collapsePrefixedValues(propertyObject.values);
774             for (var valueObj of values) {
775                 var li = document.createElement("li");
776
777                 valueObj.el = li;
778
779                 var link = undefined;
780                 var valueAliases = undefined;
781                 var status = undefined;
782                 if ('aliases' in valueObj)
783                     valueAliases = valueObj.aliases;
784
785                 if ('status' in valueObj)
786                     status = valueObj.status;
787
788                 if ('url' in valueObj)
789                     link = valueObj['url'];
790
791                 appendValueWithLink(li, valueObj.value, link);
792
793                 if (valueAliases) {
794                     var span = document.createElement('span');
795                     span.textContent = ' (' + valueAliases.join(', ') + ')';
796                     span.className = 'value-alias';
797                     li.appendChild(span);
798                 }
799
800                 if (status) {
801                     var span = document.createElement('span');
802                     span.textContent = ' (' + status + ')';
803                     span.className = 'value-status';
804                     li.appendChild(span);
805                 }
806
807                 valuesList.appendChild(li);
808             }
809             toggledContentContainer.appendChild(valuesList);
810         }
811
812         var statusContainer = document.createElement("span");
813         cornerStatus.className += propertyObject.status.status;
814         statusContainer.className = "property-status " + propertyObject.status.status;
815         if ("webkit-url" in propertyObject) {
816             var statusLink = document.createElement("a");
817             statusLink.href = propertyObject["webkit-url"];
818             statusLink.textContent = readableStatus[propertyObject.status.status];
819             statusContainer.appendChild(statusLink);
820         } else {
821             statusContainer.textContent = readableStatus[propertyObject.status.status];
822         }
823         featureHeaderContainer.appendChild(statusContainer);
824
825         var toggle = document.createElement('button');
826         toggle.className = 'toggle';
827
828         toggle.addEventListener('click', function (e) {
829             container.classList.toggle('opened');
830         });
831
832         featureHeaderContainer.appendChild(toggle);
833
834         if (specificationObject && "description" in specificationObject) {
835             var testDescription = document.createElement('p');
836             testDescription.className = "property-desc";
837             testDescription.innerHTML = specificationObject.description;
838             toggledContentContainer.appendChild(testDescription);
839         }
840
841         if (specificationObject && "comment" in specificationObject) {
842             if ("description" in specificationObject) {
843                 var hr = document.createElement("hr");
844                 hr.className = 'comment';
845                 toggledContentContainer.appendChild(hr);
846             }
847             var comment = document.createElement('p');
848             comment.className = 'comment';
849             comment.innerHTML = specificationObject.comment;
850             toggledContentContainer.appendChild(comment);
851         }
852
853         if (propertyObject.status && "comment" in propertyObject.status) {
854             var comment = document.createElement('p');
855             comment.className = 'comment';
856             comment.innerHTML = propertyObject.status.comment;
857             toggledContentContainer.appendChild(comment);
858         }
859
860         container.appendChild(descriptionContainer);
861
862         function getMostSpecificProperty(categoryObject, specificationObject, attributeName)
863         {
864             // The url in the specification object is more specific, so use it if present.
865             if (specificationObject && attributeName in specificationObject)
866                 return specificationObject[attributeName];
867
868             return categoryObject[attributeName];
869         }
870
871         var hasReferenceLink = categoryObject && "url" in categoryObject;
872         var hasDocumentationLink = (specificationObject && "documentation-url" in specificationObject) || (categoryObject && "documentation-url" in categoryObject);
873         var hasContactObject = specificationObject && "contact" in specificationObject;
874
875         if (hasDocumentationLink || hasReferenceLink || hasContactObject) {
876             var moreInfoList = document.createElement("ul");
877             moreInfoList.className = 'more-info';
878             if (hasDocumentationLink) {
879                 // The url in the specification object is more specific, so use it if present.
880                 var url = getMostSpecificProperty(categoryObject, specificationObject, 'documentation-url');
881                 moreInfoList.appendChild(createLinkWithHeading("li", "Documentation", url, url));
882             }
883
884             if (hasReferenceLink) {
885                 var url = getMostSpecificProperty(categoryObject, specificationObject, 'url');
886                 moreInfoList.appendChild(createLinkWithHeading("li", "Reference", url, url));
887
888                 if ('obsolete-url' in specificationObject){
889                     var url = specificationObject['obsolete-url'];
890                     moreInfoList.appendChild(createLinkWithHeading("li", "Reference", url, url));
891                 }
892             }
893
894             if (hasContactObject) {
895                 var li = document.createElement("li");
896                 li.textContent = "Contact: ";
897                 var contactObject = specificationObject.contact;
898                 if (contactObject.twitter) {
899                     li.appendChild(createLinkWithHeading("span", null, contactObject.twitter, makeTwitterLink(contactObject.twitter)));
900                 }
901                 if (contactObject.email) {
902                     if (contactObject.twitter) {
903                         li.appendChild(document.createTextNode(" - "));
904                     }
905                     var emailText = contactObject.email;
906                     if (contactObject.name) {
907                         emailText = contactObject.name;
908                     }
909                     li.appendChild(createLinkWithHeading("span", null, emailText, "mailto:" + contactObject.email));
910                 }
911                 moreInfoList.appendChild(li);
912             }
913
914             toggledContentContainer.appendChild(moreInfoList);
915         }
916
917         return container;
918     }
919
920     function canonicalizeIdentifier(identifier)
921     {
922         return identifier.toLocaleLowerCase().replace(/ /g, '-');
923     }
924
925     function renderSpecifications(categories, properties, selectedSpecifications)
926     {
927         var specificationsList = document.getElementById('specifications');
928         specificationsList.addEventListener('change', function() { updateSearch(properties); });
929
930         var selectedIndex = -1;
931         var allCategories = Object.keys(categories).sort();
932
933         for (var i = 0; i < allCategories.length; ++i) {
934             var categoryKey = allCategories[i];
935             var category = categories[categoryKey];
936             categoryKey = canonicalizeIdentifier(categoryKey);
937
938             var option = document.createElement("option");
939             option.setAttribute('value', categoryKey);
940             if (selectedSpecifications.indexOf(categoryKey) != -1)
941                 selectedIndex = i;
942
943             option.appendChild(document.createTextNode(" " + category['shortname']));
944             specificationsList.appendChild(option);
945         }
946         if (selectedIndex != -1)
947             specificationsList.selectedIndex = selectedIndex;
948     }
949
950     function getPropertyCategory(propertyObject)
951     {
952         if ('specification' in propertyObject && 'category' in propertyObject.specification)
953             return propertyObject.specification.category;
954
955         return undefined;
956     }
957
958     function renderProperties(categories, propertyObjects)
959     {
960         var propertiesContainer = document.getElementById('properties-container');
961         for (var propertyObject of propertyObjects) {
962             var category = getPropertyCategory(propertyObject);
963             propertiesContainer.appendChild(createPropertyView(categories[category], propertyObject));
964         }
965     }
966
967     function convertToTitleCase(string)
968     {
969         return string.replace(/\w\S*/g, function(txt){
970             return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
971         });
972     }
973
974     function initSearch(properties, categories)
975     {
976         var filtersForm = document.getElementById('property-filters');
977         var statusContainer = document.getElementById('status-filters');
978         var inputField = document.getElementById('search');
979         var featuresEls = document.querySelectorAll('.properties > li');
980         var statusFilters = {};
981
982         properties.forEach(function(property, i) {
983             property.el = featuresEls[i];
984             property.visible = true;
985
986             // FIXME: need status per value as well.
987             if (property.status != undefined) {
988                 propertyStatusKey = property.status.status.toLocaleLowerCase();
989
990                 if (!statusFilters[propertyStatusKey])
991                     statusFilters[propertyStatusKey] = property.status.status;
992
993                 if (statusOrder.indexOf(propertyStatusKey) == -1)
994                     window.console.log('Status ' + propertyStatusKey + ' is not one of the predefined status keys ', statusOrder);
995             }
996         });
997
998         var selectedStatuses = statusesFromURL();
999         var selectedSpecs = specificationsFromURL();
1000
1001         for (var key of statusOrder) {
1002             var status = statusFilters[key];
1003             var canonicalStatus = canonicalizeIdentifier(status);
1004
1005             var entry = document.createElement("li");
1006             var label = document.createElement("label");
1007             var input = document.createElement("input");
1008             input.setAttribute('type','checkbox');
1009             input.setAttribute('value', canonicalStatus);
1010             if (selectedStatuses.indexOf(canonicalStatus) != -1)
1011                 input.checked = true;
1012             input.className = 'status-checkbox';
1013             input.addEventListener('change', function() { updateSearch(properties); });
1014             label.appendChild(input);
1015             label.className = canonicalStatus;
1016             label.appendChild(document.createTextNode(" " + readableStatus[status]));
1017             entry.appendChild(label);
1018             statusContainer.appendChild(entry);
1019         }
1020
1021         // Append the special "By Specification" checkbox
1022         {
1023             var entry = document.createElement("li");
1024             var label = document.createElement("label");
1025             var input = document.createElement("input");
1026             input.id = 'by-spec-checkbox';
1027             input.setAttribute('type','checkbox');
1028             if (selectedSpecs.length > 0)
1029                 input.checked = true;
1030             input.addEventListener('change', function() { updateSearch(properties); });
1031             label.appendChild(input);
1032             label.appendChild(document.createTextNode(" By Specification:"));
1033             entry.appendChild(label);
1034
1035             var specsList = document.createElement('select');
1036             specsList.className = 'specifications';
1037             specsList.id = 'specifications';
1038             entry.appendChild(specsList);
1039
1040             statusContainer.appendChild(entry);
1041         }
1042
1043         var prefixContainer = document.getElementById('prefix-filters');
1044         var prefixFilters = {};
1045         prefixFilters['prefix-supported-property'] = 'Property with and without prefix';
1046         prefixFilters['prefix-only-property'] = 'Prefixed-only Properties';
1047         prefixFilters['prefix-supported-value'] = 'Prefixed Values';
1048         prefixFilters['prefix-only-value'] = 'Prefixed-only Values';
1049
1050         for (var key in prefixFilters) {
1051             var status = prefixFilters[key];
1052             var entry = document.createElement("li");
1053             var label = document.createElement("label");
1054             var input = document.createElement("input");
1055             input.setAttribute('type','checkbox');
1056             input.setAttribute('value', key);
1057             input.addEventListener('change', function() { updateSearch(properties); });
1058             label.appendChild(input);
1059             label.className = status.toLocaleLowerCase().replace(/ /g, '-');
1060             label.appendChild(document.createTextNode(" " + status));
1061             entry.appendChild(label);
1062             prefixContainer.appendChild(entry);
1063         }
1064
1065         filtersForm.addEventListener('click', function (e) {
1066             if ( filtersForm.className.indexOf('opened') !== -1 ) {
1067                 filtersForm.className = filtersForm.className.replace(' opened','');
1068             } else filtersForm.className += ' opened';
1069         });
1070
1071         var searchTerm = searchTermFromURL();
1072         if (searchTerm.length) {
1073             inputField.value = searchTerm;
1074             inputField.placeholder = '';
1075         }
1076         inputField.addEventListener('input', function() { updateSearch(properties); });
1077
1078         var inputs = [].slice.call(filtersForm.getElementsByTagName('input'));
1079         inputs.forEach(function (input,i) {
1080             input.addEventListener('click', function (e) {
1081                 e.stopPropagation();
1082             });
1083         });
1084
1085         renderSpecifications(categories, properties, selectedSpecs);
1086     }
1087
1088     function getValuesOfCheckedItems(items)
1089     {
1090         var checkedValues = [];
1091         items.forEach(function(item,i) {
1092             if (item.checked)
1093                 checkedValues.push(item.value);
1094         });
1095
1096         return checkedValues;
1097     }
1098
1099     function getValuesOfSelectedItems(select)
1100     {
1101         var selectedValues = [];
1102
1103         if (select.selectedIndex != -1)
1104             selectedValues.push(select.options[select.selectedIndex].value);
1105
1106         return selectedValues;
1107     }
1108
1109     function selectedSpecifications()
1110     {
1111         var specificationsList = document.getElementById('specifications');
1112         if (!document.getElementById('by-spec-checkbox').checked) {
1113             specificationsList.disabled = true;
1114             return [];
1115         }
1116         specificationsList.disabled = false;
1117         return getValuesOfSelectedItems(specificationsList);
1118     }
1119
1120     function updateSearch(properties)
1121     {
1122         var inputField = document.getElementById('search');
1123         var statusContainer = document.getElementById('status-filters');
1124
1125         var searchTerm = inputField.value.trim().toLowerCase();
1126         var activeStatusFilters = getValuesOfCheckedItems([].slice.call(statusContainer.querySelectorAll('.status-checkbox')));
1127
1128         var prefixContainer = document.getElementById('prefix-filters');
1129         var activePrefixFilters = getValuesOfCheckedItems([].slice.call(prefixContainer.getElementsByTagName('input')));
1130
1131         var numVisible = searchProperties(properties, searchTerm, selectedSpecifications(), activeStatusFilters, activePrefixFilters);
1132         document.getElementById('property-pluralize').textContent = numVisible == 1 ? 'property' : 'properties';
1133         document.getElementById('property-count').textContent = numVisible;
1134
1135         updateSpecsState();
1136         updateURL(searchTerm, selectedSpecifications(), activeStatusFilters, activePrefixFilters);
1137     }
1138
1139     function updateSpecsState()
1140     {
1141         var specsEnabled = document.getElementById('by-spec-checkbox').checked;
1142         var specificationsList = document.getElementById('specifications');
1143
1144         var radiobuttons = [].slice.call(specificationsList.getElementsByTagName('input'));
1145         radiobuttons.forEach(function(radiobutton,i) {
1146             radiobutton.disabled = !specsEnabled;
1147         });
1148     }
1149
1150     function updateURL(searchTerm, selectedSpecifications, activeStatusFilters, activePrefixFilters)
1151     {
1152         var searchString = '';
1153
1154         function appendDelimiter()
1155         {
1156             searchString += searchString.length ? '&' : '?';
1157         }
1158
1159         if (searchTerm.length > 0) {
1160             appendDelimiter();
1161             searchString += 'search=' + encodeURIComponent(searchTerm);
1162         }
1163
1164         if (activeStatusFilters.length) {
1165             appendDelimiter();
1166             searchString += 'status=' + activeStatusFilters.join(',');
1167         }
1168
1169         if (selectedSpecifications.length) {
1170             appendDelimiter();
1171             searchString += 'specs=' + selectedSpecifications.join(',');
1172         }
1173
1174         if (activePrefixFilters.length) {
1175             appendDelimiter();
1176             searchString += 'prefix=' + activePrefixFilters.join(',');
1177         }
1178
1179         var current = window.location.href;
1180         window.location.href = current.replace(/#(.*)$/, '') + '#' + searchString;
1181     }
1182
1183     function searchTermFromURL()
1184     {
1185         var search = window.location.search;
1186         var searchRegExp = /\#.*search=([^&]+)/;
1187
1188         var result;
1189         if (result = window.location.href.match(searchRegExp))
1190             return decodeURIComponent(result[1]);
1191
1192         return '';
1193     }
1194
1195     function statusesFromURL()
1196     {
1197         var search = window.location.search;
1198         var statusRegExp = /\#.*status=([^&]+)/;
1199
1200         var result;
1201         if (result = window.location.href.match(statusRegExp))
1202             return result[1].split(',');
1203
1204         return [];
1205     }
1206
1207     function specificationsFromURL()
1208     {
1209         var search = window.location.search;
1210         var specsRegExp = /\#.*specs=([^&]+)/;
1211
1212         var result;
1213         if (result = window.location.href.match(specsRegExp))
1214             return result[1].split(',');
1215
1216         return [];
1217     }
1218
1219     function valueOrAliasIsPrefixed(valueObj)
1220     {
1221         if (prefixRegexp.exec(valueObj.value))
1222             return true;
1223
1224         if ('alias' in valueObj) {
1225             for (var alias of valueObj.aliases) {
1226                 if (prefixRegexp.exec(alias))
1227                     return true;
1228             }
1229         }
1230
1231         return false;
1232     }
1233
1234     function filterValues(propertyObject, searchTerm, categories, statusFilters)
1235     {
1236         for (var valueObj of propertyObject.values) {
1237             if (!valueObj.el)
1238                 continue;
1239
1240             var visible = false;
1241             visible = valueObj.value.toLowerCase().indexOf(searchTerm) !== -1;
1242
1243             if (!visible) {
1244                 for (var currValueObj of propertyObject.values) {
1245                     if (valueOrAliasIsPrefixed(currValueObj)) {
1246                         visible = true;
1247                         break;
1248                     }
1249                 }
1250             }
1251
1252             if (!visible) {
1253                 for (var currValueObj of propertyObject.values) {
1254                     if (prefixRegexp.exec(currValueObj.value)) {
1255                         visible = true;
1256                         break;
1257                     }
1258                 }
1259             }
1260
1261             if (visible)
1262                 valueObj.el.classList.remove('hidden');
1263             else
1264                 valueObj.el.classList.add('hidden');
1265         }
1266     }
1267
1268     function searchProperties(properties, searchTerm, categories, statusFilters, prefixFilters)
1269     {
1270         var visibleCount = 0;
1271         properties.forEach(function(propertyObject) {
1272             var matchesStatusSearch = isStatusFiltered(propertyObject, statusFilters);
1273             var matchesPrefixSearch = isPrefixFiltered(propertyObject, prefixFilters);
1274
1275             var visible = propertyIsSearchMatch(propertyObject, searchTerm) && isCategoryMatch(propertyObject, categories) && matchesStatusSearch && matchesPrefixSearch;
1276             if (visible && !propertyObject.visible)
1277                 propertyObject.el.className = 'property';
1278             else if (!visible && propertyObject.visible)
1279                 propertyObject.el.className = 'property is-hidden';
1280
1281             if (visible) {
1282                 filterValues(propertyObject, searchTerm);
1283                 ++visibleCount;
1284             }
1285
1286             propertyObject.visible = visible;
1287         });
1288
1289         return visibleCount;
1290     }
1291
1292     function propertyIsSearchMatch(propertyObject, searchTerm)
1293     {
1294         if (searchTerm.length == 0)
1295             return true;
1296
1297         if (propertyObject.name.toLowerCase().indexOf(searchTerm) !== -1)
1298             return true;
1299
1300         if ('keywords' in propertyObject) {
1301             for (var keyword of propertyObject.keywords) {
1302                 if (keyword.toLowerCase().indexOf(searchTerm) !== -1)
1303                     return true;
1304             }
1305         }
1306
1307         for (var valueObj of propertyObject.values) {
1308             if (valueObj.value.toLowerCase().indexOf(searchTerm) !== -1)
1309                 return true;
1310         }
1311
1312         return false;
1313     }
1314
1315     function getSpecificationCategory(propertyObject)
1316     {
1317         if ('specification' in propertyObject) {
1318             var specification = propertyObject.specification;
1319             if ('category' in specification) {
1320                 return specification.category;
1321             }
1322         }
1323         return undefined;
1324     }
1325
1326     function getSpecificationObsoleteCategory(propertyObject)
1327     {
1328         if ('specification' in propertyObject) {
1329             var specification = propertyObject.specification;
1330             if ('obsolete-category' in specification) {
1331                 return specification['obsolete-category'];
1332             }
1333         }
1334         return undefined;
1335     }
1336
1337     function isCategoryMatch(propertyObject, categories)
1338     {
1339         if (!categories.length)
1340             return true;
1341
1342         var category;
1343         if (category = getSpecificationCategory(propertyObject)) {
1344             if (categories.indexOf(category) !== -1) {
1345                 return true;
1346             }
1347         }
1348
1349         if (category = getSpecificationObsoleteCategory(propertyObject)) {
1350             if (categories.indexOf(category) !== -1) {
1351                 return true;
1352             }
1353         }
1354         return false;
1355     }
1356
1357     function propertyOrAliasIsPrefixed(propertyObject)
1358     {
1359         if (prefixRegexp.exec(propertyObject.name))
1360             return true;
1361
1362         for (var alias of propertyNameAliases(propertyObject)) {
1363             if (prefixRegexp.exec(alias))
1364                 return true;
1365         }
1366
1367         return false;
1368     }
1369
1370     function isStatusFiltered(propertyObject, activeFilters)
1371     {
1372         if (activeFilters.length == 0)
1373             return true;
1374         if (propertyObject.status === undefined)
1375             return false;
1376         if (activeFilters.indexOf(propertyObject.status.status) !== -1)
1377             return true;
1378
1379         return false;
1380     }
1381
1382     function isPrefixFiltered(propertyObject, activeFilters)
1383     {
1384         if (activeFilters.length == 0)
1385             return true;
1386
1387         if (activeFilters.indexOf('prefix-only-property') !== -1)
1388             return prefixRegexp.exec(propertyObject.name);
1389
1390         if (activeFilters.indexOf('prefix-supported-property') !== -1)
1391             return propertyOrAliasIsPrefixed(propertyObject);
1392
1393         if (activeFilters.indexOf('prefix-supported-value') !== -1) {
1394             for (var valueObj of propertyObject.values) {
1395                 if (valueOrAliasIsPrefixed(valueObj))
1396                     return true;
1397             }
1398         }
1399
1400         if (activeFilters.indexOf('prefix-only-value') !== -1) {
1401             for (var valueObj of propertyObject.values) {
1402                 if (prefixRegexp.exec(valueObj.value))
1403                     return true;
1404             }
1405         }
1406
1407         return false;
1408     }
1409
1410     function findValueByName(values, name)
1411     {
1412         return values.find(function(element) {
1413             return element.value === name;
1414         })
1415     }
1416
1417     function mergeProperties(unprefixedPropertyObj, prefixedPropertyObj)
1418     {
1419         (unprefixedPropertyObj['codegen-properties'].aliases = unprefixedPropertyObj['codegen-properties'].aliases || []).push(prefixedPropertyObj.name);
1420
1421         for (var valueObj of prefixedPropertyObj.values) {
1422             if (!findValueByName(unprefixedPropertyObj.values, valueObj.value))
1423                 prefixedPropertyObj.values.push(valueObj);
1424         }
1425
1426         return unprefixedPropertyObj;
1427     }
1428
1429     // Sometimes we have separate entries for -webkit-foo and foo.
1430     function collapsePrefixedProperties(properties)
1431     {
1432         function findPropertyByName(properties, name)
1433         {
1434             return properties.find(function(element) {
1435                 return element.name === name;
1436             })
1437         }
1438
1439         var remainingProperties = [];
1440         var prefixMap = {};
1441
1442         for (var propertyObj of properties) {
1443             var propertyName = propertyObj.name;
1444
1445             var result = prefixRegexp.exec(propertyName);
1446             if (result) {
1447                 var unprefixed = result[2];
1448                 var unprefixedProperty = findPropertyByName(properties, unprefixed);
1449                 if (unprefixedProperty) {
1450                     mergeProperties(unprefixedProperty, propertyObj);
1451                     continue;
1452                 }
1453             }
1454
1455             remainingProperties.push(propertyObj);
1456         }
1457
1458         return remainingProperties;
1459     }
1460
1461     function canonicalizeValues(propertyObject)
1462     {
1463         var valueObjects = [];
1464         // Convert all values to objects.
1465         if ('values' in propertyObject) {
1466             for (var value of propertyObject.values) {
1467                 if (typeof value === 'object')
1468                     valueObjects.push(value);
1469                 else
1470                     valueObjects.push({ 'value' : value });
1471             }
1472         }
1473         propertyObject.values = valueObjects;
1474     }
1475
1476     function canonicalizeStatus(propertyObject, categories)
1477     {
1478         // Inherit "status" from the cateogry if not explicitly specified.
1479         if (!('status' in propertyObject)) {
1480             var category = getSpecificationCategory(propertyObject)
1481             if (category) {
1482                 var categoryObject = categories[category];
1483                 if (categoryObject) {
1484                     if ('status' in categoryObject) {
1485                         propertyObject.status = {
1486                             'status' : categoryObject.status
1487                         };
1488                     }
1489                 }
1490             }
1491         } else {
1492             // Convert all values to objects.
1493             if (typeof propertyObject.status === 'string')
1494                 propertyObject.status = { 'status': propertyObject.status };
1495         }
1496
1497         if (!('status' in propertyObject)) {
1498             propertyObject.status = {
1499                 'status' : 'supported',
1500                 'enabled-by-default' : true
1501             };
1502         } else if (!('status' in propertyObject.status))
1503             propertyObject.status.status = 'supported';
1504
1505         propertyObject.status.status = canonicalizeIdentifier(propertyObject.status.status);
1506     }
1507
1508     function renderContent(results)
1509     {
1510         var mainContent = document.getElementById("property-list");
1511         var successSubtree = document.importNode(document.getElementById("success-template").content, true);
1512         mainContent.appendChild(successSubtree);
1513
1514         var properties = results[0]['properties'];
1515         var everythingToShow = [];
1516
1517         var categories = results[0]['categories'];
1518
1519         for (var property in properties) {
1520             var propertyObject = properties[property];
1521             propertyObject.name = property;
1522
1523             canonicalizeValues(propertyObject);
1524             canonicalizeStatus(propertyObject, categories);
1525
1526             everythingToShow.push(propertyObject);
1527         }
1528
1529         everythingToShow = collapsePrefixedProperties(everythingToShow);
1530         sortAlphabetically(everythingToShow);
1531
1532         renderProperties(categories, everythingToShow);
1533
1534         initSearch(everythingToShow, categories);
1535
1536         updateSearch(everythingToShow);
1537     }
1538
1539     function displayError(error)
1540     {
1541         window.console.log('displayError', error)
1542         var mainContent = document.getElementById("property-list");
1543         var successSubtree = document.importNode(document.getElementById("error-template").content, true);
1544
1545         var errorMessage = "Unable to load " + error.url;
1546
1547         if (error.request.status !== 200) {
1548             errorMessage += ", status: " + error.request.status + " - " + error.request.statusText;
1549         } else if (!error.response) {
1550             errorMessage += ", the JSON file cannot be processed.";
1551         }
1552
1553         successSubtree.querySelector("#error-message").textContent = errorMessage;
1554
1555         mainContent.appendChild(successSubtree);
1556     }
1557
1558     Promise.all([loadCSSProperties]).then(renderContent).catch(displayError);
1559 }
1560
1561 document.addEventListener("DOMContentLoaded", initializeStatusPage);
1562 </script>
1563
1564 <?php get_footer(); ?>