mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 09:42:02 +08:00
FEATURE: server side support for upload:// markdown
This allows uploads to be specified using short sha1 hash instead of full URL Client side change is pending
This commit is contained in:
parent
b00747fd49
commit
bcf7dc38c2
|
@ -14,3 +14,4 @@
|
|||
//= 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
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// 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 longUrls = state.md.options.discourse.lookupImageUrls(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) {
|
||||
helper.whiteList(['img[data-orig-src]']);
|
||||
helper.registerPlugin(md => {
|
||||
md.core.ruler.push('image-protocol', rule);
|
||||
});
|
||||
}
|
|
@ -21,6 +21,7 @@ export function buildOptions(state) {
|
|||
lookupAvatarByPostNumber,
|
||||
emojiUnicodeReplacer,
|
||||
lookupInlineOnebox,
|
||||
lookupImageUrls,
|
||||
previewing,
|
||||
linkify,
|
||||
censoredWords
|
||||
|
@ -58,6 +59,7 @@ export function buildOptions(state) {
|
|||
mentionLookup: state.mentionLookup,
|
||||
emojiUnicodeReplacer,
|
||||
lookupInlineOnebox,
|
||||
lookupImageUrls,
|
||||
censoredWords,
|
||||
allowedHrefSchemes: siteSettings.allowed_href_schemes ? siteSettings.allowed_href_schemes.split('|') : null,
|
||||
markdownIt: true,
|
||||
|
|
|
@ -4,6 +4,7 @@ require_dependency "url_helper"
|
|||
require_dependency "db_helper"
|
||||
require_dependency "validators/upload_validator"
|
||||
require_dependency "file_store/local_store"
|
||||
require_dependency "base62"
|
||||
|
||||
class Upload < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
@ -53,6 +54,17 @@ class Upload < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def short_url
|
||||
"upload://#{Base62.encode(sha1.hex)}.#{extension}"
|
||||
end
|
||||
|
||||
def self.sha1_from_short_url(url)
|
||||
if url =~ /(upload:\/\/)?([a-zA-Z0-9]+)(\..*)?/
|
||||
sha1 = Base62.decode($2).to_s(16)
|
||||
sha1.length == 40 ? sha1 : nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.generate_digest(path)
|
||||
Digest::SHA1.file(path).hexdigest
|
||||
end
|
||||
|
|
35
lib/base62.rb
Normal file
35
lib/base62.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Modified version of: https://github.com/steventen/base62-rb
|
||||
|
||||
module Base62
|
||||
KEYS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze
|
||||
KEYS_HASH = KEYS.each_char.with_index.inject({}) { |h, (k, v)| h[k] = v; h }
|
||||
BASE = KEYS.length
|
||||
|
||||
# Encodes base10 (decimal) number to base62 string.
|
||||
def self.encode(num)
|
||||
return "0" if num == 0
|
||||
return nil if num < 0
|
||||
|
||||
str = ""
|
||||
while num > 0
|
||||
# prepend base62 charaters
|
||||
str = KEYS[num % BASE] + str
|
||||
num = num / BASE
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
# Decodes base62 string to a base10 (decimal) number.
|
||||
def self.decode(str)
|
||||
num = 0
|
||||
i = 0
|
||||
len = str.length - 1
|
||||
# while loop is faster than each_char or other 'idiomatic' way
|
||||
while i < str.length
|
||||
pow = BASE**(len - i)
|
||||
num += KEYS_HASH[str[i]] * pow
|
||||
i += 1
|
||||
end
|
||||
num
|
||||
end
|
||||
end
|
|
@ -165,6 +165,7 @@ module PrettyText
|
|||
__optInput.customEmoji = #{custom_emoji.to_json};
|
||||
__optInput.emojiUnicodeReplacer = __emojiUnicodeReplacer;
|
||||
__optInput.lookupInlineOnebox = __lookupInlineOnebox;
|
||||
__optInput.lookupImageUrls = __lookupImageUrls;
|
||||
#{opts[:linkify] == false ? "__optInput.linkify = false;" : ""}
|
||||
__optInput.censoredWords = #{WordWatcher.words_for_action(:censor).join('|').to_json};
|
||||
JS
|
||||
|
|
|
@ -45,6 +45,30 @@ module PrettyText
|
|||
end
|
||||
end
|
||||
|
||||
def lookup_image_urls(urls)
|
||||
map = {}
|
||||
result = {}
|
||||
|
||||
urls.each do |url|
|
||||
sha1 = Upload.sha1_from_short_url(url)
|
||||
map[url] = sha1 if sha1
|
||||
end
|
||||
|
||||
if map.length > 0
|
||||
reverse_map = map.invert
|
||||
|
||||
Upload.where(sha1: map.values).pluck(:sha1, :url).each do |row|
|
||||
sha1, url = row
|
||||
|
||||
if short_url = reverse_map[sha1]
|
||||
result[short_url] = url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def lookup_inline_onebox(url)
|
||||
InlineOneboxer.lookup(url)
|
||||
end
|
||||
|
|
|
@ -53,6 +53,10 @@ function __lookupInlineOnebox(url) {
|
|||
return __helpers.lookup_inline_onebox(url);
|
||||
}
|
||||
|
||||
function __lookupImageUrls(urls) {
|
||||
return __helpers.lookup_image_urls(urls);
|
||||
}
|
||||
|
||||
function __getTopicInfo(i) {
|
||||
return __helpers.get_topic_info(i);
|
||||
}
|
||||
|
|
BIN
public/images/transparent.png
Executable file
BIN
public/images/transparent.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 68 B |
|
@ -1059,4 +1059,51 @@ HTML
|
|||
end
|
||||
end
|
||||
|
||||
describe "image decoding" do
|
||||
|
||||
it "can decode upload:// for default setup" do
|
||||
upload = Fabricate(:upload)
|
||||
|
||||
raw = <<~RAW
|
||||
![upload](#{upload.short_url})
|
||||
|
||||
- ![upload](#{upload.short_url})
|
||||
|
||||
- test
|
||||
- ![upload](#{upload.short_url})
|
||||
RAW
|
||||
|
||||
cooked = <<~HTML
|
||||
<p><img src="#{upload.url}" alt="upload"></p>
|
||||
<ul>
|
||||
<li>
|
||||
<p><img src="#{upload.url}" alt="upload"></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>test</p>
|
||||
<ul>
|
||||
<li><img src="#{upload.url}" alt="upload"></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
HTML
|
||||
|
||||
expect(PrettyText.cook(raw)).to eq(cooked.strip)
|
||||
end
|
||||
|
||||
it "can place a blank image if we can not find the upload" do
|
||||
|
||||
raw = "![upload](upload://abcABC.png)"
|
||||
|
||||
cooked = <<~HTML
|
||||
<p><img src="/images/transparent.png" alt="upload" data-orig-src="upload://abcABC.png"></p>
|
||||
HTML
|
||||
|
||||
puts PrettyText.cook(raw)
|
||||
|
||||
expect(PrettyText.cook(raw)).to eq(cooked.strip)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -113,4 +113,21 @@ describe Upload do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.short_url' do
|
||||
it "should generate a correct short url" do
|
||||
upload = Upload.new(sha1: 'bda2c513e1da04f7b4e99230851ea2aafeb8cc4e', extension: 'png')
|
||||
expect(upload.short_url).to eq('upload://r3AYqESanERjladb4vBB7VsMBm6.png')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.sha1_from_short_url' do
|
||||
it "should be able to look up sha1" do
|
||||
sha1 = 'bda2c513e1da04f7b4e99230851ea2aafeb8cc4e'
|
||||
|
||||
expect(Upload.sha1_from_short_url('upload://r3AYqESanERjladb4vBB7VsMBm6.png')).to eq(sha1)
|
||||
expect(Upload.sha1_from_short_url('upload://r3AYqESanERjladb4vBB7VsMBm6')).to eq(sha1)
|
||||
expect(Upload.sha1_from_short_url('r3AYqESanERjladb4vBB7VsMBm6')).to eq(sha1)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user