UNPKG

prismjs

Version:

Lightweight, robust, elegant syntax highlighting. A spin-off project from Dabblet.

277 lines (237 loc) 8.03 kB
(function () { if (typeof self === 'undefined' || !self.Prism || !self.document) { return; } /** * @callback Adapter * @param {any} response * @param {HTMLPreElement} [pre] * @returns {string | null} */ /** * The list of adapter which will be used if `data-adapter` is not specified. * * @type {Array<{adapter: Adapter, name: string}>} */ var adapters = []; /** * Adds a new function to the list of adapters. * * If the given adapter is already registered or not a function or there is an adapter with the given name already, * nothing will happen. * * @param {Adapter} adapter The adapter to be registered. * @param {string} [name] The name of the adapter. Defaults to the function name of `adapter`. */ function registerAdapter(adapter, name) { name = name || adapter.name; if (typeof adapter === "function" && !getAdapter(adapter) && !getAdapter(name)) { adapters.push({ adapter: adapter, name: name }); } } /** * Returns the given adapter itself, if registered, or a registered adapter with the given name. * * If no fitting adapter is registered, `null` will be returned. * * @param {string|Function} adapter The adapter itself or the name of an adapter. * @returns {Adapter} A registered adapter or `null`. */ function getAdapter(adapter) { if (typeof adapter === "function") { for (var i = 0, item; item = adapters[i++];) { if (item.adapter.valueOf() === adapter.valueOf()) { return item.adapter; } } } else if (typeof adapter === "string") { for (var i = 0, item; item = adapters[i++];) { if (item.name === adapter) { return item.adapter; } } } return null; } /** * Remove the given adapter or the first registered adapter with the given name from the list of * registered adapters. * * @param {string|Function} adapter The adapter itself or the name of an adapter. */ function removeAdapter(adapter) { if (typeof adapter === "string") { adapter = getAdapter(adapter); } if (typeof adapter === "function") { var index = adapters.findIndex(function (item) { return item.adapter === adapter; }); if (index >= 0) { adapters.splice(index, 1); } } } registerAdapter(function github(rsp, el) { if (rsp && rsp.meta && rsp.data) { if (rsp.meta.status && rsp.meta.status >= 400) { return "Error: " + (rsp.data.message || rsp.meta.status); } else if (typeof (rsp.data.content) === "string") { return typeof (atob) === "function" ? atob(rsp.data.content.replace(/\s/g, "")) : "Your browser cannot decode base64"; } } return null; }, 'github'); registerAdapter(function gist(rsp, el) { if (rsp && rsp.meta && rsp.data && rsp.data.files) { if (rsp.meta.status && rsp.meta.status >= 400) { return "Error: " + (rsp.data.message || rsp.meta.status); } var files = rsp.data.files; var filename = el.getAttribute("data-filename"); if (filename == null) { // Maybe in the future we can somehow render all files // But the standard <script> include for gists does that nicely already, // so that might be getting beyond the scope of this plugin for (var key in files) { if (files.hasOwnProperty(key)) { filename = key; break; } } } if (files[filename] !== undefined) { return files[filename].content; } return "Error: unknown or missing gist file " + filename; } return null; }, 'gist'); registerAdapter(function bitbucket(rsp, el) { if (rsp && rsp.node && typeof (rsp.data) === "string") { return rsp.data; } return null; }, 'bitbucket'); var jsonpCallbackCounter = 0; var LOADING_MESSAGE = 'Loading…'; var MISSING_ADAPTER_MESSAGE = function (name) { return '✖ Error: JSONP adapter function "' + name + '" doesn\'t exist'; }; var TIMEOUT_MESSAGE = function (url) { return '✖ Error: Timeout loading ' + url; }; var UNKNOWN_FAILURE_MESSAGE = '✖ Error: Cannot parse response (perhaps you need an adapter function?)'; var STATUS_ATTR = 'data-jsonp-status'; var STATUS_LOADING = 'loading'; var STATUS_LOADED = 'loaded'; var STATUS_FAILED = 'failed'; var SELECTOR = 'pre[data-jsonp]:not([' + STATUS_ATTR + '="' + STATUS_LOADED + '"])' + ':not([' + STATUS_ATTR + '="' + STATUS_LOADING + '"])'; Prism.hooks.add('before-highlightall', function (env) { env.selector += ', ' + SELECTOR; }); Prism.hooks.add('before-sanity-check', function (env) { var pre = /** @type {HTMLPreElement} */ (env.element); if (pre.matches(SELECTOR)) { env.code = ''; // fast-path the whole thing and go to complete // mark as loading pre.setAttribute(STATUS_ATTR, STATUS_LOADING); // add code element with loading message var code = pre.appendChild(document.createElement('CODE')); code.textContent = LOADING_MESSAGE; // set language var language = env.language; code.className = 'language-' + language; // preload the language var autoloader = Prism.plugins.autoloader; if (autoloader) { autoloader.loadLanguages(language); } var adapterName = pre.getAttribute('data-adapter'); var adapter = null; if (adapterName) { if (typeof window[adapterName] === 'function') { adapter = window[adapterName]; } else { // mark as failed pre.setAttribute(STATUS_ATTR, STATUS_FAILED); code.textContent = MISSING_ADAPTER_MESSAGE(adapterName); return; } } var callbackName = 'prismjsonp' + jsonpCallbackCounter++; var uri = document.createElement('a'); var src = uri.href = pre.getAttribute('data-jsonp'); uri.href += (uri.search ? '&' : '?') + (pre.getAttribute('data-callback') || 'callback') + '=' + callbackName; var timeout = setTimeout(function () { // we could clean up window[cb], but if the request finally succeeds, keeping it around is a good thing // mark as failed pre.setAttribute(STATUS_ATTR, STATUS_FAILED); code.textContent = TIMEOUT_MESSAGE(src); }, Prism.plugins.jsonphighlight.timeout); var script = document.createElement('script'); script.src = uri.href; // the JSONP callback function window[callbackName] = function (response) { // clean up document.head.removeChild(script); clearTimeout(timeout); delete window[callbackName]; // interpret the received data using the adapter(s) var data = null; if (adapter) { data = adapter(response, pre); } else { for (var i = 0, l = adapters.length; i < l; i++) { data = adapters[i].adapter(response, pre); if (data !== null) { break; } } } if (data === null) { // mark as failed pre.setAttribute(STATUS_ATTR, STATUS_FAILED); code.textContent = UNKNOWN_FAILURE_MESSAGE; } else { // mark as loaded pre.setAttribute(STATUS_ATTR, STATUS_LOADED); code.textContent = data; Prism.highlightElement(code); } }; document.head.appendChild(script); } }); Prism.plugins.jsonphighlight = { /** * The timeout after which an error message will be displayed. * * __Note:__ If the request succeeds after the timeout, it will still be processed and will override any * displayed error messages. */ timeout: 5000, registerAdapter: registerAdapter, removeAdapter: removeAdapter, /** * Highlights all `pre` elements under the given container with a `data-jsonp` attribute by requesting the * specified JSON and using the specified adapter or a registered adapter to extract the code to highlight * from the response. The highlighted code will be inserted into the `pre` element. * * Note: Elements which are already loaded or currently loading will not be touched by this method. * * @param {Element | Document} [container=document] */ highlight: function (container) { var elements = (container || document).querySelectorAll(SELECTOR); for (var i = 0, element; element = elements[i++];) { Prism.highlightElement(element); } } }; })();