diff --git a/app/assets/javascripts/admin/addon/components/ace-editor.hbs b/app/assets/javascripts/admin/addon/components/ace-editor.hbs index 7aab68731dc..ac28b8afdb5 100644 --- a/app/assets/javascripts/admin/addon/components/ace-editor.hbs +++ b/app/assets/javascripts/admin/addon/components/ace-editor.hbs @@ -1 +1,5 @@ -
{{this.content}}
\ No newline at end of file +{{#if this.isLoading}} + {{loading-spinner size="small"}} +{{else}} +
{{this.content}}
+{{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/admin/addon/components/ace-editor.js b/app/assets/javascripts/admin/addon/components/ace-editor.js index 0b862546b41..c3c5d5aa7b2 100644 --- a/app/assets/javascripts/admin/addon/components/ace-editor.js +++ b/app/assets/javascripts/admin/addon/components/ace-editor.js @@ -1,5 +1,6 @@ import Component from "@ember/component"; import { action } from "@ember/object"; +import { next } from "@ember/runloop"; import { classNames } from "@ember-decorators/component"; import { observes, on } from "@ember-decorators/object"; import $ from "jquery"; @@ -13,6 +14,7 @@ const COLOR_VARS_REGEX = @classNames("ace-wrapper") export default class AceEditor extends Component { + isLoading = true; mode = "css"; disabled = false; htmlPlaceholder = false; @@ -95,67 +97,78 @@ export default class AceEditor extends Component { didInsertElement() { super.didInsertElement(...arguments); + loadScript("/javascripts/ace/ace.js").then(() => { - window.ace.require(["ace/ace"], (loadedAce) => { - loadedAce.config.set("loadWorkerFromBlob", false); - loadedAce.config.set("workerPath", getURL("/javascripts/ace")); // Do not use CDN for workers + loadScript(`/javascripts/ace/theme-${this.aceTheme}.js`).then(() => { + this.set("isLoading", false); - if (this.htmlPlaceholder) { - this._overridePlaceholder(loadedAce); - } + next(() => { + window.ace.require(["ace/ace"], (loadedAce) => { + loadedAce.config.set("loadWorkerFromBlob", false); + loadedAce.config.set("workerPath", getURL("/javascripts/ace")); // Do not use CDN for workers - if (!this.element || this.isDestroying || this.isDestroyed) { - return; - } - const editor = loadedAce.edit(this.element.querySelector(".ace")); + if (this.htmlPlaceholder) { + this._overridePlaceholder(loadedAce); + } - editor.setShowPrintMargin(false); - editor.setOptions({ fontSize: "14px", placeholder: this.placeholder }); - editor.getSession().setMode("ace/mode/" + this.mode); - editor.on("change", () => { - this._skipContentChangeEvent = true; - this.set("content", editor.getSession().getValue()); - }); - if (this.save) { - editor.commands.addCommand({ - name: "save", - exec: () => { - this.save(); - }, - bindKey: { mac: "cmd-s", win: "ctrl-s" }, + if (!this.element || this.isDestroying || this.isDestroyed) { + return; + } + const aceElement = this.element.querySelector(".ace"); + const editor = loadedAce.edit(aceElement); + editor.setShowPrintMargin(false); + editor.setOptions({ + fontSize: "14px", + placeholder: this.placeholder, + }); + editor.getSession().setMode("ace/mode/" + this.mode); + editor.on("change", () => { + this._skipContentChangeEvent = true; + this.set("content", editor.getSession().getValue()); + }); + if (this.save) { + editor.commands.addCommand({ + name: "save", + exec: () => { + this.save(); + }, + bindKey: { mac: "cmd-s", win: "ctrl-s" }, + }); + } + + editor.on("blur", () => { + this.warnSCSSDeprecations(); + }); + + editor.$blockScrolling = Infinity; + editor.renderer.setScrollMargin(10, 10); + + this.element.setAttribute("data-editor", editor); + this._editor = editor; + this.changeDisabledState(); + this.warnSCSSDeprecations(); + + $(window) + .off("ace:resize") + .on("ace:resize", () => this.appEvents.trigger("ace:resize")); + + if (this.appEvents) { + // xxx: don't run during qunit tests + this.appEvents.on("ace:resize", this, "resize"); + } + + if (this.autofocus) { + this.send("focus"); + } + + this.setAceTheme(); + + this._darkModeListener = window.matchMedia( + "(prefers-color-scheme: dark)" + ); + this._darkModeListener.addListener(this.setAceTheme); }); - } - - editor.on("blur", () => { - this.warnSCSSDeprecations(); }); - - editor.$blockScrolling = Infinity; - editor.renderer.setScrollMargin(10, 10); - - this.element.setAttribute("data-editor", editor); - this._editor = editor; - this.changeDisabledState(); - this.warnSCSSDeprecations(); - - $(window) - .off("ace:resize") - .on("ace:resize", () => this.appEvents.trigger("ace:resize")); - - if (this.appEvents) { - // xxx: don't run during qunit tests - this.appEvents.on("ace:resize", this, "resize"); - } - - if (this.autofocus) { - this.send("focus"); - } - - this.setAceTheme(); - this._darkModeListener = window.matchMedia( - "(prefers-color-scheme: dark)" - ); - this._darkModeListener.addListener(this.setAceTheme); }); }); } @@ -165,15 +178,17 @@ export default class AceEditor extends Component { this._darkModeListener?.removeListener(this.setAceTheme); } - @bind - setAceTheme() { + get aceTheme() { const schemeType = getComputedStyle(document.body) .getPropertyValue("--scheme-type") .trim(); - this._editor.setTheme( - `ace/theme/${schemeType === "dark" ? "chaos" : "chrome"}` - ); + return schemeType === "dark" ? "chaos" : "chrome"; + } + + @bind + setAceTheme() { + this._editor.setTheme(`ace/theme/${this.aceTheme}`); } warnSCSSDeprecations() { diff --git a/app/assets/javascripts/discourse/app/lib/public-js-versions.js b/app/assets/javascripts/discourse/app/lib/public-js-versions.js index 2d3af3a5795..1eef6426c4f 100644 --- a/app/assets/javascripts/discourse/app/lib/public-js-versions.js +++ b/app/assets/javascripts/discourse/app/lib/public-js-versions.js @@ -3,6 +3,8 @@ export const PUBLIC_JS_VERSIONS = { "ace/ace.js": "ace.js/1.4.13/ace.js", + "ace/theme-chrome.js": "ace.js/1.4.13/theme-chrome.js", + "ace/theme-chaos.js": "ace.js/1.4.13/theme-chaos.js", "jsoneditor.js": "@json-editor/json-editor/2.10.0/jsoneditor.js", "chart.min.js": "chart.js/3.5.1/chart.min.js", "chartjs-plugin-datalabels.min.js": diff --git a/app/assets/javascripts/discourse/tests/integration/components/admin-theme-settings-editor-test.js b/app/assets/javascripts/discourse/tests/integration/components/admin-theme-settings-editor-test.js index 53f8a317632..050ec713d74 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/admin-theme-settings-editor-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/admin-theme-settings-editor-test.js @@ -1,4 +1,4 @@ -import { render } from "@ember/test-helpers"; +import { render, waitUntil } from "@ember/test-helpers"; import { hbs } from "ember-cli-htmlbars"; import { module, test } from "qunit"; import { setupRenderingTest } from "discourse/tests/helpers/component-test"; @@ -56,9 +56,10 @@ module( ) ) }} />`); - const lines = document.querySelectorAll(".ace_line"); - const indexOf = lines[0].innerHTML.indexOf("["); - assert.ok(indexOf >= 0); + + await waitUntil(() => document.querySelectorAll(".ace_line")[0]); + + assert.dom(".ace_line").hasText("["); }); test("input is valid json", async function (assert) { diff --git a/lib/tasks/javascript.rake b/lib/tasks/javascript.rake index 2ed9dfcb428..1acbb36d20b 100644 --- a/lib/tasks/javascript.rake +++ b/lib/tasks/javascript.rake @@ -232,28 +232,30 @@ task "javascript:update" => "clean_up" do dest = "#{path}/#{filename}" FileUtils.mkdir_p(path) unless File.exist?(path) + + if src.include? "ace.js" + versions["ace/ace.js"] = versions.delete("ace.js") + + themes = %w[theme-chrome theme-chaos] + + themes.each do |file| + versions["ace/#{file}.js"] = "#{package_dir_name}/#{package_version}/#{file}.js" + end + + ace_root = "#{library_src}/ace-builds/src-min-noconflict/" + + addtl_files = %w[ext-searchbox mode-html mode-scss mode-sql mode-yaml worker-html].concat( + themes, + ) + + dest_path = dest.split("/")[0..-2].join("/") + addtl_files.each { |file| FileUtils.cp_r("#{ace_root}#{file}.js", dest_path) } + end end else dest = "#{vendor_js}/#{filename}" end - if src.include? "ace.js" - versions["ace/ace.js"] = versions.delete("ace.js") - ace_root = "#{library_src}/ace-builds/src-min-noconflict/" - addtl_files = %w[ - ext-searchbox - mode-html - mode-scss - mode-sql - mode-yaml - theme-chrome - theme-chaos - worker-html - ] - dest_path = dest.split("/")[0..-2].join("/") - addtl_files.each { |file| FileUtils.cp_r("#{ace_root}#{file}.js", dest_path) } - end - STDERR.puts "New dependency added: #{dest}" unless File.exist?(dest) FileUtils.cp_r(src, dest)