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..."