card-mod-content 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|