Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file-highlight plugins #15

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,10 @@ function App() {
}
```

## File Highlight

Fetch external files and highlight them with Prism.

## 📜 Changelog

Latest version 0.5.0 (2022-12-18):
Expand Down
195 changes: 195 additions & 0 deletions plugins/file-highlight/file-highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
(function () {

if (typeof Prism === 'undefined' || typeof document === 'undefined') {
return;
}

// https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}

var LOADING_MESSAGE = 'Loading…';
var FAILURE_MESSAGE = function (status, message) {
return '✖ Error ' + status + ' while fetching file: ' + message;
};
var FAILURE_EMPTY_MESSAGE = '✖ Error: File does not exist or is empty';

var EXTENSIONS = {
'js': 'javascript',
'py': 'python',
'rb': 'ruby',
'ps1': 'powershell',
'psm1': 'powershell',
'sh': 'bash',
'bat': 'batch',
'h': 'c',
'tex': 'latex'
};

var STATUS_ATTR = 'data-src-status';
var STATUS_LOADING = 'loading';
var STATUS_LOADED = 'loaded';
var STATUS_FAILED = 'failed';

var SELECTOR = 'pre[data-src]:not([' + STATUS_ATTR + '="' + STATUS_LOADED + '"])'
+ ':not([' + STATUS_ATTR + '="' + STATUS_LOADING + '"])';

/**
* Loads the given file.
*
* @param {string} src The URL or path of the source file to load.
* @param {(result: string) => void} success
* @param {(reason: string) => void} error
*/
function loadFile(src, success, error) {
var xhr = new XMLHttpRequest();
xhr.open('GET', src, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status < 400 && xhr.responseText) {
success(xhr.responseText);
} else {
if (xhr.status >= 400) {
error(FAILURE_MESSAGE(xhr.status, xhr.statusText));
} else {
error(FAILURE_EMPTY_MESSAGE);
}
}
}
};
xhr.send(null);
}

/**
* Parses the given range.
*
* This returns a range with inclusive ends.
*
* @param {string | null | undefined} range
* @returns {[number, number | undefined] | undefined}
*/
function parseRange(range) {
var m = /^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(range || '');
if (m) {
var start = Number(m[1]);
var comma = m[2];
var end = m[3];

if (!comma) {
return [start, start];
}
if (!end) {
return [start, undefined];
}
return [start, Number(end)];
}
return undefined;
}

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

pre.setAttribute(STATUS_ATTR, STATUS_LOADING); // mark as loading

// add code element with loading message
var code = pre.appendChild(document.createElement('CODE'));
code.textContent = LOADING_MESSAGE;

var src = pre.getAttribute('data-src');

var language = env.language;
if (language === 'none') {
// the language might be 'none' because there is no language set;
// in this case, we want to use the extension as the language
var extension = (/\.(\w+)$/.exec(src) || [, 'none'])[1];
language = EXTENSIONS[extension] || extension;
}

// set language classes
Prism.util.setLanguage(code, language);
Prism.util.setLanguage(pre, language);

// preload the language
var autoloader = Prism.plugins.autoloader;
if (autoloader) {
autoloader.loadLanguages(language);
}

// load file
loadFile(
src,
function (text) {
// mark as loaded
pre.setAttribute(STATUS_ATTR, STATUS_LOADED);

// handle data-range
var range = parseRange(pre.getAttribute('data-range'));
if (range) {
var lines = text.split(/\r\n?|\n/g);

// the range is one-based and inclusive on both ends
var start = range[0];
var end = range[1] == null ? lines.length : range[1];

if (start < 0) { start += lines.length; }
start = Math.max(0, Math.min(start - 1, lines.length));
if (end < 0) { end += lines.length; }
end = Math.max(0, Math.min(end, lines.length));

text = lines.slice(start, end).join('\n');

// add data-start for line numbers
if (!pre.hasAttribute('data-start')) {
pre.setAttribute('data-start', String(start + 1));
}
}

// highlight code
code.textContent = text;
Prism.highlightElement(code);
},
function (error) {
// mark as failed
pre.setAttribute(STATUS_ATTR, STATUS_FAILED);

code.textContent = error;
}
);
}
});

Prism.plugins.fileHighlight = {
/**
* Executes the File Highlight plugin for all matching `pre` elements under the given container.
*
* Note: Elements which are already loaded or currently loading will not be touched by this method.
*
* @param {ParentNode} [container=document]
*/
highlight: function highlight(container) {
var elements = (container || document).querySelectorAll(SELECTOR);

for (var i = 0, element; (element = elements[i++]);) {
Prism.highlightElement(element);
}
}
};

var logged = false;
/** @deprecated Use `Prism.plugins.fileHighlight.highlight` instead. */
Prism.fileHighlight = function () {
if (!logged) {
console.warn('Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead.');
logged = true;
}
Prism.plugins.fileHighlight.highlight.apply(this, arguments);
};

}());