hexo/node_modules/prismjs/plugins/jsonp-highlight/prism-jsonp-highlight.js

304 lines
8.8 KiB
JavaScript

(function () {
if (typeof Prism === 'undefined' || typeof document === 'undefined') {
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') {
// eslint-disable-next-line no-redeclare
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) {
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) {
if (rsp && rsp.node && typeof (rsp.data) === 'string') {
return rsp.data;
}
return null;
}, 'bitbucket');
var jsonpCallbackCounter = 0;
/**
* Makes a JSONP request.
*
* @param {string} src The URL of the resource to request.
* @param {string | undefined | null} callbackParameter The name of the callback parameter. If falsy, `"callback"`
* will be used.
* @param {(data: unknown) => void} onSuccess
* @param {(reason: "timeout" | "network") => void} onError
* @returns {void}
*/
function jsonp(src, callbackParameter, onSuccess, onError) {
var callbackName = 'prismjsonp' + jsonpCallbackCounter++;
var uri = document.createElement('a');
uri.href = src;
uri.href += (uri.search ? '&' : '?') + (callbackParameter || 'callback') + '=' + callbackName;
var script = document.createElement('script');
script.src = uri.href;
script.onerror = function () {
cleanup();
onError('network');
};
var timeoutId = setTimeout(function () {
cleanup();
onError('timeout');
}, Prism.plugins.jsonphighlight.timeout);
function cleanup() {
clearTimeout(timeoutId);
document.head.removeChild(script);
delete window[callbackName];
}
// the JSONP callback function
window[callbackName] = function (response) {
cleanup();
onSuccess(response);
};
document.head.appendChild(script);
}
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 src = pre.getAttribute('data-jsonp');
jsonp(
src,
pre.getAttribute('data-callback'),
function (response) {
// 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);
}
},
function () {
// mark as failed
pre.setAttribute(STATUS_ATTR, STATUS_FAILED);
code.textContent = TIMEOUT_MESSAGE(src);
}
);
}
});
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);
}
}
};
}());