Skip to content

Commit

Permalink
Markdown: Added support for auto-loading code block languages (#1898)
Browse files Browse the repository at this point in the history
This lets Markdown load embedded languages using the Autoloader plugin which now exposes the `loadLanguages` function.

This also changes `loadLanguages` to always invoke callbacks asynchronously and to invoke error callbacks only once.
  • Loading branch information
RunDevelopment authored Jul 22, 2019
1 parent e7702ae commit 05823e8
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 77 deletions.
20 changes: 15 additions & 5 deletions components/prism-markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,23 @@
var grammar = Prism.languages[codeLang];

if (!grammar) {
return;
}
if (codeLang && codeLang !== 'none' && Prism.plugins.autoloader) {
var id = 'md-' + new Date().valueOf() + '-' + Math.floor(Math.random() * 1e16);
env.attributes['id'] = id;

// reverse Prism.util.encode
var code = env.content.replace(/&lt;/g, '<').replace(/&amp;/g, '&');
Prism.plugins.autoloader.loadLanguages(codeLang, function () {
var ele = document.getElementById(id);
if (ele) {
ele.innerHTML = Prism.highlight(ele.textContent, Prism.languages[codeLang], codeLang);
}
});
}
} else {
// reverse Prism.util.encode
var code = env.content.replace(/&lt;/g, '<').replace(/&amp;/g, '&');

env.content = Prism.highlight(code, grammar, codeLang);
env.content = Prism.highlight(code, grammar, codeLang);
}
});

Prism.languages.md = Prism.languages.markdown;
Expand Down
2 changes: 1 addition & 1 deletion components/prism-markdown.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions plugins/autoloader/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@ <h1>Examples</h1>

</section>

<section>
<h1>Markdown</h1>

<p>Markdown will use the Autoloader to automatically load missing languages.</p>
<pre><code class="language-markdown">The C# code will be highlighted __after__ the rest of this document.

```csharp
public class Foo : IBar&lt;int&gt; {
public string Baz { get; set; } = "foo";
}
```

The CSS code will be highlighted with this document because CSS has already been loaded.

```css
a:hover {
color: green !important;
}
```</code></pre>

</section>

<footer data-src="templates/footer.html" data-type="text/html"></footer>

<script src="components/prism-core.js"></script>
Expand Down
149 changes: 79 additions & 70 deletions plugins/autoloader/prism-autoloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@

/**
* @typedef LangDataItem
* @property {{ success?: function, error?: function }[]} callbacks
* @property {{ success?: () => void, error?: () => void }[]} callbacks
* @property {boolean} [error]
* @property {boolean} [loading]
*/
Expand Down Expand Up @@ -192,48 +192,52 @@

var config = Prism.plugins.autoloader = {
languages_path: languages_path,
use_minified: true
use_minified: true,
loadLanguages: loadLanguages
};


/**
* Lazy loads an external script
* Lazily loads an external script.
*
* @param {string} src
* @param {function=} success
* @param {function=} error
* @param {() => void} [success]
* @param {() => void} [error]
*/
var addScript = function (src, success, error) {
function addScript(src, success, error) {
var s = document.createElement('script');
s.src = src;
s.async = true;
s.onload = function() {
s.onload = function () {
document.body.removeChild(s);
success && success();
};
s.onerror = function() {
s.onerror = function () {
document.body.removeChild(s);
error && error();
};
document.body.appendChild(s);
};
}

/**
* Returns the path to a grammar, using the language_path and use_minified config keys.
*
* @param {string} lang
* @returns {string}
*/
var getLanguagePath = function (lang) {
function getLanguagePath(lang) {
return config.languages_path +
'prism-' + lang
+ (config.use_minified ? '.min' : '') + '.js'
};
}

/**
* Tries to load a grammar and
* highlight again the given element once loaded.
* Tries to load the grammar(s) and once loaded, highlights the given element again.
*
* @param {string} lang
* @param {HTMLElement} elt
*/
var registerElement = function (lang, elt) {
function registerElement(lang, elt) {
if (lang in lang_aliases) {
lang = lang_aliases[lang];
}
Expand All @@ -256,42 +260,60 @@
Prism.highlightElement(elt);
});
});
};
}

/**
* Sequentially loads an array of grammars.
* @param {string[]|string} langs
* @param {function=} success
* @param {function=} error
* Loads all given grammars concurrently.
*
* @param {string[]|string} languages
* @param {(languages: string[]) => void} [success]
* @param {(language: string) => void} [error] This callback will be invoked on the first language to fail.
*/
var loadLanguages = function (langs, success, error) {
if (typeof langs === 'string') {
langs = [langs];
function loadLanguages(languages, success, error) {
if (typeof languages === 'string') {
languages = [languages];
}
var i = 0;
var l = langs.length;
var f = function () {
if (i < l) {
loadLanguage(langs[i], function () {
i++;
f();
}, function () {
error && error(langs[i]);
});
} else if (i === l) {
success && success(langs);

var total = languages.length;
var completed = 0;
var failed = false;

if (total === 0) {
if (success) {
setTimeout(success, 0);
}
};
f();
};
return;
}

function successCallback() {
if (failed) {
return;
}
completed++;
if (completed === total) {
success && success(languages);
}
}

languages.forEach(function (lang) {
loadLanguage(lang, successCallback, function () {
if (failed) {
return;
}
failed = true;
error && error(lang);
});
});
}

/**
* Load a grammar with its dependencies
* Loads a grammar with its dependencies.
*
* @param {string} lang
* @param {function=} success
* @param {function=} error
* @param {() => void} [success]
* @param {() => void} [error]
*/
var loadLanguage = function (lang, success, error) {
function loadLanguage(lang, success, error) {
var force = lang.indexOf('!') >= 0;

lang = lang.replace('!', '');
Expand All @@ -310,63 +332,50 @@
});

if (!force && Prism.languages[lang]) {
languageSuccess(lang);
languageCallback(lang, "success");
} else if (!force && data.error) {
languageError(lang);
languageCallback(lang, "error");
} else if (force || !data.loading) {
data.loading = true;
var src = getLanguagePath(lang);
addScript(src, function () {
data.loading = false;
languageSuccess(lang);
languageCallback(lang, "success");

}, function () {
data.loading = false;
data.error = true;
languageError(lang);
languageCallback(lang, "error");
});
}
};

var dependencies = lang_dependencies[lang];
if(dependencies && dependencies.length) {
loadLanguages(dependencies, load);
if (dependencies && dependencies.length) {
loadLanguages(dependencies, load, error);
} else {
load();
}
};

/**
* Runs all success callbacks for this language.
* @param {string} lang
*/
var languageSuccess = function (lang) {
if (lang_data[lang] ) {
var callbacks = lang_data[lang].callbacks;
while (callbacks.length) {
var callback = callbacks.shift().success;
if (callback) {
callback();
}
}
}
};
}

/**
* Runs all error callbacks for this language.
* Runs all callbacks of the given type for the given language.
*
* @param {string} lang
* @param {"success" | "error"} type
*/
var languageError = function (lang) {
function languageCallback(lang, type) {
if (lang_data[lang]) {
var callbacks = lang_data[lang].callbacks;
while (callbacks.length) {
var callback = callbacks.shift().error;
for (var i = 0, l = callbacks.length; i < l; i++) {
var callback = callbacks[i][type];
if (callback) {
callback();
setTimeout(callback, 0);
}
}
callbacks.length = 0;
}
};
}

Prism.hooks.add('complete', function (env) {
if (env.element && env.language && !env.grammar) {
Expand Down
Loading

0 comments on commit 05823e8

Please sign in to comment.