PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 3.37
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v3.37
3.37 3.36 3.35 3.34 3.33 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.26 2.27 2.28 2.29 2.30 2.31 2.32 2.33 2.34 2.40 2.41 2.42 2.43 All 142 releases
photonic / include / js / front-end / src / Util.js

Util.js in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 3.37, at include/js/front-end/src/Util.js

264 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Utilities for Photonic
2 export const hasClass = (element, className) => {
3 if (element.classList) {
4 return element.classList.contains(className);
5 }
6 else {
7 return new RegExp('(^| )' + className + '( |$)', 'gi').test(element.className);
8 }
9 };
10
11 function ajax(method, url, args, callback) {
12 const xhr = new XMLHttpRequest();
13 xhr.open(method, url);
14 xhr.onreadystatechange = function() {
15 if (xhr.readyState === 4) {
16 if (xhr.status === 200) {
17 const data = xhr.responseText;
18 callback(data);
19 }
20 }
21 };
22 let form = new FormData();
23 for (const [key, value] of Object.entries(args)) {
24 form.append(key, value);
25 }
26 xhr.send(form);
27 }
28
29 export const post = (url, args, callback) => {
30 ajax('POST', url, args, callback);
31 };
32
33 export const get = (url, args, callback) => {
34 ajax('GET', url, args, callback);
35 };
36
37 export const next = (elem, selector) => {
38 let sibling = elem.nextElementSibling;
39
40 if (!selector) return sibling;
41 while (sibling) {
42 if (sibling.matches(selector)) return sibling;
43 sibling = sibling.nextElementSibling;
44 }
45 };
46
47 export const getElement = value => {
48 const parser = new DOMParser();
49 const doc = parser.parseFromString(value, 'text/html');
50 return doc.body;
51 };
52
53 export const getText = value => {
54 // Not using innerHTML because of vulnerability to XSS
55 if (value == null) {
56 return '';
57 }
58 return value.replace(/<[^>]+>/g, '');
59 };
60
61 export const slideUpDown = (element, state) => {
62 if (element != null && element.classList) {
63 if (!element.classList.contains('photonic-can-slide')) {
64 element.classList.add('photonic-can-slide');
65 }
66 if ('show' === state) {
67 element.classList.remove('photonic-can-slide-hide');
68 element.style.height = `${element.scrollHeight}px`;
69 }
70 else {
71 element.classList.add('photonic-can-slide-hide');
72 element.style.height = 0
73 }
74 }
75 };
76
77 export const slideUpTitle = (element, state) => {
78 if (element && element.classList) {
79 if ('show' === state) {
80 let currentPadding = 0;
81 if (element.offsetHeight) {
82 currentPadding = parseInt(getComputedStyle(element).paddingTop.slice(0, -2)) * 2;
83 }
84 element.style.height = (element.scrollHeight + 6 - currentPadding) + 'px';
85 element.classList.add('slideup-show');
86 }
87 else {
88 element.style.height = '';
89 element.classList.remove('slideup-show');
90 }
91 }
92 }
93
94 export const fadeIn = (el) => {
95 if (!hasClass(el, 'fade-in')) {
96 el.style.display = 'block';
97 el.style.visibility = 'visible';
98 el.classList.add('fade-in');
99 }
100 }
101
102 export const fadeOut = (el, duration) => {
103 let s = el.style,
104 step = 25/(duration || 500);
105 s.opacity = s.opacity || 1;
106 (function fade() {
107 s.opacity -= step;
108 if (s.opacity < 0) {
109 s.display = "none";
110 el.classList.remove('fade-in');
111 }
112 else {
113 setTimeout(fade, 25);
114 }
115 })();
116 }
117
118 // get the default display style of an element
119 const defaultDisplay = tag => {
120 const iframe = document.createElement('iframe');
121 iframe.setAttribute('frameborder', 0);
122 iframe.setAttribute('width', 0);
123 iframe.setAttribute('height', 0);
124 document.documentElement.appendChild(iframe);
125
126 const doc = (iframe.contentWindow || iframe.contentDocument).document;
127
128 // IE support
129 doc.write();
130 doc.close();
131
132 const testEl = doc.createElement(tag);
133 doc.documentElement.appendChild(testEl);
134 const display = (window.getComputedStyle ? getComputedStyle(testEl, null) : testEl.currentStyle).display;
135 iframe.parentNode.removeChild(iframe);
136 return display;
137 };
138
139 // actual show/hide function used by show() and hide() below
140 const showHide = (el, show) => {
141 let value = el.getAttribute('data-olddisplay'),
142 display = el.style.display,
143 computedDisplay = (window.getComputedStyle ? getComputedStyle(el, null) : el.currentStyle).display;
144
145 if (show) {
146 if (!value && display === 'none') el.style.display = '';
147 if (el.style.display === '' && (computedDisplay === 'none')) value = value || defaultDisplay(el.nodeName);
148 }
149 else {
150 if (display && display !== 'none' || !(computedDisplay === 'none'))
151 el.setAttribute('data-olddisplay', (computedDisplay === 'none') ? display : computedDisplay);
152 }
153 if (!show || el.style.display === 'none' || el.style.display === '')
154 el.style.display = show ? value || '' : 'none';
155 };
156
157 // helper functions
158 export const show = (el) => showHide(el, true);
159 export const hide = (el) => showHide(el);
160
161 //JavaScript HTML Sanitizer v2.0.3, (c) Alexander Yumashev, Jitbit Software.
162 //homepage https://github.com/jitbit/HtmlSanitizer
163 //License: MIT https://github.com/jitbit/HtmlSanitizer/blob/master/LICENSE
164 export const HTMLSanitizer = new (function () {
165 const _tagWhitelist = {
166 'A': true, 'ABBR': true, 'B': true, 'BLOCKQUOTE': true, 'BODY': true, 'BR': true, 'CENTER': true, 'CODE': true, 'DD': true, 'DIV': true, 'DL': true, 'DT': true, 'EM': true, 'FONT': true,
167 'H1': true, 'H2': true, 'H3': true, 'H4': true, 'H5': true, 'H6': true, 'HR': true, 'I': true, 'IMG': true, 'LABEL': true, 'LI': true, 'OL': true, 'P': true, 'PRE': true,
168 'SMALL': true, 'SOURCE': true, 'SPAN': true, 'STRONG': true, 'SUB': true, 'SUP': true, 'TABLE': true, 'TBODY': true, 'TR': true, 'TD': true, 'TH': true, 'THEAD': true, 'UL': true, 'U': true, 'VIDEO': true
169 };
170
171 const _contentTagWhiteList = { 'FORM': true, 'GOOGLE-SHEETS-HTML-ORIGIN': true }; //tags that will be converted to DIVs
172 const _attributeWhitelist = { 'align': true, 'color': true, 'controls': true, 'height': true, 'href': true, 'id': true, 'src': true, 'style': true, 'target': true, 'title': true, 'type': true, 'width': true };
173 const _cssWhitelist = { 'background-color': true, 'color': true, 'font-size': true, 'font-weight': true, 'text-align': true, 'text-decoration': true, 'width': true };
174 const _schemaWhiteList = [ 'http:', 'https:', 'data:', 'm-files:', 'file:', 'ftp:', 'mailto:', 'pw:' ]; //which "protocols" are allowed in "href", "src" etc
175 const _uriAttributes = { 'href': true, 'action': true };
176 const _parser = new DOMParser();
177
178 this.SanitizeHTML = (input, extraSelector) => {
179 if (input == null) return null;
180
181 input = input.trim();
182 if (input === "") return ""; //to save performance
183
184 //firefox "bogus node" workaround for wysiwyg's
185 if (input === "<br>") return "";
186
187 if (input.indexOf("<body") === -1) input = "<body>" + input + "</body>"; //add "body" otherwise some tags are skipped, like <style>
188
189 let doc = _parser.parseFromString(input, "text/html");
190
191 //DOM clobbering check (damn you firefox)
192 if (doc.body.tagName !== 'BODY')
193 doc.body.remove();
194 if (typeof doc.createElement !== 'function')
195 doc.createElement.remove();
196
197 function makeSanitizedCopy(node) {
198 let newNode;
199 if (node.nodeType === Node.TEXT_NODE) {
200 newNode = node.cloneNode(true);
201 } else if (node.nodeType === Node.ELEMENT_NODE && (_tagWhitelist[node.tagName] || _contentTagWhiteList[node.tagName] || (extraSelector && node.matches(extraSelector)))) { //is tag allowed?
202
203 if (_contentTagWhiteList[node.tagName])
204 newNode = doc.createElement('DIV'); //convert to DIV
205 else
206 newNode = doc.createElement(node.tagName);
207
208 for (let i = 0; i < node.attributes.length; i++) {
209 let attr = node.attributes[i];
210 if (_attributeWhitelist[attr.name]) {
211 if (attr.name === "style") {
212 for (let s = 0; s < node.style.length; s++) {
213 let styleName = node.style[s];
214 if (_cssWhitelist[styleName])
215 newNode.style.setProperty(styleName, node.style.getPropertyValue(styleName));
216 }
217 }
218 else {
219 if (_uriAttributes[attr.name]) { //if this is a "uri" attribute, that can have "javascript:" or something
220 if (attr.value.indexOf(":") > -1 && !startsWithAny(attr.value, _schemaWhiteList))
221 continue;
222 }
223 newNode.setAttribute(attr.name, attr.value);
224 }
225 }
226 }
227 for (let i = 0; i < node.childNodes.length; i++) {
228 let subCopy = makeSanitizedCopy(node.childNodes[i]);
229 newNode.appendChild(subCopy, false);
230 }
231
232 //remove useless empty spans (lots of those when pasting from MS Outlook)
233 if ((newNode.tagName === "SPAN" || newNode.tagName === "B" || newNode.tagName === "I" || newNode.tagName === "U")
234 && newNode.innerHTML.trim() === "") {
235 return doc.createDocumentFragment();
236 }
237
238 } else {
239 newNode = doc.createDocumentFragment();
240 }
241 return newNode;
242 }
243
244 let resultElement = makeSanitizedCopy(doc.body);
245
246 return resultElement.innerHTML
247 .replace(/div><div/g, "div>\n<div"); //replace is just for cleaner code
248 }
249
250 function startsWithAny(str, substrings) {
251 for (let i = 0; i < substrings.length; i++) {
252 if (str.indexOf(substrings[i]) === 0) {
253 return true;
254 }
255 }
256 return false;
257 }
258
259 this.AllowedTags = _tagWhitelist;
260 this.AllowedAttributes = _attributeWhitelist;
261 this.AllowedCssStyles = _cssWhitelist;
262 this.AllowedSchemas = _schemaWhiteList;
263 });
264