277 lines
7.8 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
})();
|