FIX: Replace links to removed uploads from reviewables with a placeholder (#10180)

FIX: Replace links to removed uploads from reviewables with a placeholder (#10180)

diff --git a/app/assets/javascripts/discourse/app/components/cook-text.js b/app/assets/javascripts/discourse/app/components/cook-text.js
index 27441d8..cd3a783 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 { 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";
-import { afterRender } from "discourse-common/utils/decorators";
 
 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 215b4e7..a5abc9d 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 @@
   <div class="post-contents">
     {{reviewable-post-header reviewable=reviewable createdBy=reviewable.created_by tagName=""}}
 
-    <div class="post-body">
-      {{cook-text reviewable.payload.raw}}
-    </div>
+    {{cook-text reviewable.payload.raw class="post-body" opts=(hash removeMissing=true)}}
 
     {{yield}}
   </div>
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 5ccdc41..070881f 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 c77ef6d..4b13817 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..."

GitHub sha: 275b7480

This commit appears in #10180 which was approved by ZogStriP, ZogStriP, and ZogStriP. It was merged by nbianca.