diff --git a/app/assets/javascripts/discourse/app/templates/modal/feature-topic.hbs b/app/assets/javascripts/discourse/app/templates/modal/feature-topic.hbs index fddb84f15a..efc5bdc5f8 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/feature-topic.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/feature-topic.hbs @@ -147,7 +147,7 @@ {{#if model.isBanner}} {{d-button action=(action "removeBanner") icon="thumbtack" label="topic.feature.remove_banner" class="btn-primary"}} {{else}} - {{d-button action=(action "makeBanner") icon="thumbtack" label="topic.feature.make_banner" class="btn-primary"}} + {{d-button action=(action "makeBanner") icon="thumbtack" label="topic.feature.make_banner" class="btn-primary make-banner"}} {{/if}}

diff --git a/app/assets/javascripts/discourse/app/templates/tags/index.hbs b/app/assets/javascripts/discourse/app/templates/tags/index.hbs index be37746670..fc04e0209f 100644 --- a/app/assets/javascripts/discourse/app/templates/tags/index.hbs +++ b/app/assets/javascripts/discourse/app/templates/tags/index.hbs @@ -19,14 +19,16 @@
-{{#each model.extras.categories as |category|}} - {{tag-list tags=category.tags sortProperties=sortProperties categoryId=category.id}} -{{/each}} +
+ {{#each model.extras.categories as |category|}} + {{tag-list tags=category.tags sortProperties=sortProperties categoryId=category.id}} + {{/each}} -{{#each model.extras.tag_groups as |tagGroup|}} - {{tag-list tags=tagGroup.tags sortProperties=sortProperties tagGroupName=tagGroup.name}} -{{/each}} + {{#each model.extras.tag_groups as |tagGroup|}} + {{tag-list tags=tagGroup.tags sortProperties=sortProperties tagGroupName=tagGroup.name}} + {{/each}} -{{#if model}} - {{tag-list tags=model sortProperties=sortProperties titleKey=otherTagsTitleKey}} -{{/if}} + {{#if model}} + {{tag-list tags=model sortProperties=sortProperties titleKey=otherTagsTitleKey}} + {{/if}} +
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js new file mode 100644 index 0000000000..22cc046d42 --- /dev/null +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -0,0 +1,48 @@ +"use strict"; + +const EmberApp = require("ember-cli/lib/broccoli/ember-app"); +const resolve = require("path").resolve; +const mergeTrees = require("broccoli-merge-trees"); +const concat = require("broccoli-concat"); +const babel = require("broccoli-babel-transpiler"); +const path = require("path"); +const funnel = require("broccoli-funnel"); + +function prettyTextEngine(vendorJs, engine) { + let engineTree = babel(`../pretty-text/engines/${engine}`, { + plugins: ["@babel/plugin-transform-modules-amd"], + moduleIds: true, + + getModuleId(name) { + return `pretty-text/engines/${engine}/${path.basename(name)}`; + }, + }); + + let markdownIt = funnel(vendorJs, { files: ["markdown-it.js"] }); + return concat(mergeTrees([engineTree, markdownIt]), { + outputFile: `assets/${engine}.js`, + }); +} + +module.exports = function (defaults) { + let discourseRoot = resolve("../../../.."); + let vendorJs = discourseRoot + "/vendor/assets/javascripts/"; + + let app = new EmberApp(defaults, { autoRun: false }); + + // WARNING: We should only import scripts here if they are not in NPM. + // For example: our very specific version of bootstrap-modal. + app.import(vendorJs + "bootbox.js"); + app.import(vendorJs + "bootstrap-modal.js"); + app.import(vendorJs + "jquery.ui.widget.js"); + app.import(vendorJs + "jquery.fileupload.js"); + app.import(vendorJs + "jquery.autoellipsis-1.0.10.js"); + + return mergeTrees([ + app.toTree(), + concat(app.options.adminTree, { + outputFile: `assets/admin.js`, + }), + prettyTextEngine(vendorJs, "discourse-markdown"), + ]); +}; diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json new file mode 100644 index 0000000000..97a11a3d6d --- /dev/null +++ b/app/assets/javascripts/discourse/package.json @@ -0,0 +1,59 @@ +{ + "name": "discourse", + "version": "0.0.0", + "private": true, + "description": "Small description for discourse-frontend goes here", + "repository": "", + "license": "MIT", + "author": "", + "directories": { + "doc": "doc", + "test": "tests" + }, + "scripts": { + "build": "ember build", + "start": "ember serve", + "test": "ember test" + }, + "devDependencies": { + "@ember/optional-features": "^1.1.0", + "@glimmer/component": "^1.0.0", + "@popperjs/core": "^2.4.4", + "admin": "^1.0.0", + "broccoli-asset-rev": "^3.0.0", + "discourse-common": "^1.0.0", + "discourse-hbr": "^1.0.0", + "discourse-widget-hbs": "^1.0.0", + "ember-auto-import": "^1.5.3", + "ember-buffered-proxy": "^2.0.0-beta.0", + "ember-cli": "~3.15.2", + "ember-cli-app-version": "^3.2.0", + "ember-cli-babel": "^7.13.0", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-htmlbars": "^4.2.0", + "ember-cli-inject-live-reload": "^2.0.1", + "ember-cli-sri": "^2.1.1", + "ember-cli-uglify": "^3.0.0", + "ember-export-application-global": "^2.0.1", + "ember-load-initializers": "^2.1.1", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-qunit": "^4.6.0", + "ember-source": "~3.15.0", + "loader.js": "^4.7.0", + "message-bus-client": "^3.3.0", + "mousetrap": "^1.6.5", + "mousetrap-global-bind": "^1.1.0", + "pretender": "^3.4.3", + "pretty-text": "^1.0.0", + "qunit-dom": "^0.9.2", + "select-kit": "^1.0.0", + "sinon": "^9.2.0", + "virtual-dom": "^2.1.1" + }, + "engines": { + "node": "8.* || >= 10.*" + }, + "ember": { + "edition": "default" + } +} diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js new file mode 100644 index 0000000000..fdf76361dc --- /dev/null +++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js @@ -0,0 +1,190 @@ +(function () { + // TODO: These are needed to load plugins because @ember has its own loader. + // We should find a nicer way to do this. + const EMBER_MODULES = { + "@ember/array": { + default: Ember.Array, + A: Ember.A, + isArray: Ember.isArray, + }, + "@ember/array/proxy": { + default: Ember.ArrayProxy, + }, + "@ember/component": { + default: Ember.Component, + }, + "@ember/controller": { + default: Ember.Controller, + inject: Ember.inject.controller, + }, + "@ember/debug": { + warn: Ember.warn, + }, + "@ember/object": { + action: Ember._action, + default: Ember.Object, + get: Ember.get, + getProperties: Ember.getProperties, + set: Ember.set, + setProperties: Ember.setProperties, + computed: Ember.computed, + defineProperty: Ember.defineProperty, + }, + "@ember/object/computed": { + alias: Ember.computed.alias, + and: Ember.computed.and, + bool: Ember.computed.bool, + collect: Ember.computed.collect, + deprecatingAlias: Ember.computed.deprecatingAlias, + empty: Ember.computed.empty, + equal: Ember.computed.equal, + filter: Ember.computed.filter, + filterBy: Ember.computed.filterBy, + gt: Ember.computed.gt, + gte: Ember.computed.gte, + intersect: Ember.computed.intersect, + lt: Ember.computed.lt, + lte: Ember.computed.lte, + map: Ember.computed.map, + mapBy: Ember.computed.mapBy, + match: Ember.computed.match, + max: Ember.computed.max, + min: Ember.computed.min, + none: Ember.computed.none, + not: Ember.computed.not, + notEmpty: Ember.computed.notEmpty, + oneWay: Ember.computed.oneWay, + or: Ember.computed.or, + readOnly: Ember.computed.readOnly, + reads: Ember.computed.reads, + setDiff: Ember.computed.setDiff, + sort: Ember.computed.sort, + sum: Ember.computed.sum, + union: Ember.computed.union, + uniq: Ember.computed.uniq, + uniqBy: Ember.computed.uniqBy, + }, + "@ember/object/mixin": { default: Ember.Mixin }, + "@ember/object/proxy": { default: Ember.ObjectProxy }, + "@ember/object/promise-proxy-mixin": { default: Ember.PromiseProxyMixin }, + "@ember/object/evented": { + default: Ember.Evented, + on: Ember.on, + }, + "@ember/routing/route": { default: Ember.Route }, + "@ember/routing/router": { default: Ember.Router }, + "@ember/runloop": { + bind: Ember.run.bind, + cancel: Ember.run.cancel, + debounce: Ember.testing ? Ember.run : Ember.run.debounce, + later: Ember.run.later, + next: Ember.run.next, + once: Ember.run.once, + run: Ember.run, + schedule: Ember.run.schedule, + scheduleOnce: Ember.run.scheduleOnce, + throttle: Ember.run.throttle, + }, + "@ember/service": { + default: Ember.Service, + inject: Ember.inject.service, + }, + "@ember/template": { + htmlSafe: Ember.String.htmlSafe, + }, + "@ember/utils": { + isBlank: Ember.isBlank, + isEmpty: Ember.isEmpty, + isNone: Ember.isNone, + isPresent: Ember.isPresent, + }, + }; + Object.keys(EMBER_MODULES).forEach((mod) => { + define(mod, () => EMBER_MODULES[mod]); + }); + + // TODO: Remove this and have resolver find the templates + const prefix = "discourse/templates/"; + const adminPrefix = "admin/templates/"; + let len = prefix.length; + Object.keys(requirejs.entries).forEach(function (key) { + if (key.indexOf(prefix) === 0) { + Ember.TEMPLATES[key.substr(len)] = require(key).default; + } else if (key.indexOf(adminPrefix) === 0) { + Ember.TEMPLATES[key] = require(key).default; + } + }); + + // TODO: Eliminate this global + window.virtualDom = require("virtual-dom"); + + let head = document.getElementsByTagName("head")[0]; + function loadScript(src) { + return new Promise((resolve, reject) => { + let script = document.createElement("script"); + script.onload = () => resolve(); + script.src = src; + head.appendChild(script); + }); + } + + let isTesting = require("discourse-common/config/environment").isTesting; + + let element = document.querySelector( + `meta[name="discourse/config/environment"]` + ); + const config = JSON.parse( + decodeURIComponent(element.getAttribute("content")) + ); + fetch("/bootstrap.json") + .then((res) => res.json()) + .then((data) => { + config.bootstrap = data.bootstrap; + + // We know better, we packaged this. + config.bootstrap.setup_data.markdown_it_url = + "/assets/discourse-markdown.js"; + + let locale = data.bootstrap.locale_script; + + (data.bootstrap.stylesheets || []).forEach((s) => { + let link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute("href", s.href); + if (s.media) { + link.setAttribute("media", s.media); + } + if (s.target) { + link.setAttribute("data-target", s.target); + } + if (s.theme_id) { + link.setAttribute("data-theme-id", s.theme_id); + } + head.append(link); + }); + + let pluginJs = data.bootstrap.plugin_js; + if (isTesting()) { + // pluginJs = pluginJs.concat(data.bootstrap.plugin_test_js); + } + + pluginJs.forEach((src) => { + let script = document.createElement("script"); + script.setAttribute("src", src); + head.append(script); + }); + + loadScript(locale).then(() => { + define("I18n", ["exports"], function (exports) { + return I18n; + }); + window.__widget_helpers = require("discourse-widget-hbs/helpers").default; + let extras = (data.bootstrap.extra_locales || []).map(loadScript); + return Promise.all(extras).then(() => { + const event = new CustomEvent("discourse-booted", { detail: config }); + document.dispatchEvent(event); + }); + }); + }); +})(); diff --git a/app/assets/javascripts/discourse/testem.js b/app/assets/javascripts/discourse/testem.js new file mode 100644 index 0000000000..bef0cc9d5d --- /dev/null +++ b/app/assets/javascripts/discourse/testem.js @@ -0,0 +1,20 @@ +module.exports = { + test_page: "tests/index.html?hidepassed", + disable_watching: true, + launch_in_ci: ["Chrome"], + launch_in_dev: ["Chrome"], + browser_args: { + Chrome: { + ci: [ + // --no-sandbox is needed when running Chrome inside a container + process.env.CI ? "--no-sandbox" : null, + "--headless", + "--disable-dev-shm-usage", + "--disable-software-rasterizer", + "--mute-audio", + "--remote-debugging-port=0", + "--window-size=1440,900", + ].filter(Boolean), + }, + }, +};