card-mod-content 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/card/content/chunk/escaped_literal.rb +27 -0
- data/lib/card/content/chunk/keep_escaped_literal.rb +26 -0
- data/lib/card/content/chunk/link.rb +124 -0
- data/lib/card/content/chunk/nest.rb +126 -0
- data/lib/card/content/chunk/query_reference.rb +95 -0
- data/lib/card/content/chunk/reference.rb +60 -0
- data/lib/card/content/chunk/uri.rb +145 -0
- data/lib/card/content/chunk/view_stub.rb +62 -0
- data/set/abstract/code_file.rb +100 -0
- data/set/abstract/haml_file.rb +24 -0
- data/set/abstract/lock.rb +26 -0
- data/set/abstract/templated_nests.rb +7 -0
- data/set/abstract/vendor_code_file.rb +9 -0
- data/set/all/chunk.rb +142 -0
- data/set/all/templating.rb +70 -0
- data/set/right/default.rb +34 -0
- data/set/right/structure.rb +72 -0
- data/set/self/default.rb +2 -0
- data/set/self/structure.rb +4 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bb52b2764cccee68e9de8d73edf69ae8be71529d81728b009fbf2f93bac63964
|
4
|
+
data.tar.gz: 576999f00236a239917c9671a301358987554468e9e6b1640616f3c5a6cd1134
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d375445000c6b0de05d4f455f24b45dde8999bc38e1e5021342865e74aa8d8e02ad057460411fa79c52892d15bdc97c0897ed4fe59a8a5cb37ab5ab08b234575
|
7
|
+
data.tar.gz: b01ba00743f9392eee0ace1b6717fcbd5a2ca21c5b4b619a01ef51fd66ecb4e3c15628d6d90e90d0850cff22a8693ef4a2e3c516ae2c6b2c8f16c539cd82d813
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Card
|
4
|
+
class Content
|
5
|
+
module Chunk
|
6
|
+
# These are basic chunks that have a pattern and can be protected.
|
7
|
+
# They are used by rendering process to prevent wiki rendering
|
8
|
+
# occuring within literal areas such as <code> and <pre> blocks
|
9
|
+
# and within HTML tags.
|
10
|
+
class EscapedLiteral < Abstract
|
11
|
+
FULL_RE = { "[" => /\A\\\[\[[^\]]*\]\]/,
|
12
|
+
"{" => /\A\\\{\{[^\}]*\}\}/ }.freeze
|
13
|
+
Card::Content::Chunk.register_class self,
|
14
|
+
prefix_re: '\\\\(?:\\[\\[|\\{\\{)',
|
15
|
+
idx_char: '\\'
|
16
|
+
|
17
|
+
def self.full_re prefix
|
18
|
+
FULL_RE[prefix[1, 1]]
|
19
|
+
end
|
20
|
+
|
21
|
+
def interpret match, _content
|
22
|
+
@process_chunk = match[0].sub(/^\\(.)/, format.escape_literal('\1'))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Card
|
4
|
+
class Content
|
5
|
+
module Chunk
|
6
|
+
# These are basic chunks that have a pattern and can be protected.
|
7
|
+
# This chunk is used for markdown processing to ensure that
|
8
|
+
# the escaping survives the markdown rendering.
|
9
|
+
class KeepEscapedLiteral < Abstract
|
10
|
+
FULL_RE = { "[" => /\A\\\[\[[^\]]*\]\]/,
|
11
|
+
"{" => /\A\\\{\{[^\}]*\}\}/ }.freeze
|
12
|
+
Card::Content::Chunk.register_class self,
|
13
|
+
prefix_re: '\\\\(?:\\[\\[|\\{\\{)',
|
14
|
+
idx_char: '\\'
|
15
|
+
|
16
|
+
def self.full_re prefix
|
17
|
+
FULL_RE[prefix[1, 1]]
|
18
|
+
end
|
19
|
+
|
20
|
+
def interpret match, _content
|
21
|
+
@process_chunk = match[0].sub(/^\\(.)/, '\\\\\\\\\1')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# require File.expand_path("../reference", __FILE__)
|
4
|
+
load File.expand_path("../reference.rb", __FILE__)
|
5
|
+
|
6
|
+
class Card
|
7
|
+
class Content
|
8
|
+
module Chunk
|
9
|
+
# extend ActiveSupport::Autoload
|
10
|
+
# autoload :Reference , "reference"
|
11
|
+
|
12
|
+
class Link < Card::Content::Chunk::Reference
|
13
|
+
CODE = "L".freeze # L for "Link"
|
14
|
+
attr_reader :link_text
|
15
|
+
# Groups: $1, [$2]: [[$1]] or [[$1|$2]] or $3, $4: [$3][$4]
|
16
|
+
Card::Content::Chunk.register_class self,
|
17
|
+
prefix_re: '\\[\\[',
|
18
|
+
full_re: /\A\[\[([^\]]+)\]\]/,
|
19
|
+
idx_char: "["
|
20
|
+
def reference_code
|
21
|
+
CODE
|
22
|
+
end
|
23
|
+
|
24
|
+
def interpret match, _content
|
25
|
+
target, @link_text = target_and_link_text match[1]
|
26
|
+
|
27
|
+
@link_text = objectify @link_text
|
28
|
+
if target.match? %r{^(/|https?:|mailto:)}
|
29
|
+
@explicit_link = objectify target
|
30
|
+
else
|
31
|
+
@name = target
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def target_and_link_text raw_syntax
|
36
|
+
return unless raw_syntax
|
37
|
+
|
38
|
+
if (i = divider_index raw_syntax) # [[A | B]]
|
39
|
+
[raw_syntax[0..(i - 1)], raw_syntax[(i + 1)..-1]] # [A, B]
|
40
|
+
else # [[ A ]]
|
41
|
+
[raw_syntax, nil] # [A, nil]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def divider_index string
|
46
|
+
# there's probably a better way to do the following.
|
47
|
+
# point is to find the first pipe that's not inside an nest
|
48
|
+
return unless string.index "|"
|
49
|
+
string_copy = string.dup
|
50
|
+
string.scan(/\{\{[^\}]*\}\}/) do |incl|
|
51
|
+
string_copy.gsub! incl, ("x" * incl.length)
|
52
|
+
end
|
53
|
+
string_copy.index "|"
|
54
|
+
end
|
55
|
+
|
56
|
+
# view options
|
57
|
+
def options
|
58
|
+
link_text ? { title: link_text } : {}
|
59
|
+
end
|
60
|
+
|
61
|
+
def objectify raw
|
62
|
+
return unless raw
|
63
|
+
raw.strip!
|
64
|
+
if raw.match?(/(^|[^\\])\{\{/)
|
65
|
+
Card::Content.new raw, format
|
66
|
+
else
|
67
|
+
raw
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def render_link view: :link, explicit_link_opts: {}
|
72
|
+
@link_text = render_obj @link_text
|
73
|
+
|
74
|
+
if @explicit_link
|
75
|
+
@explicit_link = render_obj @explicit_link
|
76
|
+
format.link_to_resource @explicit_link, @link_text, explicit_link_opts
|
77
|
+
elsif @name
|
78
|
+
format.with_nest_mode :normal do
|
79
|
+
format.nest referee_name, options.merge(view: view)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def link_target
|
85
|
+
if @explicit_link
|
86
|
+
render_obj @explicit_link
|
87
|
+
elsif @name
|
88
|
+
referee_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def process_chunk
|
93
|
+
@process_chunk ||= render_link
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect
|
97
|
+
"<##{self.class}:e[#{@explicit_link}]n[#{@name}]l[#{@link_text}]" \
|
98
|
+
"p[#{@process_chunk}] txt:#{@text}>"
|
99
|
+
end
|
100
|
+
|
101
|
+
def replace_reference old_name, new_name
|
102
|
+
replace_name_reference old_name, new_name
|
103
|
+
replace_link_text old_name, new_name
|
104
|
+
@text =
|
105
|
+
@link_text.nil? ? "[[#{referee_name}]]" : "[[#{referee_name}|#{@link_text}]]"
|
106
|
+
end
|
107
|
+
|
108
|
+
def replace_link_text old_name, new_name
|
109
|
+
if @link_text.is_a?(Card::Content)
|
110
|
+
@link_text.find_chunks(Card::Content::Chunk::Reference).each do |chunk|
|
111
|
+
chunk.replace_reference old_name, new_name
|
112
|
+
end
|
113
|
+
elsif @link_text.present?
|
114
|
+
@link_text = old_name.to_name.sub_in(@link_text, with: new_name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def explicit_link?
|
119
|
+
@explicit_link
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# require File.expand_path("../reference", __FILE__)
|
4
|
+
|
5
|
+
class Card
|
6
|
+
class Content
|
7
|
+
module Chunk
|
8
|
+
# Handler for nest chunks: {{example}}
|
9
|
+
class Nest < Reference
|
10
|
+
attr_reader :options
|
11
|
+
DEFAULT_OPTION = :view # a value without a key is interpreted as view
|
12
|
+
|
13
|
+
Chunk.register_class(self, prefix_re: '\\{\\{',
|
14
|
+
full_re: /\A\{\{([^\{\}]*)\}\}/,
|
15
|
+
idx_char: "{")
|
16
|
+
|
17
|
+
def interpret match, _content
|
18
|
+
in_brackets = strip_tags match[1]
|
19
|
+
name, @opt_lists = in_brackets.split "|", 2
|
20
|
+
name = name.to_s.strip
|
21
|
+
if name.match?(/^\#/)
|
22
|
+
@process_chunk = name.match?(/^\#\#/) ? "" : visible_comment(in_brackets)
|
23
|
+
else
|
24
|
+
@options = interpret_options.merge nest_name: name, nest_syntax: in_brackets
|
25
|
+
@name = name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def strip_tags string
|
30
|
+
# note: not using ActionView's strip_tags here
|
31
|
+
# because this needs to be super fast.
|
32
|
+
string.gsub(/\<[^\>]*\>/, "")
|
33
|
+
end
|
34
|
+
|
35
|
+
def visible_comment message
|
36
|
+
"<!-- #{CGI.escapeHTML message} -->"
|
37
|
+
end
|
38
|
+
|
39
|
+
def interpret_options
|
40
|
+
raw_options = @opt_lists.to_s.split("|").reverse
|
41
|
+
raw_options.inject(nil) do |prev_level, level_options|
|
42
|
+
interpret_piped_options level_options, prev_level
|
43
|
+
end || {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def interpret_piped_options list_string, items
|
47
|
+
options_hash = items.nil? ? {} : { items: items }
|
48
|
+
option_string_to_hash list_string, options_hash
|
49
|
+
options_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def option_string_to_hash list_string, options_hash
|
53
|
+
each_option(list_string) do |key, value|
|
54
|
+
key = key.to_sym
|
55
|
+
if key == :item
|
56
|
+
options_hash[:items] ||= {}
|
57
|
+
options_hash[:items][:view] = value
|
58
|
+
elsif Card::View::Options.shark_keys.include? key
|
59
|
+
options_hash[key] = value
|
60
|
+
# else
|
61
|
+
# handle other keys
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect
|
67
|
+
"<##{self.class}:n[#{@name}] p[#{@process_chunk}] txt:#{@text}>"
|
68
|
+
end
|
69
|
+
|
70
|
+
def process_chunk
|
71
|
+
return @process_chunk if @process_chunk
|
72
|
+
|
73
|
+
referee_name
|
74
|
+
@processed = format.content_nest(@options)
|
75
|
+
# this is not necessarily text, sometimes objects for json
|
76
|
+
end
|
77
|
+
|
78
|
+
def replace_reference old_name, new_name
|
79
|
+
replace_name_reference old_name, new_name
|
80
|
+
nest_body = [@name.to_s, @opt_lists].compact * "|"
|
81
|
+
@text = "{{#{nest_body}}}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def explicit_view= view
|
85
|
+
return if @options[:view]
|
86
|
+
# could check to make sure it's not already the default...
|
87
|
+
if @text.match?(/\|/)
|
88
|
+
@text.sub! "|", "|#{view};"
|
89
|
+
else
|
90
|
+
@text.sub! "}}", "|#{view}}}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def main?
|
95
|
+
nest_name == "_main"
|
96
|
+
end
|
97
|
+
|
98
|
+
def nest_name
|
99
|
+
options&.dig :nest_name
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.gsub string
|
103
|
+
string.gsub(/\{\{([^\}]*)\}\}/) do |_match|
|
104
|
+
yield(Regexp.last_match[1])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def raw_options
|
109
|
+
@opt_lists
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def each_option attr_string
|
115
|
+
return if attr_string.blank?
|
116
|
+
attr_string.strip.split(";").each do |pair|
|
117
|
+
# key is optional for view option
|
118
|
+
value, key = pair.split(":", 2).reverse
|
119
|
+
key ||= self.class::DEFAULT_OPTION.to_s
|
120
|
+
yield key.strip, value.strip
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Card
|
4
|
+
class Content
|
5
|
+
module Chunk
|
6
|
+
# This should find +Alfred+ in expressions like
|
7
|
+
# 1) {"name":"Alfred"}
|
8
|
+
# 2a) {"name":["in","Alfred"]}
|
9
|
+
# 3a) {"plus_right":["Alfred"]}
|
10
|
+
# but not in
|
11
|
+
# 2b) "content":"foo", "Alfred":"bar"
|
12
|
+
# 3b) {"name":["Alfred", "Toni"]} ("Alfred" is an operator here)
|
13
|
+
# It's not possible to distinguish between 2a) and 2b) or 3a) and 3b) with a
|
14
|
+
# simple regex, hence we use a too general regex and check for query keywords
|
15
|
+
# after the match, which of course means that we don't find references with
|
16
|
+
# query keywords as name
|
17
|
+
|
18
|
+
require File.expand_path("reference", __dir__)
|
19
|
+
class QueryReference < Reference
|
20
|
+
QUERY_KEYWORDS = ::Set.new(
|
21
|
+
(
|
22
|
+
::Card::Query::MODIFIERS.keys +
|
23
|
+
::Card::Query::OPERATORS.keys +
|
24
|
+
::Card::Query::ATTRIBUTES.keys +
|
25
|
+
::Card::Query::CONJUNCTIONS.keys +
|
26
|
+
%w[desc asc count]
|
27
|
+
).map(&:to_s)
|
28
|
+
)
|
29
|
+
|
30
|
+
Card::Content::Chunk.register_class(
|
31
|
+
self, prefix_re: '(?<=[:,\\[])\\s*"',
|
32
|
+
# we check for colon, comma or square bracket before a quote
|
33
|
+
# we have to use a lookbehind, otherwise
|
34
|
+
# if the colon matches it would be
|
35
|
+
# identified mistakenly as an URI chunk
|
36
|
+
full_re: /\A\s*"([^"]+)"/,
|
37
|
+
idx_char: '"'
|
38
|
+
)
|
39
|
+
|
40
|
+
# OPTIMIZE: instead of comma or square bracket check for operator followed
|
41
|
+
# by comma or "plus_right"|"plus_left"|"plus" followed by square bracket
|
42
|
+
# something like
|
43
|
+
# prefix_patterns = [
|
44
|
+
# "\"\\s*(?:#{Card::Query::OPERATORS.keys.join('|')})\"\\s*,",
|
45
|
+
# "\"\\s*(?:#{Card::Query::PLUS_ATTRIBUTES}.keys
|
46
|
+
# .join('|')})\\s*:\\s*\\[\\s*",
|
47
|
+
# "\"\\s*(?:#{(QUERY_KEYWORDS - Card::Query::PLUS_ATTRIBUTES)
|
48
|
+
# .join('|')})\"\\s*:",
|
49
|
+
# ]
|
50
|
+
# prefix_re: '(?<=#{prefix_patterns.join('|')})\\s*"'
|
51
|
+
# But: What do we do with the "in" operator? After the first value there is
|
52
|
+
# no prefix which we can use to detect the following values as
|
53
|
+
# QueryReference chunks
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def full_match content, prefix
|
57
|
+
# matches cardnames that are not keywords
|
58
|
+
# FIXME: would not match cardnames that are keywords
|
59
|
+
match, offset = super(content, prefix)
|
60
|
+
return if !match || keyword?(match[1])
|
61
|
+
|
62
|
+
[match, offset]
|
63
|
+
end
|
64
|
+
|
65
|
+
def keyword? str
|
66
|
+
return unless str
|
67
|
+
|
68
|
+
QUERY_KEYWORDS.include?(str.tr(" ", "_").downcase)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def interpret match, _content
|
73
|
+
@name = match[1]
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_chunk
|
77
|
+
@process_chunk ||= @text
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
"<##{self.class}:n[#{@name}] p[#{@process_chunk}] txt:#{@text}>"
|
82
|
+
end
|
83
|
+
|
84
|
+
def replace_reference old_name, new_name
|
85
|
+
replace_name_reference old_name, new_name
|
86
|
+
@text = "\"#{@name}\""
|
87
|
+
end
|
88
|
+
|
89
|
+
def reference_code
|
90
|
+
"Q" # for "Query"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Card
|
4
|
+
class Content
|
5
|
+
module Chunk
|
6
|
+
class Reference < Abstract
|
7
|
+
attr_writer :referee_name
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def referee_name
|
11
|
+
return if name.nil?
|
12
|
+
@referee_name ||= referee_name_from_rendered(render_obj(name))
|
13
|
+
@referee_name = @referee_name.absolute(card.name).to_name
|
14
|
+
rescue Card::Error::NotFound
|
15
|
+
# do not break on missing id/codename references.
|
16
|
+
end
|
17
|
+
|
18
|
+
def referee_name_from_rendered rendered_name
|
19
|
+
ref_card = fetch_referee_card rendered_name
|
20
|
+
ref_card ? ref_card.name : rendered_name.to_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def referee_card
|
24
|
+
@referee_card ||= referee_name && Card.fetch(referee_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def replace_name_reference old_name, new_name
|
28
|
+
@referee_card = nil
|
29
|
+
@referee_name = nil
|
30
|
+
if name.is_a? Card::Content
|
31
|
+
name.find_chunks(Chunk::Reference).each do |chunk|
|
32
|
+
chunk.replace_reference old_name, new_name
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@name = name.to_name.swap old_name, new_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def render_obj raw
|
40
|
+
if format && raw.is_a?(Card::Content)
|
41
|
+
format.process_content raw
|
42
|
+
else
|
43
|
+
raw
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def fetch_referee_card rendered_name
|
50
|
+
case rendered_name # FIXME: this should be standard fetch option.
|
51
|
+
when /^\~(\d+)$/ # get by id
|
52
|
+
Card.fetch Regexp.last_match(1).to_i
|
53
|
+
when /^\:(\w+)$/ # get by codename
|
54
|
+
Card.fetch Regexp.last_match(1).to_sym
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
# This wiki chunk matches arbitrary URIs, using patterns from the Ruby URI
|
6
|
+
# modules.
|
7
|
+
# It parses out a variety of fields that could be used by formats to format
|
8
|
+
# the links in various ways (shortening domain names, hiding email addresses)
|
9
|
+
# It matches email addresses and host.com.au domains without schemes (http://)
|
10
|
+
# but adds these on as required.
|
11
|
+
#
|
12
|
+
# The heuristic used to match a URI is designed to err on the side of caution.
|
13
|
+
# That is, it is more likely to not autolink a URI than it is to accidently
|
14
|
+
# autolink something that is not a URI. The reason behind this is it is easier
|
15
|
+
# to force a URI link by prefixing 'http://' to it than it is to escape and
|
16
|
+
# incorrectly marked up non-URI.
|
17
|
+
#
|
18
|
+
# I'm using a part of the [ISO 3166-1 Standard][iso3166] for country name
|
19
|
+
# suffixes.
|
20
|
+
# The generic names are from www.bnoack.com/data/countrycode2.html)
|
21
|
+
# [iso3166]: http://geotags.com/iso3166/
|
22
|
+
module Card::Content::Chunk
|
23
|
+
class Uri < Abstract
|
24
|
+
SCHEMES = %w[irc http https ftp ssh git sftp file ldap ldaps mailto].freeze
|
25
|
+
|
26
|
+
REJECTED_PREFIX_RE = %w{! ": " ' ](}.map { |s| Regexp.escape s } * "|"
|
27
|
+
|
28
|
+
attr_reader :uri, :link_text
|
29
|
+
delegate :to, :scheme, :host, :port, :path, :query, :fragment, to: :uri
|
30
|
+
|
31
|
+
Card::Content::Chunk.register_class(
|
32
|
+
self, prefix_re: "(?:(?!#{REJECTED_PREFIX_RE})(?:#{SCHEMES * '|'})\\:)",
|
33
|
+
full_re: /\A#{::URI.regexp(SCHEMES)}/,
|
34
|
+
idx_char: ":"
|
35
|
+
)
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def full_match content, prefix
|
39
|
+
prepend_str = if prefix[-1, 1] != ":" && config[:prepend_str]
|
40
|
+
config[:prepend_str]
|
41
|
+
else
|
42
|
+
""
|
43
|
+
end
|
44
|
+
content = prepend_str + content
|
45
|
+
match = super content, prefix
|
46
|
+
[match, prepend_str.length]
|
47
|
+
end
|
48
|
+
|
49
|
+
def context_ok? content, chunk_start
|
50
|
+
preceding_string = content[chunk_start - 2..chunk_start - 1]
|
51
|
+
preceding_string !~ /(?:#{REJECTED_PREFIX_RE})$/
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def interpret match, _content
|
56
|
+
chunk = match[0]
|
57
|
+
last_char = chunk[-1, 1]
|
58
|
+
chunk.gsub!(/(?: )+/, "")
|
59
|
+
|
60
|
+
@trailing_punctuation =
|
61
|
+
if %w[, . ) ! ? :].member?(last_char)
|
62
|
+
@text.chop!
|
63
|
+
chunk.chop!
|
64
|
+
last_char
|
65
|
+
end
|
66
|
+
chunk.sub!(/\.$/, "")
|
67
|
+
|
68
|
+
@link_text = chunk
|
69
|
+
@uri = ::URI.parse(chunk)
|
70
|
+
@process_chunk = process_uri_chunk
|
71
|
+
rescue ::URI::Error => e
|
72
|
+
# warn "rescue parse #{chunk_class}:
|
73
|
+
# '#{m}' #{e.inspect} #{e.backtrace*"\n"}"
|
74
|
+
Rails.logger.warn "rescue parse #{self.class}: #{e.inspect}"
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def process_text
|
80
|
+
@link_text
|
81
|
+
end
|
82
|
+
|
83
|
+
def process_uri_chunk
|
84
|
+
link = format.link_to_resource @link_text, process_text
|
85
|
+
"#{link}#{@trailing_punctuation}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# FIXME: DRY, merge these two into one class
|
90
|
+
class EmailUri < Uri
|
91
|
+
PREPEND_STR = "mailto:".freeze
|
92
|
+
EMAIL = '[a-zA-Z\\d](?:[-a-zA-Z\\d.]*[a-zA-Z\\d])?\\@'.freeze
|
93
|
+
|
94
|
+
Card::Content::Chunk.register_class(
|
95
|
+
self, prefix_re: "(?:(?!#{REJECTED_PREFIX_RE})#{EMAIL})\\b",
|
96
|
+
full_re: /\A#{::URI.regexp(SCHEMES)}/,
|
97
|
+
prepend_str: PREPEND_STR,
|
98
|
+
idx_char: "@"
|
99
|
+
)
|
100
|
+
|
101
|
+
# removes the prepended string from the unchanged match text
|
102
|
+
def process_text
|
103
|
+
@text = @text.sub(/^mailto:/, "")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class HostUri < Uri
|
108
|
+
GENERIC = "aero|biz|com|coop|edu|gov|info|int|mil|" \
|
109
|
+
"museum|name|net|org".freeze
|
110
|
+
|
111
|
+
COUNTRY = "ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|" \
|
112
|
+
"bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cf|cd|cg|" \
|
113
|
+
"ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|" \
|
114
|
+
"ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|fx|ga|gb|gd|ge|gf|gh|" \
|
115
|
+
"gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|" \
|
116
|
+
"il|in|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|" \
|
117
|
+
"kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|" \
|
118
|
+
"mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|" \
|
119
|
+
"no|np|nr|nt|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pt|pw|py|" \
|
120
|
+
"qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|" \
|
121
|
+
"st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|" \
|
122
|
+
"tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|" \
|
123
|
+
"za|zm|zr|zw|" \
|
124
|
+
"eu".freeze # made this separate, since it's not technically
|
125
|
+
# a country -efm
|
126
|
+
# These are needed otherwise HOST will match almost anything
|
127
|
+
|
128
|
+
TLDS = "(?:#{GENERIC}|#{COUNTRY})".freeze
|
129
|
+
# TLDS = "(?:#{GENERIC})"
|
130
|
+
|
131
|
+
PREPEND_STR = "http://".freeze
|
132
|
+
HOST = "(?:[a-zA-Z\\d](?:[-a-zA-Z\\d]*[a-zA-Z\\d])?\\.)+#{TLDS}".freeze
|
133
|
+
|
134
|
+
Card::Content::Chunk.register_class(
|
135
|
+
self, prefix_re: "(?:(?!#{REJECTED_PREFIX_RE})#{HOST})\\b",
|
136
|
+
full_re: /\A#{::URI.regexp(SCHEMES)}/,
|
137
|
+
prepend_str: PREPEND_STR
|
138
|
+
)
|
139
|
+
|
140
|
+
# removes the prepended string from the unchanged match text
|
141
|
+
def process_text
|
142
|
+
@text = @text.sub(%r{^http://}, "")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Card
|
2
|
+
class Content
|
3
|
+
module Chunk
|
4
|
+
class ViewStub < Abstract
|
5
|
+
Chunk.register_class(
|
6
|
+
self,
|
7
|
+
prefix_re: Regexp.escape("(StUb"),
|
8
|
+
full_re: /\A\(StUb(.*?)sTuB\)/m,
|
9
|
+
idx_char: "("
|
10
|
+
)
|
11
|
+
|
12
|
+
def initialize text, content
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def interpret match, _content
|
17
|
+
@stub_hash = initial_stub_hash match[1]
|
18
|
+
interpret_hash_values
|
19
|
+
end
|
20
|
+
|
21
|
+
def initial_stub_hash string
|
22
|
+
JSON.parse(string).symbolize_keys
|
23
|
+
# MessagePack.unpack(hex_to_bin(string)).symbolize_keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def hex_to_bin string
|
27
|
+
string.scan(/../).map { |x| x.hex.chr }.join
|
28
|
+
end
|
29
|
+
|
30
|
+
def interpret_hash_values
|
31
|
+
@stub_hash.keys.each do |key|
|
32
|
+
send "interpret_#{key}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def interpret_cast
|
37
|
+
@stub_hash[:cast].symbolize_keys!
|
38
|
+
end
|
39
|
+
|
40
|
+
def interpret_view_opts
|
41
|
+
@stub_hash[:view_opts].symbolize_keys!
|
42
|
+
end
|
43
|
+
|
44
|
+
def interpret_format_opts
|
45
|
+
hash = @stub_hash[:format_opts]
|
46
|
+
hash.symbolize_keys!
|
47
|
+
hash[:nest_mode] = hash[:nest_mode].to_sym
|
48
|
+
hash[:override] = hash[:override] == "true"
|
49
|
+
hash[:context_names].map!(&:to_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_chunk
|
53
|
+
@processed = format.stub_nest @stub_hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def result
|
57
|
+
@processed
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class << self
|
2
|
+
|
3
|
+
def included host_class
|
4
|
+
track_mod_name host_class, caller
|
5
|
+
end
|
6
|
+
|
7
|
+
def track_mod_name host_class, caller
|
8
|
+
host_class.mattr_accessor :file_content_mod_name
|
9
|
+
host_class.file_content_mod_name = Card::Set.mod_name(caller)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# FIXME: these should abstracted and configured on the types
|
14
|
+
# (same codes for `rake card:create:codefile`)
|
15
|
+
|
16
|
+
# @return [Array<String>, String] the name of file(s) to be loaded
|
17
|
+
def source_files
|
18
|
+
case type_id
|
19
|
+
when CoffeeScriptID then "#{codename}.js.coffee"
|
20
|
+
when JavaScriptID then "#{codename}.js"
|
21
|
+
when CssID then "#{codename}.css"
|
22
|
+
when ScssID then "#{codename}.scss"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def source_dir
|
27
|
+
case type_id
|
28
|
+
when CoffeeScriptID, JavaScriptID then "lib/javascript"
|
29
|
+
when CssID, ScssID then "lib/stylesheets"
|
30
|
+
else
|
31
|
+
"lib"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_file filename
|
36
|
+
File.join(mod_path, source_dir, filename).tap do |file_path|
|
37
|
+
return nil if unknown_file? filename, file_path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def mod_path
|
42
|
+
modname = file_content_mod_name
|
43
|
+
if (match = modname.match(/^card-mod-(\w*)/))
|
44
|
+
modname = match[1]
|
45
|
+
end
|
46
|
+
Cardio::Mod.dirs.path modname
|
47
|
+
end
|
48
|
+
|
49
|
+
def unknown_file? filename, file_path
|
50
|
+
return false if File.exist? file_path
|
51
|
+
|
52
|
+
Rails.logger.info "couldn't locate file #{filename} at #{file_path}"
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def existing_source_paths
|
57
|
+
Array.wrap(source_files).map do |filename|
|
58
|
+
find_file(filename)
|
59
|
+
end.compact
|
60
|
+
end
|
61
|
+
|
62
|
+
def source_changed? since:
|
63
|
+
existing_source_paths.any? { |path| ::File.mtime(path) > since }
|
64
|
+
end
|
65
|
+
|
66
|
+
def content
|
67
|
+
Array.wrap(source_files).map do |filename|
|
68
|
+
if (source_path = find_file filename)
|
69
|
+
Rails.logger.debug "reading file: #{source_path}"
|
70
|
+
File.read source_path
|
71
|
+
end
|
72
|
+
end.compact.join "\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
format :html do
|
76
|
+
view :input do
|
77
|
+
"Content is stored in file and can't be edited."
|
78
|
+
end
|
79
|
+
|
80
|
+
view :file_size do
|
81
|
+
"#{card.name}: #{number_to_human_size card.content.bytesize}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def short_content
|
85
|
+
fa_icon("exclamation-circle", class: "text-muted pr-2") +
|
86
|
+
wrap_with(:span, "file", class: "text-muted")
|
87
|
+
end
|
88
|
+
|
89
|
+
def standard_submit_button
|
90
|
+
multi_card_editor? ? super : ""
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def coffee_files files
|
95
|
+
files.map { |f| "script_#{f}.js.coffee" }
|
96
|
+
end
|
97
|
+
|
98
|
+
def scss_files files
|
99
|
+
files.map { |f| "style_#{f}.scss" }
|
100
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
def self.included host_class
|
3
|
+
host_class.mattr_accessor :template_path
|
4
|
+
host_class.extend Card::Set::Format::HamlPaths
|
5
|
+
host_class.template_path = host_class.haml_template_path
|
6
|
+
end
|
7
|
+
|
8
|
+
def content
|
9
|
+
File.read template_path
|
10
|
+
end
|
11
|
+
|
12
|
+
format :html do
|
13
|
+
view :input do
|
14
|
+
"Content is managed by code and cannot be edited"
|
15
|
+
end
|
16
|
+
|
17
|
+
def haml_locals
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
|
21
|
+
view :core do
|
22
|
+
haml card.content, haml_locals
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
def lock
|
2
|
+
was_already_locked = locked?
|
3
|
+
return if was_already_locked
|
4
|
+
Auth.as_bot do
|
5
|
+
lock!
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
ensure
|
9
|
+
unlock! unless was_already_locked
|
10
|
+
end
|
11
|
+
|
12
|
+
def lock_cache_key
|
13
|
+
"UPDATE-LOCK:#{key}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def locked?
|
17
|
+
Card.cache.read lock_cache_key
|
18
|
+
end
|
19
|
+
|
20
|
+
def lock!
|
21
|
+
Card.cache.write lock_cache_key, true
|
22
|
+
end
|
23
|
+
|
24
|
+
def unlock!
|
25
|
+
Card.cache.write lock_cache_key, false
|
26
|
+
end
|
data/set/all/chunk.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
|
2
|
+
def chunks content, type, named=false
|
3
|
+
content ||= self.content
|
4
|
+
type ||= Card::Content::Chunk
|
5
|
+
all_chunks = Card::Content.new(content, self).find_chunks type
|
6
|
+
named ? all_chunks.select(&:referee_name) : all_chunks
|
7
|
+
end
|
8
|
+
|
9
|
+
def reference_chunks content=nil, named=true
|
10
|
+
chunks content, Card::Content::Chunk::Reference, named
|
11
|
+
end
|
12
|
+
|
13
|
+
# named=true rejects commented nests
|
14
|
+
def nest_chunks content=nil, named=true
|
15
|
+
chunks content, Card::Content::Chunk::Nest, named
|
16
|
+
end
|
17
|
+
|
18
|
+
# named=true rejects external links (since the don't refer to a card name)
|
19
|
+
def link_chunks content=nil, named=false
|
20
|
+
chunks content, Card::Content::Chunk::Link, named
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_item_name_with_options content=nil
|
24
|
+
reference_chunks(content).each do |chunk|
|
25
|
+
options = chunk.respond_to?(:options) ? chunk.options : {}
|
26
|
+
yield chunk.referee_name, options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
format do
|
31
|
+
def nest_chunks content=nil
|
32
|
+
content ||= _render_raw
|
33
|
+
card.nest_chunks content
|
34
|
+
end
|
35
|
+
|
36
|
+
def nested_cards content=nil
|
37
|
+
nest_chunks(content).map(&:referee_card).uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
def edit_fields
|
41
|
+
voo.edit_structure || []
|
42
|
+
end
|
43
|
+
|
44
|
+
def nested_field_names content=nil
|
45
|
+
nest_chunks(content).map(&:referee_name).select { |n| field_name? n }
|
46
|
+
end
|
47
|
+
|
48
|
+
def nested_field_cards content=nil
|
49
|
+
nested_cards(content).select { |c| field_name? c.name }
|
50
|
+
end
|
51
|
+
|
52
|
+
def field_name? name
|
53
|
+
name.field_of? card.name
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Array] of Arrays. each is [nest_name, nest_options_hash]
|
57
|
+
def edit_field_configs fields_only=false
|
58
|
+
if edit_fields.present?
|
59
|
+
explicit_edit_fields_config # explicitly configured in voo or code
|
60
|
+
else
|
61
|
+
implicit_edit_fields_config fields_only # inferred from nests
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def implicit_edit_fields_config fields_only
|
66
|
+
result = []
|
67
|
+
each_nested_chunk(fields: fields_only) do |chunk|
|
68
|
+
result << [chunk.options[:nest_name], chunk.options]
|
69
|
+
end
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def each_nested_field_chunk &block
|
74
|
+
each_nested_chunk fields: true, &block
|
75
|
+
end
|
76
|
+
|
77
|
+
def each_nested_chunk content: nil, fields: false, uniq: true, virtual: true, &block
|
78
|
+
return unless block_given?
|
79
|
+
chunks = prepare_nested_chunks content, fields, uniq
|
80
|
+
process_nested_chunks chunks, virtual, &block
|
81
|
+
end
|
82
|
+
|
83
|
+
def uniq_chunks chunks
|
84
|
+
processed = ::Set.new [card.key]
|
85
|
+
chunks.select do |chunk|
|
86
|
+
key = chunk.referee_name.key
|
87
|
+
ok = !processed.include?(key)
|
88
|
+
processed << key
|
89
|
+
ok
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def field_chunks chunks
|
94
|
+
chunks.select { |chunk| field_name?(chunk.referee_name) }
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def prepare_nested_chunks content, fields, uniq
|
100
|
+
chunks = nest_chunks content
|
101
|
+
chunks = field_chunks chunks if fields
|
102
|
+
chunks = uniq_chunks chunks if uniq
|
103
|
+
chunks
|
104
|
+
end
|
105
|
+
|
106
|
+
def process_nested_chunks chunks, virtual, &block
|
107
|
+
chunks.each do |chunk|
|
108
|
+
process_nested_chunk chunk, virtual, &block
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def process_nested_chunk chunk, virtual, &block
|
113
|
+
if chunk.referee_card&.virtual?
|
114
|
+
process_nested_virtual_chunk chunk, &block unless virtual
|
115
|
+
else
|
116
|
+
yield chunk
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_virtual_chunk chunk
|
121
|
+
subformat(chunk.referee_card).each_nested_field_chunk { |sub_chunk| yield sub_chunk }
|
122
|
+
end
|
123
|
+
|
124
|
+
def explicit_edit_fields_config
|
125
|
+
edit_fields.map do |cardish, options|
|
126
|
+
field_mark = normalized_edit_field_mark cardish, options
|
127
|
+
options = normalized_edit_field_options options, Card::Name[field_mark]
|
128
|
+
[field_mark, options]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def normalized_edit_field_options options, cardname
|
133
|
+
options ||= cardname
|
134
|
+
options.is_a?(String) ? { title: options } : options
|
135
|
+
end
|
136
|
+
|
137
|
+
def normalized_edit_field_mark cardish, options
|
138
|
+
return cardish if cardish.is_a?(Card) ||
|
139
|
+
(options.is_a?(Hash) && options.delete(:absolute))
|
140
|
+
card.name.field cardish
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
def is_template?
|
3
|
+
return @is_template unless @is_template.nil?
|
4
|
+
|
5
|
+
@is_template = name.trait_name? :structure, :default
|
6
|
+
end
|
7
|
+
|
8
|
+
def is_structure?
|
9
|
+
return @is_structure unless @is_structure.nil?
|
10
|
+
|
11
|
+
@is_structure = name.trait_name? :structure
|
12
|
+
end
|
13
|
+
|
14
|
+
def template
|
15
|
+
# currently applicable templating card.
|
16
|
+
# note that a *default template is never returned for an existing card.
|
17
|
+
@template ||= begin
|
18
|
+
@virtual = false
|
19
|
+
|
20
|
+
if new_card?
|
21
|
+
new_card_template
|
22
|
+
else
|
23
|
+
structure_rule_card
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_type_id
|
29
|
+
Card.default_type_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def new_card_template
|
33
|
+
default = rule_card :default, skip_modules: true
|
34
|
+
return default unless (structure = dup_structure default&.type_id)
|
35
|
+
|
36
|
+
@virtual = true if compound?
|
37
|
+
self.type_id = structure.type_id if assign_type_to?(structure)
|
38
|
+
structure
|
39
|
+
end
|
40
|
+
|
41
|
+
def dup_structure type_id
|
42
|
+
dup_card = dup
|
43
|
+
dup_card.type_id = type_id || default_type_id
|
44
|
+
dup_card.structure_rule_card
|
45
|
+
end
|
46
|
+
|
47
|
+
def assign_type_to? structure
|
48
|
+
return if type_id == structure.type_id
|
49
|
+
structure.assigns_type?
|
50
|
+
end
|
51
|
+
|
52
|
+
def assigns_type?
|
53
|
+
# needed because not all *structure templates govern the type of set members
|
54
|
+
# for example, X+*type+*structure governs all cards of type X,
|
55
|
+
# but the content rule does not (in fact cannot) have the type X.
|
56
|
+
pattern_code = Card.quick_fetch(name.trunk_name.tag_name)&.codename
|
57
|
+
return unless pattern_code && (set_class = Set::Pattern.find pattern_code)
|
58
|
+
|
59
|
+
set_class.assigns_type
|
60
|
+
end
|
61
|
+
|
62
|
+
def structure
|
63
|
+
template&.is_structure? ? template : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def structure_rule_card
|
67
|
+
return unless (card = rule_card :structure, skip_modules: true)
|
68
|
+
|
69
|
+
card.db_content&.strip == "_self" ? nil : card
|
70
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
include_set Abstract::TemplatedNests
|
2
|
+
|
3
|
+
format :html do
|
4
|
+
view :one_line_content do
|
5
|
+
raw = _render_raw
|
6
|
+
"#{card.type_name} : #{raw.present? ? raw : '<em>empty</em>'}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def quick_form_opts
|
10
|
+
super.merge "data-update-foreign-slot":
|
11
|
+
".card-slot.quick_edit-view.RIGHT-Xinput_type,"\
|
12
|
+
".card-slot.quick_edit-view.RIGHT-Xcontent_option"\
|
13
|
+
".card-slot.quick_edit-view.RIGHT-Xcontent_option_view"
|
14
|
+
end
|
15
|
+
|
16
|
+
def quick_editor
|
17
|
+
wrap_type_formgroup do
|
18
|
+
type_field class: "type-field rule-type-field _submit-on-select"
|
19
|
+
end +
|
20
|
+
wrap_content_formgroup do
|
21
|
+
text_field :content, class: "d0-card-content _submit-after-typing"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def visible_cardtype_groups
|
26
|
+
hash = ::Card::Set::Self::Cardtype::GROUP.slice("Text", "Data", "Upload")
|
27
|
+
hash["Organize"] = ["List", "Pointer", "Link list", "Nest list"]
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def empty_ok?
|
33
|
+
true
|
34
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
include_set Abstract::TemplatedNests
|
2
|
+
|
3
|
+
format :rss do
|
4
|
+
def raw_feed_items
|
5
|
+
[card]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
format :html do
|
10
|
+
view :one_line_content do
|
11
|
+
"#{_render_type} : #{_render_raw}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def visible_cardtype_groups
|
15
|
+
hash = ::Card::Set::Self::Cardtype::GROUP.slice("Text")
|
16
|
+
hash["Organize"] = ["Search", "Nest list"]
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
event :update_structurees_references, :integrate,
|
22
|
+
when: :update_structurees_references? do
|
23
|
+
return unless (query = structuree_query)
|
24
|
+
|
25
|
+
Auth.as_bot do
|
26
|
+
query.run.each(&:update_references_out)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_structurees_references?
|
31
|
+
db_content_changed? || action == :delete
|
32
|
+
end
|
33
|
+
|
34
|
+
event :reset_cache_to_use_new_structure,
|
35
|
+
before: :update_structurees_references do
|
36
|
+
Card::Cache.reset_hard
|
37
|
+
Card::Cache.reset_soft
|
38
|
+
end
|
39
|
+
|
40
|
+
event :update_structurees_type, :finalize,
|
41
|
+
changed: :type_id, when: proc { |c| c.assigns_type? } do
|
42
|
+
update_structurees type_id: type_id
|
43
|
+
end
|
44
|
+
|
45
|
+
def structuree_names
|
46
|
+
return [] unless (query = structuree_query(return: :name))
|
47
|
+
|
48
|
+
Auth.as_bot do
|
49
|
+
query.run
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_structurees args
|
54
|
+
# note that this is not smart about overriding templating rules
|
55
|
+
# for example, if someone were to change the type of a
|
56
|
+
# +*right+*structure rule that was overridden
|
57
|
+
# by a +*type plus right+*structure rule, the override would not be respected.
|
58
|
+
return unless (query = structuree_query(return: :id))
|
59
|
+
|
60
|
+
Auth.as_bot do
|
61
|
+
query.run.each_slice(100) do |id_batch|
|
62
|
+
Card.where(id: id_batch).update_all args
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def structuree_query args={}
|
68
|
+
set_card = trunk
|
69
|
+
return unless set_card.type_id == SetID
|
70
|
+
|
71
|
+
set_card.fetch_query args
|
72
|
+
end
|
data/set/self/default.rb
ADDED
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: card-mod-content
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.11.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ethan McCutchen
|
8
|
+
- Philipp Kühl
|
9
|
+
- Gerry Gleason
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2020-12-24 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: card
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.101.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - '='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 1.101.0
|
29
|
+
description: ''
|
30
|
+
email:
|
31
|
+
- info@decko.org
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- lib/card/content/chunk/escaped_literal.rb
|
37
|
+
- lib/card/content/chunk/keep_escaped_literal.rb
|
38
|
+
- lib/card/content/chunk/link.rb
|
39
|
+
- lib/card/content/chunk/nest.rb
|
40
|
+
- lib/card/content/chunk/query_reference.rb
|
41
|
+
- lib/card/content/chunk/reference.rb
|
42
|
+
- lib/card/content/chunk/uri.rb
|
43
|
+
- lib/card/content/chunk/view_stub.rb
|
44
|
+
- set/abstract/code_file.rb
|
45
|
+
- set/abstract/haml_file.rb
|
46
|
+
- set/abstract/lock.rb
|
47
|
+
- set/abstract/templated_nests.rb
|
48
|
+
- set/abstract/vendor_code_file.rb
|
49
|
+
- set/all/chunk.rb
|
50
|
+
- set/all/templating.rb
|
51
|
+
- set/right/default.rb
|
52
|
+
- set/right/structure.rb
|
53
|
+
- set/self/default.rb
|
54
|
+
- set/self/structure.rb
|
55
|
+
homepage: http://decko.org
|
56
|
+
licenses:
|
57
|
+
- GPL-3.0
|
58
|
+
metadata:
|
59
|
+
card-mod: content
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.5'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubygems_version: 3.0.3
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: card content handling
|
79
|
+
test_files: []
|