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