diff --git a/app/assets/javascripts/admin/components/highlighted-code.js b/app/assets/javascripts/admin/components/highlighted-code.js index 30f89e40850..9159bb574ad 100644 --- a/app/assets/javascripts/admin/components/highlighted-code.js +++ b/app/assets/javascripts/admin/components/highlighted-code.js @@ -1,37 +1,11 @@ import Component from "@ember/component"; -import { highlightText } from "discourse/lib/highlight-syntax"; -import { escapeExpression } from "discourse/lib/utilities"; -import discourseComputed from "discourse-common/utils/decorators"; -import { htmlSafe } from "@ember/template"; +import { on, observes } from "discourse-common/utils/decorators"; +import highlightSyntax from "discourse/lib/highlight-syntax"; export default Component.extend({ - didReceiveAttrs() { - this._super(...arguments); - if (this.code === this.previousCode) return; - - this.set("previousCode", this.code); - this.set("highlightResult", null); - const toHighlight = this.code; - highlightText(escapeExpression(toHighlight), this.lang).then( - ({ result }) => { - if (toHighlight !== this.code) return; // Code has changed since highlight was requested - this.set("highlightResult", result); - } - ); - }, - - @discourseComputed("code", "highlightResult") - displayCode(code, highlightResult) { - if (highlightResult) return htmlSafe(highlightResult); - return code; - }, - - @discourseComputed("highlightResult", "lang") - codeClasses(highlightResult, lang) { - const classes = []; - if (lang) classes.push(lang); - if (highlightResult) classes.push("hljs"); - - return classes.join(" "); + @on("didInsertElement") + @observes("code") + _refresh: function() { + highlightSyntax($(this.element)); } }); diff --git a/app/assets/javascripts/admin/models/theme.js b/app/assets/javascripts/admin/models/theme.js index d17b87dfd0d..0b96660f99b 100644 --- a/app/assets/javascripts/admin/models/theme.js +++ b/app/assets/javascripts/admin/models/theme.js @@ -7,8 +7,8 @@ import discourseComputed from "discourse-common/utils/decorators"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { ajax } from "discourse/lib/ajax"; import { escapeExpression } from "discourse/lib/utilities"; -import { url } from "discourse/lib/computed"; import highlightSyntax from "discourse/lib/highlight-syntax"; +import { url } from "discourse/lib/computed"; const THEME_UPLOAD_VAR = 2; const FIELDS_IDS = [0, 1, 5]; @@ -321,7 +321,7 @@ const Theme = RestModel.extend({ } } ); - highlightSyntax(document.querySelector(".bootbox.modal")); + highlightSyntax(); } else { return this.save({ remote_update: true }).then(() => this.set("changed", false) diff --git a/app/assets/javascripts/admin/templates/components/highlighted-code.hbs b/app/assets/javascripts/admin/templates/components/highlighted-code.hbs index 5dd40c0476b..4d67dd6fd2e 100644 --- a/app/assets/javascripts/admin/templates/components/highlighted-code.hbs +++ b/app/assets/javascripts/admin/templates/components/highlighted-code.hbs @@ -1 +1 @@ -
{{displayCode}}
+{{code}}
diff --git a/app/assets/javascripts/discourse/app/initializers/post-decorations.js b/app/assets/javascripts/discourse/app/initializers/post-decorations.js
index d1205bcfdb1..7b7d543aa87 100644
--- a/app/assets/javascripts/discourse/app/initializers/post-decorations.js
+++ b/app/assets/javascripts/discourse/app/initializers/post-decorations.js
@@ -1,15 +1,15 @@
+import highlightSyntax from "discourse/lib/highlight-syntax";
import lightbox from "discourse/lib/lightbox";
import { setupLazyLoading } from "discourse/lib/lazy-load-images";
import { setTextDirections } from "discourse/lib/text-direction";
import { withPluginApi } from "discourse/lib/plugin-api";
-import highlightSyntax from "discourse/lib/highlight-syntax";
export default {
name: "post-decorations",
initialize(container) {
withPluginApi("0.1", api => {
const siteSettings = container.lookup("site-settings:main");
- api.decorateCookedElement(highlightSyntax, {
+ api.decorateCooked(highlightSyntax, {
id: "discourse-syntax-highlighting"
});
api.decorateCookedElement(lightbox, { id: "discourse-lightbox" });
diff --git a/app/assets/javascripts/discourse/app/lib/highlight-syntax.js b/app/assets/javascripts/discourse/app/lib/highlight-syntax.js
index 302fd4e62a0..404e73cadcc 100644
--- a/app/assets/javascripts/discourse/app/lib/highlight-syntax.js
+++ b/app/assets/javascripts/discourse/app/lib/highlight-syntax.js
@@ -1,181 +1,40 @@
-import { Promise } from "rsvp";
-import { getURLWithCDN } from "discourse-common/lib/get-url";
-import { next, schedule } from "@ember/runloop";
+/*global hljs:true */
+let _moreLanguages = [];
+
import loadScript from "discourse/lib/load-script";
-import { isTesting } from "discourse-common/config/environment";
-let highlightJsUrl;
-let highlightJsWorkerUrl;
+export default function highlightSyntax($elem) {
+ const selector = Discourse.SiteSettings.autohighlight_all_code
+ ? "pre code"
+ : "pre code[class]",
+ path = Discourse.HighlightJSPath;
-const _moreLanguages = [];
-let _worker = null;
-let _workerPromise = null;
-const _pendingResolution = {};
-let _counter = 0;
-let _cachedResultsMap = new Map();
+ if (!path) {
+ return;
+ }
-const CACHE_SIZE = 100;
+ $(selector, $elem).each(function(i, e) {
+ // Large code blocks can cause crashes or slowdowns
+ if (e.innerHTML.length > 30000) {
+ return;
+ }
-export function setupHighlightJs(args) {
- highlightJsUrl = args.highlightJsUrl;
- highlightJsWorkerUrl = args.highlightJsWorkerUrl;
+ $(e).removeClass("lang-auto");
+ loadScript(path).then(() => {
+ customHighlightJSLanguages();
+ hljs.highlightBlock(e);
+ });
+ });
}
export function registerHighlightJSLanguage(name, fn) {
_moreLanguages.push({ name: name, fn: fn });
}
-export default function highlightSyntax(elem, { autoHighlight = false } = {}) {
- const selector = autoHighlight ? "pre code" : "pre code[class]";
-
- elem.querySelectorAll(selector).forEach(e => highlightElement(e));
-}
-
-function highlightElement(e) {
- e.classList.remove("lang-auto");
- let lang = null;
- e.classList.forEach(c => {
- if (c.startsWith("lang-")) {
- lang = c.slice("lang-".length);
- }
- });
-
- const requestString = e.textContent;
- highlightText(e.textContent, lang).then(({ result, fromCache }) => {
- const doRender = () => {
- // Ensure the code hasn't changed since highlighting was triggered:
- if (requestString !== e.textContent) return;
-
- e.innerHTML = result;
- e.classList.add("hljs");
- };
-
- if (fromCache) {
- // This happened synchronously, we can safely add rendering
- // to the end of the current Runloop
- schedule("afterRender", null, doRender);
- } else {
- // This happened async, we are probably not in a runloop
- // If we call `schedule`, a new runloop will be triggered immediately
- // So schedule rendering to happen in the next runloop
- next(() => schedule("afterRender", null, doRender));
+function customHighlightJSLanguages() {
+ _moreLanguages.forEach(l => {
+ if (hljs.getLanguage(l.name) === undefined) {
+ hljs.registerLanguage(l.name, l.fn);
}
});
}
-
-export function highlightText(text, language) {
- // Large code blocks can cause crashes or slowdowns
- if (text.length > 30000) {
- return Promise.resolve({ result: text, fromCache: true });
- }
-
- return getWorker().then(w => {
- let result;
- if ((result = _cachedResultsMap.get(cacheKey(text, language)))) {
- return Promise.resolve({ result, fromCache: true });
- }
-
- let resolve;
- const promise = new Promise(f => (resolve = f));
-
- w.postMessage({
- type: "highlight",
- id: _counter,
- text,
- language
- });
-
- _pendingResolution[_counter] = {
- promise,
- resolve,
- text,
- language
- };
-
- _counter++;
-
- return promise;
- });
-}
-
-function getWorker() {
- if (_worker) return Promise.resolve(_worker);
- if (_workerPromise) return _workerPromise;
-
- const w = new Worker(highlightJsWorkerUrl);
- w.onmessage = onWorkerMessage;
- w.postMessage({
- type: "loadHighlightJs",
- path: fullHighlightJsUrl()
- });
-
- _workerPromise = setupCustomLanguages(w).then(() => (_worker = w));
- return _workerPromise;
-}
-
-function setupCustomLanguages(worker) {
- if (_moreLanguages.length === 0) return Promise.resolve();
- // To build custom language definitions we need to have hljs loaded
- // Plugins/themes can't run code in a worker, so we have to load hljs in the main thread
- // But the actual highlighting will still be done in the worker
-
- return loadScript(highlightJsUrl).then(() => {
- _moreLanguages.forEach(({ name, fn }) => {
- const definition = fn(window.hljs);
- worker.postMessage({
- type: "registerLanguage",
- definition,
- name
- });
- });
- });
-}
-
-function onWorkerMessage(message) {
- const id = message.data.id;
- const request = _pendingResolution[id];
- delete _pendingResolution[id];
- request.resolve({ result: message.data.result, fromCache: false });
-
- cacheResult({
- text: request.text,
- language: request.language,
- result: message.data.result
- });
-}
-
-function cacheResult({ text, language, result }) {
- _cachedResultsMap.set(cacheKey(text, language), result);
- while (_cachedResultsMap.size > CACHE_SIZE) {
- _cachedResultsMap.delete(_cachedResultsMap.entries().next().value[0]);
- }
-}
-
-function cacheKey(text, lang) {
- return `${lang}:${text}`;
-}
-
-function fullHighlightJsUrl() {
- let hljsUrl = getURLWithCDN(highlightJsUrl);
-
- // Need to use full URL including protocol/domain
- // for use in a worker
- if (hljsUrl.startsWith("/")) {
- hljsUrl = window.location.protocol + "//" + window.location.host + hljsUrl;
- }
-
- return hljsUrl;
-}
-
-// To be used in qunit tests. Running highlight in a worker means that the
-// normal system which waits for ember rendering in tests doesn't work.
-// This promise will resolve once all pending highlights are done
-export function waitForHighlighting() {
- if (!isTesting()) {
- throw "This function should only be called in a test environment";
- }
- const promises = Object.values(_pendingResolution).map(r => r.promise);
- return new Promise(resolve => {
- Promise.all(promises).then(() => next(resolve));
- });
-}
diff --git a/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js b/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
index 54f55d897f3..d8eb01dadc6 100644
--- a/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
+++ b/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
@@ -9,7 +9,6 @@ import {
} from "discourse-common/config/environment";
import { setupURL, setupS3CDN } from "discourse-common/lib/get-url";
import deprecated from "discourse-common/lib/deprecated";
-import { setupHighlightJs } from "discourse/lib/highlight-syntax";
export default {
name: "discourse-bootstrap",
@@ -82,11 +81,7 @@ export default {
Session.currentProp("safe_mode", setupData.safeMode);
}
- setupHighlightJs({
- highlightJsUrl: setupData.highlightJsUrl,
- highlightJsWorkerUrl: setupData.highlightJsWorkerUrl
- });
-
+ app.HighlightJSPath = setupData.highlightJsPath;
app.SvgSpritePath = setupData.svgSpritePath;
if (app.Environment === "development") {
diff --git a/app/assets/javascripts/highlightjs-worker.js b/app/assets/javascripts/highlightjs-worker.js
deleted file mode 100644
index 64dfefbf2ec..00000000000
--- a/app/assets/javascripts/highlightjs-worker.js
+++ /dev/null
@@ -1,49 +0,0 @@
-// discourse-skip-module
-
-// Standalone worker for highlightjs syntax generation
-
-// The highlightjs path changes based on site settings,
-// so we wait for Discourse to pass the path into the worker
-const loadHighlightJs = path => {
- self.importScripts(path);
-};
-
-const highlight = ({ id, text, language }) => {
- if (!self.hljs) {
- throw "HighlightJS is not loaded";
- }
-
- const result = language
- ? self.hljs.highlight(language, text, true).value
- : self.hljs.highlightAuto(text).value;
-
- postMessage({
- type: "highlightResult",
- id: id,
- result: result
- });
-};
-
-const registerLanguage = ({ name, definition }) => {
- if (!self.hljs) {
- throw "HighlightJS is not loaded";
- }
- self.hljs.registerLanguage(name, () => {
- return definition;
- });
-};
-
-onmessage = event => {
- const data = event.data;
- const messageType = data.type;
-
- if (messageType === "loadHighlightJs") {
- loadHighlightJs(data.path);
- } else if (messageType === "registerLanguage") {
- registerLanguage(data);
- } else if (messageType === "highlight") {
- highlight(data);
- } else {
- throw `Unknown message type: ${messageType}`;
- }
-};
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index c051ec5b316..f6bda658a38 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -469,8 +469,7 @@ module ApplicationHelper
default_locale: SiteSetting.default_locale,
asset_version: Discourse.assets_digest,
disable_custom_css: loading_admin?,
- highlight_js_url: HighlightJs.path,
- highlight_js_worker_url: script_asset_path('highlightjs-worker'),
+ highlight_js_path: HighlightJs.path,
svg_sprite_path: SvgSprite.path(theme_ids),
enable_js_error_reporting: GlobalSetting.enable_js_error_reporting,
}
diff --git a/config/application.rb b/config/application.rb
index 194cc262350..6120831f6dd 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -171,7 +171,6 @@ module Discourse
confirm-new-email/bootstrap.js
onpopstate-handler.js
embed-application.js
- highlightjs-worker.js
}
# Precompile all available locales
diff --git a/lib/discourse_js_processor.rb b/lib/discourse_js_processor.rb
index e0ee89e0efd..274fda206d9 100644
--- a/lib/discourse_js_processor.rb
+++ b/lib/discourse_js_processor.rb
@@ -56,7 +56,6 @@ class DiscourseJsProcessor
activate-account
auto-redirect
embed-application
- highlightjs-worker
app-boot
).any? { |f| relative_path == "#{js_root}/#{f}.js" }
diff --git a/test/javascripts/components/highlighted-code-test.js b/test/javascripts/components/highlighted-code-test.js
index 1cde451bcab..27cfe5696d3 100644
--- a/test/javascripts/components/highlighted-code-test.js
+++ b/test/javascripts/components/highlighted-code-test.js
@@ -1,8 +1,4 @@
import componentTest from "helpers/component-test";
-import {
- waitForHighlighting,
- setupHighlightJs
-} from "discourse/lib/highlight-syntax";
const LONG_CODE_BLOCK = "puts a\n".repeat(15000);
@@ -12,15 +8,12 @@ componentTest("highlighting code", {
template: "{{highlighted-code lang='ruby' code=code}}",
beforeEach() {
- setupHighlightJs({
- highlightJsUrl: "/assets/highlightjs/highlight-test-bundle.min.js",
- highlightJsWorkerUrl: "/assets/highlightjs-worker.js"
- });
+ Discourse.HighlightJSPath =
+ "assets/highlightjs/highlight-test-bundle.min.js";
+ this.set("code", "def test; end");
},
async test(assert) {
- this.set("code", "def test; end");
- await waitForHighlighting();
assert.equal(
find("code.ruby.hljs .hljs-function .hljs-keyword")
.text()
@@ -30,19 +23,16 @@ componentTest("highlighting code", {
}
});
-componentTest("highlighting code limit", {
+componentTest("large code blocks are not highlighted", {
template: "{{highlighted-code lang='ruby' code=code}}",
beforeEach() {
- setupHighlightJs({
- highlightJsUrl: "/assets/highlightjs/highlight-test-bundle.min.js",
- highlightJsWorkerUrl: "/assets/highlightjs-worker.js"
- });
+ Discourse.HighlightJSPath =
+ "assets/highlightjs/highlight-test-bundle.min.js";
+ this.set("code", LONG_CODE_BLOCK);
},
async test(assert) {
- this.set("code", LONG_CODE_BLOCK);
- await waitForHighlighting();
assert.equal(
find("code")
.text()