Echo/node_modules/prismjs/plugins/jsonp-highlight/prism-jsonp-highlight.js
2021-02-11 21:31:41 +08:00

277 lines
7.8 KiB
JavaScript

(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);
}
}
};
})();