trix_embed 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +120 -47
- data/app/assets/builds/trix-embed.js +38 -19
- data/app/assets/builds/trix-embed.metafile.json +1 -1
- data/app/javascript/controller.js +342 -145
- data/app/javascript/enumerable.js +19 -0
- data/app/javascript/forms.js +134 -0
- data/app/javascript/guard.js +29 -47
- data/app/javascript/index.js +9 -3
- data/app/javascript/media.js +12 -2
- data/app/javascript/metadata.js +4 -0
- data/app/javascript/renderer.js +155 -75
- data/app/javascript/store.js +13 -1
- data/app/javascript/templates.js +45 -28
- data/app/javascript/urls.js +57 -42
- data/app/models/trix_embed/attachment.rb +20 -6
- data/app/views/action_text/contents/_content.html.erb +1 -1
- data/lib/trix_embed/engine.rb +1 -1
- data/lib/trix_embed/version.rb +1 -1
- metadata +37 -4
data/app/javascript/urls.js
CHANGED
@@ -4,10 +4,10 @@
|
|
4
4
|
// @param {Function} callback - Function to be called with the URL object
|
5
5
|
// @returns {URL, null} URL object
|
6
6
|
//
|
7
|
-
export function
|
7
|
+
export function createURLObject(value, callback = url => {}) {
|
8
8
|
try {
|
9
9
|
const url = new URL(String(value).trim())
|
10
|
-
if (callback) callback(url)
|
10
|
+
if (url && callback) callback(url)
|
11
11
|
return url
|
12
12
|
} catch (_error) {
|
13
13
|
console.info(`Failed to parse URL! value='${value}']`)
|
@@ -21,67 +21,82 @@ export function createURL(value, callback = url => {}) {
|
|
21
21
|
// @param {Function} callback - Function to be called with the URL host
|
22
22
|
// @returns {String, null} URL host
|
23
23
|
//
|
24
|
-
function
|
25
|
-
let host =
|
26
|
-
createURL(value, url => (host = url.host))
|
24
|
+
function extractURLHost(value, callback = host => {}) {
|
25
|
+
let host = createURLObject(value)?.host
|
27
26
|
if (host && callback) callback(host)
|
28
27
|
return host
|
29
28
|
}
|
30
29
|
|
31
|
-
function
|
32
|
-
|
33
|
-
|
34
|
-
const value = node.nodeValue
|
35
|
-
if (!value.includes('http')) return NodeFilter.FILTER_REJECT
|
36
|
-
return NodeFilter.FILTER_ACCEPT
|
30
|
+
export function createURLTextNodeTreeWalker(element) {
|
31
|
+
return document.createTreeWalker(element, NodeFilter.SHOW_TEXT, node => {
|
32
|
+
return node.nodeValue.match(/http/gi) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
|
37
33
|
})
|
34
|
+
}
|
35
|
+
|
36
|
+
function extractURLsFromTextNodes(element) {
|
37
|
+
const urls = new Set()
|
38
|
+
const walker = createURLTextNodeTreeWalker(element)
|
38
39
|
|
39
40
|
let node
|
40
41
|
while ((node = walker.nextNode()))
|
41
42
|
node.nodeValue
|
42
43
|
.split(/\s+/)
|
43
44
|
.filter(val => val.startsWith('http'))
|
44
|
-
.forEach(match =>
|
45
|
-
createURL(match, url => {
|
46
|
-
if (!urls.includes(url.href)) urls.push(url.href)
|
47
|
-
})
|
48
|
-
)
|
45
|
+
.forEach(match => createURLObject(match, url => urls.add(url.href)))
|
49
46
|
|
50
|
-
return urls
|
47
|
+
return [...urls]
|
51
48
|
}
|
52
49
|
|
53
|
-
function
|
54
|
-
|
50
|
+
export function extractURLFromElement(element) {
|
51
|
+
if (element.src) {
|
52
|
+
const url = element.src.trim()
|
53
|
+
if (url.length) return url
|
54
|
+
}
|
55
|
+
|
56
|
+
if (element.href) {
|
57
|
+
const url = element.href.trim()
|
58
|
+
if (url.length) return url
|
59
|
+
}
|
55
60
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
+
return ''
|
62
|
+
}
|
63
|
+
|
64
|
+
function extractURLsFromElementNodes(element) {
|
65
|
+
const urls = new Set()
|
66
|
+
|
67
|
+
if (element.src) createURLObject(element.src, url => urls.add(url.href))
|
68
|
+
if (element.href) createURLObject(element.href, url => urls.add(url.href))
|
61
69
|
|
62
70
|
const elements = element.querySelectorAll('[src], [href]')
|
63
|
-
elements.forEach(el =>
|
64
|
-
createURL(el.src || el.href, url => {
|
65
|
-
if (!urls.includes(url.href)) urls.push(url.href)
|
66
|
-
})
|
67
|
-
})
|
71
|
+
elements.forEach(el => createURLObject(extractURLFromElement(el), u => urls.add(u.href)))
|
68
72
|
|
69
|
-
return urls
|
73
|
+
return [...urls]
|
70
74
|
}
|
71
75
|
|
72
|
-
export function validateURL(value, allowedHosts = []) {
|
73
|
-
|
74
|
-
|
75
|
-
return
|
76
|
+
export function validateURL(value, allowedHosts = [], blockedHosts = []) {
|
77
|
+
const host = extractURLHost(value)
|
78
|
+
|
79
|
+
if (blockedHosts.includes('*')) return false
|
80
|
+
if (blockedHosts.find(blockedHosts => host.endsWith(blockedHosts))) return false
|
81
|
+
if (allowedHosts.find(allowedHosts => host.endsWith(allowedHosts))) return true
|
82
|
+
|
83
|
+
if (allowedHosts.includes('*')) {
|
84
|
+
if (host) return true
|
85
|
+
if (value.startsWith('data:')) return true
|
86
|
+
if (value.startsWith('news:')) return true
|
87
|
+
if (value.startsWith('tel:')) return true
|
88
|
+
}
|
89
|
+
|
90
|
+
return false
|
76
91
|
}
|
77
92
|
|
78
93
|
export function extractURLHosts(values) {
|
79
|
-
return
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
94
|
+
return [
|
95
|
+
...values.reduce((hosts, value) => {
|
96
|
+
extractURLHost(value, host => hosts.add(host))
|
97
|
+
return hosts
|
98
|
+
}, new Set())
|
99
|
+
]
|
85
100
|
}
|
86
101
|
|
87
102
|
// Extracts all URLs from an HTML element (all inclusive i.e. elements and text nodes)
|
@@ -89,8 +104,8 @@ export function extractURLHosts(values) {
|
|
89
104
|
// @param {HTMLElement} element - HTML element
|
90
105
|
// @returns {String[]} list of unique URLs
|
91
106
|
//
|
92
|
-
export function
|
93
|
-
const elementURLs =
|
107
|
+
export function extractURLs(element) {
|
108
|
+
const elementURLs = extractURLsFromElementNodes(element)
|
94
109
|
const textNodeURLs = extractURLsFromTextNodes(element)
|
95
110
|
const uniqueURLs = new Set([...elementURLs, ...textNodeURLs])
|
96
111
|
return [...uniqueURLs]
|
@@ -6,14 +6,28 @@ module TrixEmbed
|
|
6
6
|
include GlobalID::Identification
|
7
7
|
include ActionText::Attachable
|
8
8
|
|
9
|
-
CONTENT_TYPE = "application/vnd.trix-embed"
|
10
9
|
ALLOWED_TAGS = ActionText::ContentHelper.allowed_tags + %w[iframe]
|
11
|
-
ALLOWED_ATTRIBUTES =
|
10
|
+
ALLOWED_ATTRIBUTES = (
|
11
|
+
ActionText::ContentHelper.allowed_attributes + %w[
|
12
|
+
allow
|
13
|
+
allowfullscreen
|
14
|
+
allowpaymentrequest
|
15
|
+
credentialless
|
16
|
+
csp
|
17
|
+
data-trix-embed
|
18
|
+
data-trix-embed-error
|
19
|
+
data-trix-embed-prohibited
|
20
|
+
data-trix-embed-warning
|
21
|
+
loading
|
22
|
+
referrerpolicy
|
23
|
+
sandbox
|
24
|
+
srcdoc
|
25
|
+
]) - %w[class style]
|
12
26
|
|
13
27
|
class << self
|
14
|
-
def
|
28
|
+
def rewrite_for_display(content)
|
15
29
|
fragment = Nokogiri::HTML.fragment(content)
|
16
|
-
matches = fragment.css("#{ActionText::Attachment.tag_name}[sgid][content-type='#{
|
30
|
+
matches = fragment.css("#{ActionText::Attachment.tag_name}[sgid][content-type='#{Mime::Type.lookup_by_extension(:trix_embed_attachment)}']")
|
17
31
|
|
18
32
|
matches.each do |match|
|
19
33
|
attachment = ActionText::Attachment.from_node(match)
|
@@ -37,9 +51,9 @@ module TrixEmbed
|
|
37
51
|
fragment.to_html.html_safe
|
38
52
|
end
|
39
53
|
|
40
|
-
def
|
54
|
+
def rewrite_for_storage(trix_html)
|
41
55
|
fragment = Nokogiri::HTML.fragment(trix_html)
|
42
|
-
matches = fragment.css("[data-trix-attachment][data-trix-content-type='#{
|
56
|
+
matches = fragment.css("[data-trix-attachment][data-trix-content-type='#{Mime::Type.lookup_by_extension(:trix_embed_attachment)}']")
|
43
57
|
|
44
58
|
matches.each do |match|
|
45
59
|
data = JSON.parse(match["data-trix-attachment"]).deep_transform_keys(&:underscore)
|
@@ -1 +1 @@
|
|
1
|
-
<%= TrixEmbed::Attachment.
|
1
|
+
<%= TrixEmbed::Attachment.rewrite_for_display render_action_text_content(content) %>
|
data/lib/trix_embed/engine.rb
CHANGED
@@ -16,7 +16,7 @@ module TrixEmbed
|
|
16
16
|
config.trix_embed = ActiveSupport::OrderedOptions.new
|
17
17
|
|
18
18
|
initializer "trix_embed.configuration" do
|
19
|
-
Mime::Type.register "
|
19
|
+
Mime::Type.register "trix-embed/attachment", :trix_embed_attachment
|
20
20
|
|
21
21
|
ActiveSupport.on_load :action_controller do
|
22
22
|
helper TrixEmbed::ApplicationHelper
|
data/lib/trix_embed/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trix_embed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Hopkins (hopsoft)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.15'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: amazing_print
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: capybara
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -220,6 +234,20 @@ dependencies:
|
|
220
234
|
- - ">="
|
221
235
|
- !ruby/object:Gem::Version
|
222
236
|
version: '0'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: rerun
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
type: :development
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
223
251
|
- !ruby/object:Gem::Dependency
|
224
252
|
name: rexml
|
225
253
|
requirement: !ruby/object:Gem::Requirement
|
@@ -304,7 +332,8 @@ dependencies:
|
|
304
332
|
- - ">="
|
305
333
|
- !ruby/object:Gem::Version
|
306
334
|
version: '0'
|
307
|
-
description:
|
335
|
+
description: Take control over what external links and embedded media is permitted
|
336
|
+
in the Trix editor via copy/paste
|
308
337
|
email:
|
309
338
|
- natehop@gmail.com
|
310
339
|
executables: []
|
@@ -318,9 +347,12 @@ files:
|
|
318
347
|
- app/helpers/trix_embed/application_helper.rb
|
319
348
|
- app/javascript/controller.js
|
320
349
|
- app/javascript/encryption.js
|
350
|
+
- app/javascript/enumerable.js
|
351
|
+
- app/javascript/forms.js
|
321
352
|
- app/javascript/guard.js
|
322
353
|
- app/javascript/index.js
|
323
354
|
- app/javascript/media.js
|
355
|
+
- app/javascript/metadata.js
|
324
356
|
- app/javascript/renderer.js
|
325
357
|
- app/javascript/store.js
|
326
358
|
- app/javascript/templates.js
|
@@ -358,5 +390,6 @@ requirements: []
|
|
358
390
|
rubygems_version: 3.1.6
|
359
391
|
signing_key:
|
360
392
|
specification_version: 4
|
361
|
-
summary:
|
393
|
+
summary: Take control over what external links and embedded media is permitted in
|
394
|
+
the Trix editor via copy/paste
|
362
395
|
test_files: []
|