diff --git a/app/assets/javascripts/discourse/app/components/cook-text.js b/app/assets/javascripts/discourse/app/components/cook-text.js index 27441d835b2..cd3a7836d35 100644 --- a/app/assets/javascripts/discourse/app/components/cook-text.js +++ b/app/assets/javascripts/discourse/app/components/cook-text.js @@ -1,8 +1,8 @@ import Component from "@ember/component"; -import { cookAsync } from "discourse/lib/text"; -import { ajax } from "discourse/lib/ajax"; -import { resolveAllShortUrls } from "pretty-text/upload-short-url"; import { afterRender } from "discourse-common/utils/decorators"; +import { ajax } from "discourse/lib/ajax"; +import { cookAsync } from "discourse/lib/text"; +import { resolveAllShortUrls } from "pretty-text/upload-short-url"; const CookText = Component.extend({ cooked: null, @@ -17,7 +17,7 @@ const CookText = Component.extend({ @afterRender _resolveUrls() { - resolveAllShortUrls(ajax, this.siteSettings, this.element); + resolveAllShortUrls(ajax, this.siteSettings, this.element, this.opts); } }); diff --git a/app/assets/javascripts/discourse/app/templates/components/reviewable-queued-post.hbs b/app/assets/javascripts/discourse/app/templates/components/reviewable-queued-post.hbs index 215b4e7005c..a5abc9d00b6 100644 --- a/app/assets/javascripts/discourse/app/templates/components/reviewable-queued-post.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/reviewable-queued-post.hbs @@ -18,9 +18,7 @@
{{reviewable-post-header reviewable=reviewable createdBy=reviewable.created_by tagName=""}} -
- {{cook-text reviewable.payload.raw}} -
+ {{cook-text reviewable.payload.raw class="post-body" opts=(hash removeMissing=true)}} {{yield}}
diff --git a/app/assets/javascripts/pretty-text/addon/upload-short-url.js b/app/assets/javascripts/pretty-text/addon/upload-short-url.js index 5ccdc41417a..070881fdc4a 100644 --- a/app/assets/javascripts/pretty-text/addon/upload-short-url.js +++ b/app/assets/javascripts/pretty-text/addon/upload-short-url.js @@ -1,4 +1,6 @@ import { debounce } from "@ember/runloop"; +import I18n from "I18n"; + let _cache = {}; export function lookupCachedUploadUrl(shortUrl) { @@ -43,7 +45,13 @@ export function resetCache() { _cache = {}; } -function retrieveCachedUrl(upload, siteSettings, dataAttribute, callback) { +function retrieveCachedUrl( + upload, + siteSettings, + dataAttribute, + opts, + callback +) { const cachedUpload = lookupCachedUploadUrl( upload.getAttribute(`data-${dataAttribute}`) ); @@ -53,6 +61,42 @@ function retrieveCachedUrl(upload, siteSettings, dataAttribute, callback) { upload.removeAttribute(`data-${dataAttribute}`); if (url !== MISSING) { callback(url); + } else if (opts && opts.removeMissing) { + const style = getComputedStyle(document.body); + const canvas = document.createElement("canvas"); + canvas.width = upload.width; + canvas.height = upload.height; + + const context = canvas.getContext("2d"); + + // Draw background + context.fillStyle = getComputedStyle(document.body).backgroundColor; + context.strokeRect(0, 0, canvas.width, canvas.height); + + // Draw border + context.lineWidth = 2; + context.strokeStyle = getComputedStyle(document.body).color; + context.strokeRect(0, 0, canvas.width, canvas.height); + + let fontSize = 25; + const text = I18n.t("image_removed"); + + // Fill text size to fit the canvas + let textSize; + do { + --fontSize; + context.font = `${fontSize}px ${style.fontFamily}`; + textSize = context.measureText(text); + } while (textSize.width > canvas.width); + + context.fillStyle = getComputedStyle(document.body).color; + context.fillText( + text, + (canvas.width - textSize.width) / 2, + (canvas.height + fontSize) / 2 + ); + + upload.parentNode.replaceChild(canvas, upload); } } } @@ -79,23 +123,23 @@ function getAttributeBasedUrl(dataAttribute, cachedUpload, siteSettings) { return cachedUpload.short_path; } -function _loadCachedShortUrls(uploadElements, siteSettings) { +function _loadCachedShortUrls(uploadElements, siteSettings, opts) { uploadElements.forEach(upload => { switch (upload.tagName) { case "A": - retrieveCachedUrl(upload, siteSettings, "orig-href", url => { + retrieveCachedUrl(upload, siteSettings, "orig-href", opts, url => { upload.href = url; }); break; case "IMG": - retrieveCachedUrl(upload, siteSettings, "orig-src", url => { + retrieveCachedUrl(upload, siteSettings, "orig-src", opts, url => { upload.src = url; }); break; case "SOURCE": // video/audio tag > source tag - retrieveCachedUrl(upload, siteSettings, "orig-src", url => { + retrieveCachedUrl(upload, siteSettings, "orig-src", opts, url => { if (url.startsWith(`//${window.location.host}`)) { let hostRegex = new RegExp("//" + window.location.host, "g"); url = url.replace(hostRegex, ""); @@ -120,7 +164,7 @@ function _loadCachedShortUrls(uploadElements, siteSettings) { }); } -function _loadShortUrls(uploads, ajax, siteSettings) { +function _loadShortUrls(uploads, ajax, siteSettings, opts) { let urls = [...uploads].map(upload => { return ( upload.getAttribute("data-orig-src") || @@ -129,17 +173,17 @@ function _loadShortUrls(uploads, ajax, siteSettings) { }); return lookupUncachedUploadUrls(urls, ajax).then(() => - _loadCachedShortUrls(uploads, siteSettings) + _loadCachedShortUrls(uploads, siteSettings, opts) ); } -export function resolveAllShortUrls(ajax, siteSettings, scope) { +export function resolveAllShortUrls(ajax, siteSettings, scope, opts) { const attributes = "img[data-orig-src], a[data-orig-href], source[data-orig-src]"; let shortUploadElements = scope.querySelectorAll(attributes); if (shortUploadElements.length > 0) { - _loadCachedShortUrls(shortUploadElements, siteSettings); + _loadCachedShortUrls(shortUploadElements, siteSettings, opts); shortUploadElements = scope.querySelectorAll(attributes); if (shortUploadElements.length > 0) { @@ -150,6 +194,7 @@ export function resolveAllShortUrls(ajax, siteSettings, scope) { shortUploadElements, ajax, siteSettings, + opts, 450, true ); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index c77ef6dd27c..4b13817ba8f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3401,6 +3401,8 @@ en: safe_mode: enabled: "Safe mode is enabled, to exit safe mode close this browser window" + image_removed: "(image removed)" + # This section is exported to the javascript for i18n in the admin section admin_js: type_to_filter: "type to filter..."