UNPKG

8.03 kBJavaScriptView Raw
1(function () {
2 if (typeof self === 'undefined' || !self.Prism || !self.document) {
3 return;
4 }
5
6 /**
7 * @callback Adapter
8 * @param {any} response
9 * @param {HTMLPreElement} [pre]
10 * @returns {string | null}
11 */
12
13 /**
14 * The list of adapter which will be used if `data-adapter` is not specified.
15 *
16 * @type {Array<{adapter: Adapter, name: string}>}
17 */
18 var adapters = [];
19
20 /**
21 * Adds a new function to the list of adapters.
22 *
23 * If the given adapter is already registered or not a function or there is an adapter with the given name already,
24 * nothing will happen.
25 *
26 * @param {Adapter} adapter The adapter to be registered.
27 * @param {string} [name] The name of the adapter. Defaults to the function name of `adapter`.
28 */
29 function registerAdapter(adapter, name) {
30 name = name || adapter.name;
31 if (typeof adapter === "function" && !getAdapter(adapter) && !getAdapter(name)) {
32 adapters.push({ adapter: adapter, name: name });
33 }
34 }
35 /**
36 * Returns the given adapter itself, if registered, or a registered adapter with the given name.
37 *
38 * If no fitting adapter is registered, `null` will be returned.
39 *
40 * @param {string|Function} adapter The adapter itself or the name of an adapter.
41 * @returns {Adapter} A registered adapter or `null`.
42 */
43 function getAdapter(adapter) {
44 if (typeof adapter === "function") {
45 for (var i = 0, item; item = adapters[i++];) {
46 if (item.adapter.valueOf() === adapter.valueOf()) {
47 return item.adapter;
48 }
49 }
50 }
51 else if (typeof adapter === "string") {
52 for (var i = 0, item; item = adapters[i++];) {
53 if (item.name === adapter) {
54 return item.adapter;
55 }
56 }
57 }
58 return null;
59 }
60 /**
61 * Remove the given adapter or the first registered adapter with the given name from the list of
62 * registered adapters.
63 *
64 * @param {string|Function} adapter The adapter itself or the name of an adapter.
65 */
66 function removeAdapter(adapter) {
67 if (typeof adapter === "string") {
68 adapter = getAdapter(adapter);
69 }
70 if (typeof adapter === "function") {
71 var index = adapters.findIndex(function (item) {
72 return item.adapter === adapter;
73 });
74 if (index >= 0) {
75 adapters.splice(index, 1);
76 }
77 }
78 }
79
80 registerAdapter(function github(rsp, el) {
81 if (rsp && rsp.meta && rsp.data) {
82 if (rsp.meta.status && rsp.meta.status >= 400) {
83 return "Error: " + (rsp.data.message || rsp.meta.status);
84 }
85 else if (typeof (rsp.data.content) === "string") {
86 return typeof (atob) === "function"
87 ? atob(rsp.data.content.replace(/\s/g, ""))
88 : "Your browser cannot decode base64";
89 }
90 }
91 return null;
92 }, 'github');
93 registerAdapter(function gist(rsp, el) {
94 if (rsp && rsp.meta && rsp.data && rsp.data.files) {
95 if (rsp.meta.status && rsp.meta.status >= 400) {
96 return "Error: " + (rsp.data.message || rsp.meta.status);
97 }
98
99 var files = rsp.data.files;
100 var filename = el.getAttribute("data-filename");
101 if (filename == null) {
102 // Maybe in the future we can somehow render all files
103 // But the standard <script> include for gists does that nicely already,
104 // so that might be getting beyond the scope of this plugin
105 for (var key in files) {
106 if (files.hasOwnProperty(key)) {
107 filename = key;
108 break;
109 }
110 }
111 }
112
113 if (files[filename] !== undefined) {
114 return files[filename].content;
115 }
116 return "Error: unknown or missing gist file " + filename;
117 }
118 return null;
119 }, 'gist');
120 registerAdapter(function bitbucket(rsp, el) {
121 if (rsp && rsp.node && typeof (rsp.data) === "string") {
122 return rsp.data;
123 }
124 return null;
125 }, 'bitbucket');
126
127
128 var jsonpCallbackCounter = 0;
129
130 var LOADING_MESSAGE = 'Loading…';
131 var MISSING_ADAPTER_MESSAGE = function (name) {
132 return '✖ Error: JSONP adapter function "' + name + '" doesn\'t exist';
133 };
134 var TIMEOUT_MESSAGE = function (url) {
135 return '✖ Error: Timeout loading ' + url;
136 };
137 var UNKNOWN_FAILURE_MESSAGE = '✖ Error: Cannot parse response (perhaps you need an adapter function?)';
138
139 var STATUS_ATTR = 'data-jsonp-status';
140 var STATUS_LOADING = 'loading';
141 var STATUS_LOADED = 'loaded';
142 var STATUS_FAILED = 'failed';
143
144 var SELECTOR = 'pre[data-jsonp]:not([' + STATUS_ATTR + '="' + STATUS_LOADED + '"])'
145 + ':not([' + STATUS_ATTR + '="' + STATUS_LOADING + '"])';
146
147
148 Prism.hooks.add('before-highlightall', function (env) {
149 env.selector += ', ' + SELECTOR;
150 });
151
152 Prism.hooks.add('before-sanity-check', function (env) {
153 var pre = /** @type {HTMLPreElement} */ (env.element);
154 if (pre.matches(SELECTOR)) {
155 env.code = ''; // fast-path the whole thing and go to complete
156
157 // mark as loading
158 pre.setAttribute(STATUS_ATTR, STATUS_LOADING);
159
160 // add code element with loading message
161 var code = pre.appendChild(document.createElement('CODE'));
162 code.textContent = LOADING_MESSAGE;
163
164 // set language
165 var language = env.language;
166 code.className = 'language-' + language;
167
168 // preload the language
169 var autoloader = Prism.plugins.autoloader;
170 if (autoloader) {
171 autoloader.loadLanguages(language);
172 }
173
174 var adapterName = pre.getAttribute('data-adapter');
175 var adapter = null;
176 if (adapterName) {
177 if (typeof window[adapterName] === 'function') {
178 adapter = window[adapterName];
179 } else {
180 // mark as failed
181 pre.setAttribute(STATUS_ATTR, STATUS_FAILED);
182
183 code.textContent = MISSING_ADAPTER_MESSAGE(adapterName);
184 return;
185 }
186 }
187
188 var callbackName = 'prismjsonp' + jsonpCallbackCounter++;
189
190 var uri = document.createElement('a');
191 var src = uri.href = pre.getAttribute('data-jsonp');
192 uri.href += (uri.search ? '&' : '?') + (pre.getAttribute('data-callback') || 'callback') + '=' + callbackName;
193
194
195 var timeout = setTimeout(function () {
196 // we could clean up window[cb], but if the request finally succeeds, keeping it around is a good thing
197
198 // mark as failed
199 pre.setAttribute(STATUS_ATTR, STATUS_FAILED);
200
201 code.textContent = TIMEOUT_MESSAGE(src);
202 }, Prism.plugins.jsonphighlight.timeout);
203
204
205 var script = document.createElement('script');
206 script.src = uri.href;
207
208 // the JSONP callback function
209 window[callbackName] = function (response) {
210 // clean up
211 document.head.removeChild(script);
212 clearTimeout(timeout);
213 delete window[callbackName];
214
215 // interpret the received data using the adapter(s)
216 var data = null;
217 if (adapter) {
218 data = adapter(response, pre);
219 } else {
220 for (var i = 0, l = adapters.length; i < l; i++) {
221 data = adapters[i].adapter(response, pre);
222 if (data !== null) {
223 break;
224 }
225 }
226 }
227
228 if (data === null) {
229 // mark as failed
230 pre.setAttribute(STATUS_ATTR, STATUS_FAILED);
231
232 code.textContent = UNKNOWN_FAILURE_MESSAGE;
233 } else {
234 // mark as loaded
235 pre.setAttribute(STATUS_ATTR, STATUS_LOADED);
236
237 code.textContent = data;
238 Prism.highlightElement(code);
239 }
240 };
241
242 document.head.appendChild(script);
243 }
244 });
245
246
247 Prism.plugins.jsonphighlight = {
248 /**
249 * The timeout after which an error message will be displayed.
250 *
251 * __Note:__ If the request succeeds after the timeout, it will still be processed and will override any
252 * displayed error messages.
253 */
254 timeout: 5000,
255 registerAdapter: registerAdapter,
256 removeAdapter: removeAdapter,
257
258 /**
259 * Highlights all `pre` elements under the given container with a `data-jsonp` attribute by requesting the
260 * specified JSON and using the specified adapter or a registered adapter to extract the code to highlight
261 * from the response. The highlighted code will be inserted into the `pre` element.
262 *
263 * Note: Elements which are already loaded or currently loading will not be touched by this method.
264 *
265 * @param {Element | Document} [container=document]
266 */
267 highlight: function (container) {
268 var elements = (container || document).querySelectorAll(SELECTOR);
269
270 for (var i = 0, element; element = elements[i++];) {
271 Prism.highlightElement(element);
272 }
273 }
274 };
275
276})();