mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Support [description|attachment](upload://<short-sha>) in MD. (#7603)
This commit is contained in:
committed by
Penar Musaraj
parent
42818b810e
commit
b1d3c678ca
@@ -36,7 +36,7 @@ import {
|
||||
import {
|
||||
cacheShortUploadUrl,
|
||||
resolveAllShortUrls
|
||||
} from "pretty-text/image-short-url";
|
||||
} from "pretty-text/upload-short-url";
|
||||
|
||||
import {
|
||||
INLINE_ONEBOX_LOADING_CSS_CLASS,
|
||||
|
||||
@@ -13,7 +13,7 @@ const CookText = Ember.Component.extend({
|
||||
// pretty text may only be loaded now
|
||||
Ember.run.next(() =>
|
||||
window
|
||||
.requireModule("pretty-text/image-short-url")
|
||||
.requireModule("pretty-text/upload-short-url")
|
||||
.resolveAllShortUrls(ajax)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -444,15 +444,9 @@ export function getUploadMarkdown(upload) {
|
||||
) {
|
||||
return uploadLocation(upload.url);
|
||||
} else {
|
||||
return (
|
||||
'<a class="attachment" href="' +
|
||||
upload.url +
|
||||
'">' +
|
||||
upload.original_filename +
|
||||
"</a> (" +
|
||||
I18n.toHumanSize(upload.filesize) +
|
||||
")\n"
|
||||
);
|
||||
return `[${upload.original_filename} (${I18n.toHumanSize(
|
||||
upload.filesize
|
||||
)})|attachment](${upload.short_url})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
//= require ./pretty-text/engines/discourse-markdown/newline
|
||||
//= require ./pretty-text/engines/discourse-markdown/html-img
|
||||
//= require ./pretty-text/engines/discourse-markdown/text-post-process
|
||||
//= require ./pretty-text/engines/discourse-markdown/image-protocol
|
||||
//= require ./pretty-text/engines/discourse-markdown/upload-protocol
|
||||
//= require ./pretty-text/engines/discourse-markdown/inject-line-number
|
||||
//= require ./pretty-text/engines/discourse-markdown/d-wrap
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
//= require ./pretty-text/sanitizer
|
||||
//= require ./pretty-text/oneboxer
|
||||
//= require ./pretty-text/inline-oneboxer
|
||||
//= require ./pretty-text/image-short-url
|
||||
//= require ./pretty-text/upload-short-url
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { default as WhiteLister } from "pretty-text/white-lister";
|
||||
import { sanitize } from "pretty-text/sanitizer";
|
||||
import guid from "pretty-text/guid";
|
||||
import { ATTACHMENT_CSS_CLASS } from "pretty-text/upload-short-url";
|
||||
|
||||
function deprecate(feature, name) {
|
||||
return function() {
|
||||
@@ -187,6 +188,26 @@ function setupImageDimensions(md) {
|
||||
md.renderer.rules.image = renderImage;
|
||||
}
|
||||
|
||||
function renderAttachment(tokens, idx, options, env, slf) {
|
||||
const linkOpenToken = tokens[idx];
|
||||
const linkTextToken = tokens[idx + 1];
|
||||
const split = linkTextToken.content.split("|");
|
||||
const isValid = !linkOpenToken.attrs[
|
||||
linkOpenToken.attrIndex("data-orig-href")
|
||||
];
|
||||
|
||||
if (isValid && split.length === 2 && split[1] === ATTACHMENT_CSS_CLASS) {
|
||||
linkOpenToken.attrs.unshift(["class", split[1]]);
|
||||
linkTextToken.content = split[0];
|
||||
}
|
||||
|
||||
return slf.renderToken(tokens, idx, options);
|
||||
}
|
||||
|
||||
function setupAttachments(md) {
|
||||
md.renderer.rules.link_open = renderAttachment;
|
||||
}
|
||||
|
||||
let Helpers;
|
||||
|
||||
export function setup(opts, siteSettings, state) {
|
||||
@@ -276,6 +297,7 @@ export function setup(opts, siteSettings, state) {
|
||||
setupUrlDecoding(opts.engine);
|
||||
setupHoister(opts.engine);
|
||||
setupImageDimensions(opts.engine);
|
||||
setupAttachments(opts.engine);
|
||||
setupBlockBBCode(opts.engine);
|
||||
setupInlineBBCode(opts.engine);
|
||||
setupTextPostProcessRuler(opts.engine);
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// add image to array if src has an upload
|
||||
function addImage(images, token) {
|
||||
if (token.attrs) {
|
||||
for (let i = 0; i < token.attrs.length; i++) {
|
||||
if (token.attrs[i][1].indexOf("upload://") === 0) {
|
||||
images.push([token, i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rule(state) {
|
||||
let images = [];
|
||||
|
||||
for (let i = 0; i < state.tokens.length; i++) {
|
||||
let blockToken = state.tokens[i];
|
||||
|
||||
if (blockToken.tag === "img") {
|
||||
addImage(images, blockToken);
|
||||
}
|
||||
|
||||
if (!blockToken.children) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < blockToken.children.length; j++) {
|
||||
let token = blockToken.children[j];
|
||||
if (token.tag === "img") {
|
||||
addImage(images, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (images.length > 0) {
|
||||
let srcList = images.map(([token, srcIndex]) => token.attrs[srcIndex][1]);
|
||||
let lookup = state.md.options.discourse.lookupImageUrls;
|
||||
let longUrls = (lookup && lookup(srcList)) || {};
|
||||
|
||||
images.forEach(([token, srcIndex]) => {
|
||||
let origSrc = token.attrs[srcIndex][1];
|
||||
let mapped = longUrls[origSrc];
|
||||
if (mapped) {
|
||||
token.attrs[srcIndex][1] = mapped;
|
||||
} else {
|
||||
token.attrs[srcIndex][1] = state.md.options.discourse.getURL(
|
||||
"/images/transparent.png"
|
||||
);
|
||||
token.attrs.push(["data-orig-src", origSrc]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function setup(helper) {
|
||||
const opts = helper.getOptions();
|
||||
if (opts.previewing) helper.whiteList(["img.resizable"]);
|
||||
helper.whiteList(["img[data-orig-src]"]);
|
||||
helper.registerPlugin(md => {
|
||||
md.core.ruler.push("image-protocol", rule);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// add image to array if src has an upload
|
||||
function addImage(uploads, token) {
|
||||
if (token.attrs) {
|
||||
for (let i = 0; i < token.attrs.length; i++) {
|
||||
if (token.attrs[i][1].indexOf("upload://") === 0) {
|
||||
uploads.push([token, i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rule(state) {
|
||||
let uploads = [];
|
||||
|
||||
for (let i = 0; i < state.tokens.length; i++) {
|
||||
let blockToken = state.tokens[i];
|
||||
|
||||
if (blockToken.tag === "img" || blockToken.tag === "a") {
|
||||
addImage(uploads, blockToken);
|
||||
}
|
||||
|
||||
if (!blockToken.children) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < blockToken.children.length; j++) {
|
||||
let token = blockToken.children[j];
|
||||
if (token.tag === "img" || token.tag === "a") {
|
||||
addImage(uploads, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uploads.length > 0) {
|
||||
let srcList = uploads.map(([token, srcIndex]) => token.attrs[srcIndex][1]);
|
||||
let lookup = state.md.options.discourse.lookupUploadUrls;
|
||||
let longUrls = (lookup && lookup(srcList)) || {};
|
||||
|
||||
uploads.forEach(([token, srcIndex]) => {
|
||||
let origSrc = token.attrs[srcIndex][1];
|
||||
let mapped = longUrls[origSrc];
|
||||
|
||||
switch (token.tag) {
|
||||
case "img":
|
||||
if (mapped) {
|
||||
token.attrs[srcIndex][1] = mapped.url;
|
||||
} else {
|
||||
token.attrs[srcIndex][1] = state.md.options.discourse.getURL(
|
||||
"/images/transparent.png"
|
||||
);
|
||||
|
||||
token.attrs.push(["data-orig-src", origSrc]);
|
||||
}
|
||||
break;
|
||||
case "a":
|
||||
if (mapped) {
|
||||
token.attrs[srcIndex][1] = mapped.short_path;
|
||||
} else {
|
||||
token.attrs[srcIndex][1] = state.md.options.discourse.getURL(
|
||||
"/404"
|
||||
);
|
||||
|
||||
token.attrs.push(["data-orig-href", origSrc]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function setup(helper) {
|
||||
const opts = helper.getOptions();
|
||||
if (opts.previewing) helper.whiteList(["img.resizable"]);
|
||||
helper.whiteList(["img[data-orig-src]", "a[data-orig-href]"]);
|
||||
|
||||
helper.registerPlugin(md => {
|
||||
md.core.ruler.push("upload-protocol", rule);
|
||||
});
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
let _cache = {};
|
||||
|
||||
export function lookupCachedUploadUrl(shortUrl) {
|
||||
return _cache[shortUrl];
|
||||
}
|
||||
|
||||
export function lookupUncachedUploadUrls(urls, ajax) {
|
||||
return ajax("/uploads/lookup-urls", {
|
||||
method: "POST",
|
||||
data: { short_urls: urls }
|
||||
}).then(uploads => {
|
||||
uploads.forEach(upload =>
|
||||
cacheShortUploadUrl(upload.short_url, upload.url)
|
||||
);
|
||||
urls.forEach(url =>
|
||||
cacheShortUploadUrl(url, lookupCachedUploadUrl(url) || "missing")
|
||||
);
|
||||
return uploads;
|
||||
});
|
||||
}
|
||||
|
||||
export function cacheShortUploadUrl(shortUrl, url) {
|
||||
_cache[shortUrl] = url;
|
||||
}
|
||||
|
||||
export function resetCache() {
|
||||
_cache = {};
|
||||
}
|
||||
|
||||
function _loadCachedShortUrls($images) {
|
||||
$images.each((idx, image) => {
|
||||
const $image = $(image);
|
||||
const url = lookupCachedUploadUrl($image.data("orig-src"));
|
||||
|
||||
if (url) {
|
||||
$image.removeAttr("data-orig-src");
|
||||
if (url !== "missing") {
|
||||
$image.attr("src", url);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _loadShortUrls($images, ajax) {
|
||||
const urls = $images.toArray().map(img => $(img).data("orig-src"));
|
||||
return lookupUncachedUploadUrls(urls, ajax).then(() =>
|
||||
_loadCachedShortUrls($images)
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveAllShortUrls(ajax) {
|
||||
let $shortUploadUrls = $("img[data-orig-src]");
|
||||
|
||||
if ($shortUploadUrls.length > 0) {
|
||||
_loadCachedShortUrls($shortUploadUrls);
|
||||
|
||||
$shortUploadUrls = $("img[data-orig-src]");
|
||||
if ($shortUploadUrls.length > 0) {
|
||||
// this is carefully batched so we can do a leading debounce (trigger right away)
|
||||
return Ember.run.debounce(
|
||||
null,
|
||||
() => _loadShortUrls($shortUploadUrls, ajax),
|
||||
450,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export function buildOptions(state) {
|
||||
lookupPrimaryUserGroupByPostNumber,
|
||||
formatUsername,
|
||||
emojiUnicodeReplacer,
|
||||
lookupImageUrls,
|
||||
lookupUploadUrls,
|
||||
previewing,
|
||||
linkify,
|
||||
censoredWords
|
||||
@@ -65,7 +65,7 @@ export function buildOptions(state) {
|
||||
lookupPrimaryUserGroupByPostNumber,
|
||||
formatUsername,
|
||||
emojiUnicodeReplacer,
|
||||
lookupImageUrls,
|
||||
lookupUploadUrls,
|
||||
censoredWords,
|
||||
allowedHrefSchemes: siteSettings.allowed_href_schemes
|
||||
? siteSettings.allowed_href_schemes.split("|")
|
||||
|
||||
111
app/assets/javascripts/pretty-text/upload-short-url.js.es6
Normal file
111
app/assets/javascripts/pretty-text/upload-short-url.js.es6
Normal file
@@ -0,0 +1,111 @@
|
||||
let _cache = {};
|
||||
|
||||
export function lookupCachedUploadUrl(shortUrl) {
|
||||
return _cache[shortUrl] || {};
|
||||
}
|
||||
|
||||
const MISSING = "missing";
|
||||
|
||||
export function lookupUncachedUploadUrls(urls, ajax) {
|
||||
return ajax("/uploads/lookup-urls", {
|
||||
method: "POST",
|
||||
data: { short_urls: urls }
|
||||
}).then(uploads => {
|
||||
uploads.forEach(upload => {
|
||||
cacheShortUploadUrl(upload.short_url, {
|
||||
url: upload.url,
|
||||
short_path: upload.short_path
|
||||
});
|
||||
});
|
||||
|
||||
urls.forEach(url =>
|
||||
cacheShortUploadUrl(url, {
|
||||
url: lookupCachedUploadUrl(url).url || MISSING,
|
||||
short_path: lookupCachedUploadUrl(url).short_path || MISSING
|
||||
})
|
||||
);
|
||||
|
||||
return uploads;
|
||||
});
|
||||
}
|
||||
|
||||
export function cacheShortUploadUrl(shortUrl, value) {
|
||||
_cache[shortUrl] = value;
|
||||
}
|
||||
|
||||
export function resetCache() {
|
||||
_cache = {};
|
||||
}
|
||||
|
||||
export const ATTACHMENT_CSS_CLASS = "attachment";
|
||||
|
||||
function _loadCachedShortUrls($uploads) {
|
||||
$uploads.each((idx, upload) => {
|
||||
const $upload = $(upload);
|
||||
let url;
|
||||
|
||||
switch (upload.tagName) {
|
||||
case "A":
|
||||
url = lookupCachedUploadUrl($upload.data("orig-href")).short_path;
|
||||
|
||||
if (url) {
|
||||
$upload.removeAttr("data-orig-href");
|
||||
|
||||
if (url !== MISSING) {
|
||||
$upload.attr("href", url);
|
||||
const content = $upload.text().split("|");
|
||||
|
||||
if (content[1] === ATTACHMENT_CSS_CLASS) {
|
||||
$upload.addClass(ATTACHMENT_CSS_CLASS);
|
||||
$upload.text(content[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "IMG":
|
||||
url = lookupCachedUploadUrl($upload.data("orig-src")).url;
|
||||
|
||||
if (url) {
|
||||
$upload.removeAttr("data-orig-src");
|
||||
|
||||
if (url !== MISSING) {
|
||||
$upload.attr("src", url);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _loadShortUrls($uploads, ajax) {
|
||||
const urls = $uploads.toArray().map(upload => {
|
||||
const $upload = $(upload);
|
||||
return $upload.data("orig-src") || $upload.data("orig-href");
|
||||
});
|
||||
|
||||
return lookupUncachedUploadUrls(urls, ajax).then(() =>
|
||||
_loadCachedShortUrls($uploads)
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveAllShortUrls(ajax) {
|
||||
const attributes = "img[data-orig-src], a[data-orig-href]";
|
||||
let $shortUploadUrls = $(attributes);
|
||||
|
||||
if ($shortUploadUrls.length > 0) {
|
||||
_loadCachedShortUrls($shortUploadUrls);
|
||||
|
||||
$shortUploadUrls = $(attributes);
|
||||
if ($shortUploadUrls.length > 0) {
|
||||
// this is carefully batched so we can do a leading debounce (trigger right away)
|
||||
return Ember.run.debounce(
|
||||
null,
|
||||
() => _loadShortUrls($shortUploadUrls, ajax),
|
||||
450,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user