cosensee 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +56 -0
- data/.rubocop_todo.yml +7 -0
- data/README.md +40 -0
- data/Rakefile +10 -0
- data/lib/cosensee/blank_bracket.rb +8 -0
- data/lib/cosensee/bracket_parser.rb +82 -0
- data/lib/cosensee/bracket_serializer.rb +26 -0
- data/lib/cosensee/code.rb +18 -0
- data/lib/cosensee/codeblock.rb +18 -0
- data/lib/cosensee/command_line.rb +18 -0
- data/lib/cosensee/decorate_bracket.rb +16 -0
- data/lib/cosensee/double_bracket.rb +20 -0
- data/lib/cosensee/empty_bracket.rb +8 -0
- data/lib/cosensee/error.rb +5 -0
- data/lib/cosensee/external_link_bracket.rb +8 -0
- data/lib/cosensee/formula_bracket.rb +8 -0
- data/lib/cosensee/hash_tag.rb +19 -0
- data/lib/cosensee/html_builder.rb +59 -0
- data/lib/cosensee/html_encodable.rb +12 -0
- data/lib/cosensee/icon_bracket.rb +8 -0
- data/lib/cosensee/image_bracket.rb +8 -0
- data/lib/cosensee/indent.rb +25 -0
- data/lib/cosensee/internal_link_bracket.rb +8 -0
- data/lib/cosensee/line.rb +47 -0
- data/lib/cosensee/line_parser.rb +253 -0
- data/lib/cosensee/link.rb +18 -0
- data/lib/cosensee/link_encodable.rb +26 -0
- data/lib/cosensee/page.rb +68 -0
- data/lib/cosensee/page_store.rb +63 -0
- data/lib/cosensee/parsed_bracket.rb +42 -0
- data/lib/cosensee/parsed_line.rb +76 -0
- data/lib/cosensee/project.rb +46 -0
- data/lib/cosensee/quote.rb +22 -0
- data/lib/cosensee/tailwind_renderer/blank_bracket.rb +11 -0
- data/lib/cosensee/tailwind_renderer/code.rb +14 -0
- data/lib/cosensee/tailwind_renderer/codeblock_builder.rb +37 -0
- data/lib/cosensee/tailwind_renderer/command_line.rb +17 -0
- data/lib/cosensee/tailwind_renderer/decorate_bracket.rb +25 -0
- data/lib/cosensee/tailwind_renderer/double_bracket.rb +13 -0
- data/lib/cosensee/tailwind_renderer/empty_bracket.rb +11 -0
- data/lib/cosensee/tailwind_renderer/external_link_bracket.rb +15 -0
- data/lib/cosensee/tailwind_renderer/formula_bracket.rb +13 -0
- data/lib/cosensee/tailwind_renderer/hash_tag.rb +17 -0
- data/lib/cosensee/tailwind_renderer/icon_bracket.rb +14 -0
- data/lib/cosensee/tailwind_renderer/image_bracket.rb +15 -0
- data/lib/cosensee/tailwind_renderer/internal_link_bracket.rb +15 -0
- data/lib/cosensee/tailwind_renderer/link.rb +14 -0
- data/lib/cosensee/tailwind_renderer/page.rb +31 -0
- data/lib/cosensee/tailwind_renderer/parsed_line.rb +30 -0
- data/lib/cosensee/tailwind_renderer/quote.rb +14 -0
- data/lib/cosensee/tailwind_renderer/text_bracket.rb +12 -0
- data/lib/cosensee/tailwind_renderer.rb +36 -0
- data/lib/cosensee/text_bracket.rb +10 -0
- data/lib/cosensee/user.rb +37 -0
- data/lib/cosensee/version.rb +5 -0
- data/lib/cosensee.rb +54 -0
- data/sig/cosensee.rbs +4 -0
- data/templates/index.html.erb +39 -0
- data/templates/page.html.erb +59 -0
- metadata +130 -0
@@ -0,0 +1,253 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Cosensee
|
6
|
+
# parse a line
|
7
|
+
class LineParser
|
8
|
+
INDENT_PATTERN = /\A([\t ]*)(.*)\z/
|
9
|
+
QUOTE_PATTERN = /\A(>)(.*)\z/
|
10
|
+
CODEBLOCK_PATTERN = /\A(code:)(.+)\z/
|
11
|
+
COMMANDLINE_PATTERN = /\A([$%]) (.+)\z/
|
12
|
+
|
13
|
+
def self.parse(line)
|
14
|
+
new.parse(line)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@bracket_parser = Cosensee::BracketParser.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse(line)
|
22
|
+
parsed_line = ParsedLine.new(rest: line)
|
23
|
+
parsed_line
|
24
|
+
.then { parse_indent(it) }
|
25
|
+
.then { parse_whole_line(it) }
|
26
|
+
.then { parse_code(it) }
|
27
|
+
.then { parse_double_bracket(it) }
|
28
|
+
.then { parse_bracket(it) }
|
29
|
+
.then { parse_url(it) }
|
30
|
+
.then { parse_hashtag(it) }
|
31
|
+
.then { done_parsing(it) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_indent(line)
|
35
|
+
matched = line.match(INDENT_PATTERN)
|
36
|
+
ParsedLine.new(indent: Cosensee::Indent.new(matched[1], matched[1]),
|
37
|
+
rest: matched[2])
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_whole_line(line)
|
41
|
+
# parse quote
|
42
|
+
matched = line.match(QUOTE_PATTERN)
|
43
|
+
if matched
|
44
|
+
line.update(rest: matched[2],
|
45
|
+
line_content: Cosensee::Quote.new(content: nil, raw: matched[0], mark: matched[1]))
|
46
|
+
return line
|
47
|
+
end
|
48
|
+
|
49
|
+
# parse codeblock
|
50
|
+
matched = line.match(CODEBLOCK_PATTERN)
|
51
|
+
if matched
|
52
|
+
return line.update(rest: nil,
|
53
|
+
line_content: Cosensee::Codeblock.new(content: matched[2], raw: matched[0]),
|
54
|
+
parsed: true)
|
55
|
+
end
|
56
|
+
|
57
|
+
# parse command line
|
58
|
+
matched = line.match(COMMANDLINE_PATTERN)
|
59
|
+
if matched
|
60
|
+
return line.update(rest: nil,
|
61
|
+
line_content: Cosensee::CommandLine.new(content: matched[2],
|
62
|
+
prompt: matched[1],
|
63
|
+
raw: matched[0]),
|
64
|
+
parsed: true)
|
65
|
+
end
|
66
|
+
|
67
|
+
line
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_code(line)
|
71
|
+
return line if line.parsed?
|
72
|
+
|
73
|
+
parsed = []
|
74
|
+
strs = line.split_rest_by('`')
|
75
|
+
loop do
|
76
|
+
str = strs.shift
|
77
|
+
unless str
|
78
|
+
return line.update(rest: nil,
|
79
|
+
content: parsed)
|
80
|
+
end
|
81
|
+
|
82
|
+
parsed << str
|
83
|
+
|
84
|
+
str = strs.shift
|
85
|
+
unless str
|
86
|
+
line.update(rest: nil,
|
87
|
+
content: parsed)
|
88
|
+
return line
|
89
|
+
end
|
90
|
+
|
91
|
+
if strs.empty?
|
92
|
+
parsed.last.concat("`#{str}")
|
93
|
+
return line.update(rest: nil,
|
94
|
+
content: parsed)
|
95
|
+
else
|
96
|
+
parsed << Code.new(str, "`#{str}`")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_hashtag(line)
|
102
|
+
return line if line.parsed?
|
103
|
+
|
104
|
+
parsed = []
|
105
|
+
|
106
|
+
line.content.each do |elem|
|
107
|
+
if elem.is_a?(String)
|
108
|
+
loop do
|
109
|
+
matched = elem.match(/(^|\s)#(\S+)/)
|
110
|
+
if matched
|
111
|
+
parsed << "#{matched.pre_match}#{matched[1]}"
|
112
|
+
parsed << Cosensee::HashTag.new(content: matched[2], raw: "##{matched[2]}")
|
113
|
+
elem = matched.post_match
|
114
|
+
else
|
115
|
+
parsed << elem
|
116
|
+
break # loop
|
117
|
+
end
|
118
|
+
end
|
119
|
+
else
|
120
|
+
parsed << elem
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
line.update(content: clean_elements(parsed))
|
125
|
+
end
|
126
|
+
|
127
|
+
def parse_url(line)
|
128
|
+
return line if line.parsed?
|
129
|
+
|
130
|
+
parsed = []
|
131
|
+
|
132
|
+
line.content.each do |elem|
|
133
|
+
if elem.is_a?(String)
|
134
|
+
loop do
|
135
|
+
matched = elem.match(%r{(^|\s)(https?://[^\s]+)})
|
136
|
+
if matched
|
137
|
+
parsed << "#{matched.pre_match}#{matched[1]}"
|
138
|
+
parsed << Cosensee::Link.new(matched[2], matched[2])
|
139
|
+
elem = matched.post_match
|
140
|
+
else
|
141
|
+
parsed << elem
|
142
|
+
break # loop
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
parsed << elem
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
line.update(content: clean_elements(parsed))
|
151
|
+
end
|
152
|
+
|
153
|
+
def parse_double_bracket(line)
|
154
|
+
return line if line.parsed?
|
155
|
+
|
156
|
+
parsed = []
|
157
|
+
|
158
|
+
line.content.each do |elem|
|
159
|
+
if elem.is_a?(String)
|
160
|
+
loop do
|
161
|
+
matched = elem.match(/\[\[(.+?)\]\]/)
|
162
|
+
if matched
|
163
|
+
parsed << matched.pre_match
|
164
|
+
parsed << Cosensee::DoubleBracket.new(content: [matched[1]], raw: matched[0])
|
165
|
+
elem = matched.post_match
|
166
|
+
else
|
167
|
+
parsed << elem
|
168
|
+
break # loop
|
169
|
+
end
|
170
|
+
end
|
171
|
+
else
|
172
|
+
parsed << elem
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
line.update(content: clean_elements(parsed))
|
177
|
+
end
|
178
|
+
|
179
|
+
def parse_bracket(line)
|
180
|
+
return line if line.parsed?
|
181
|
+
|
182
|
+
parsed = []
|
183
|
+
stack = nil
|
184
|
+
target_char = '[' # or "]"
|
185
|
+
|
186
|
+
line.content.each do |elem|
|
187
|
+
case elem
|
188
|
+
when Cosensee::Code, Cosensee::DoubleBracket
|
189
|
+
if target_char == '['
|
190
|
+
parsed << elem
|
191
|
+
else
|
192
|
+
stack << elem
|
193
|
+
end
|
194
|
+
else # elem is String
|
195
|
+
loop do
|
196
|
+
n = elem.index(target_char)
|
197
|
+
if n
|
198
|
+
if target_char == '['
|
199
|
+
parsed << elem[0, n]
|
200
|
+
target_char = ']'
|
201
|
+
stack = []
|
202
|
+
else
|
203
|
+
stack << elem[0, n]
|
204
|
+
target_char = '['
|
205
|
+
parsed << @bracket_parser.parse(stack)
|
206
|
+
stack = nil
|
207
|
+
end
|
208
|
+
elem = elem[(n + 1)..]
|
209
|
+
else
|
210
|
+
if target_char == '['
|
211
|
+
parsed << elem
|
212
|
+
else
|
213
|
+
stack << elem
|
214
|
+
end
|
215
|
+
break
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
if stack
|
222
|
+
# parsed += ['['] + stack
|
223
|
+
parsed << '['
|
224
|
+
parsed.concat(stack)
|
225
|
+
end
|
226
|
+
|
227
|
+
line.update(content: clean_elements(parsed))
|
228
|
+
end
|
229
|
+
|
230
|
+
def done_parsing(line)
|
231
|
+
# If the line_content is Cosensee::Quote, move content into it.
|
232
|
+
if line.line_content.is_a?(Cosensee::Quote)
|
233
|
+
new_quote = line.line_content.replace_content(line.content)
|
234
|
+
line.update(line_content: new_quote, content: [])
|
235
|
+
end
|
236
|
+
line.update(parsed: true)
|
237
|
+
end
|
238
|
+
|
239
|
+
def clean_elements(elements)
|
240
|
+
fixed = []
|
241
|
+
elements.each do |elem|
|
242
|
+
last_elem = fixed.last
|
243
|
+
if last_elem.is_a?(String) && elem.is_a?(String)
|
244
|
+
fixed[-1] = "#{last_elem}#{elem}"
|
245
|
+
else
|
246
|
+
fixed << elem
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
fixed.filter { |elem| !elem.nil? && elem != '' }
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Cosensee
|
6
|
+
# link node
|
7
|
+
Link = Data.define(:content, :raw) do
|
8
|
+
alias_method :to_s, :raw
|
9
|
+
|
10
|
+
def to_obj
|
11
|
+
content
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_json(*)
|
15
|
+
to_obj.to_json(*)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Cosensee
|
6
|
+
# parser of Bracket
|
7
|
+
module LinkEncodable
|
8
|
+
UNESCAPED_REGEX = /[A-Za-z0-9!"\$&'\(\)\-\~@+;:*<>,._]/
|
9
|
+
|
10
|
+
def make_link(anchor)
|
11
|
+
"#{encode_link(anchor)}.html"
|
12
|
+
end
|
13
|
+
|
14
|
+
def encode_link(str)
|
15
|
+
str.chars.map do |char|
|
16
|
+
if char.match?(UNESCAPED_REGEX)
|
17
|
+
char
|
18
|
+
elsif char == ' '
|
19
|
+
'_'
|
20
|
+
else
|
21
|
+
URI.encode_www_form_component(char)
|
22
|
+
end
|
23
|
+
end.join
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
# require 'uri'
|
5
|
+
|
6
|
+
module Cosensee
|
7
|
+
# for Page
|
8
|
+
Page = Data.define(:id, :title, :created, :updated, :views, :lines) do
|
9
|
+
def self.from_array(args_list)
|
10
|
+
args_list.map { |args| new(**args) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_hash(obj)
|
14
|
+
new(**obj)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(id:, title:, created:, updated:, views:, lines:)
|
18
|
+
@parsed_lines = Cosensee::Line.from_array(lines.drop(1)).map(&:parsed)
|
19
|
+
@linking_page_titles = @parsed_lines.map(&:internal_links).flatten
|
20
|
+
|
21
|
+
super(
|
22
|
+
id:,
|
23
|
+
title:,
|
24
|
+
created: Time.at(created),
|
25
|
+
updated: Time.at(updated),
|
26
|
+
views:,
|
27
|
+
lines:
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :parsed_lines, :linking_page_titles
|
32
|
+
|
33
|
+
def body_lines
|
34
|
+
lines.drop(1)
|
35
|
+
end
|
36
|
+
|
37
|
+
def some_images?
|
38
|
+
lines.any?(&:some_image?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def first_image
|
42
|
+
lines.find(&:some_image?)&.first_image
|
43
|
+
end
|
44
|
+
|
45
|
+
def link_path
|
46
|
+
# body = URI.encode_www_form_component(title.gsub(/ /, '_'))
|
47
|
+
body = title.gsub(/ /, '_').gsub('/', '%2F')
|
48
|
+
|
49
|
+
"#{body}.html"
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_html
|
53
|
+
Cosensee::TailwindRenderer.new(content: self).render
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_obj
|
57
|
+
{ id:, title:, created: created.to_i, updated: updated.to_i, views:, lines: }
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_json(*)
|
61
|
+
to_obj.to_json(*)
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"#<data Cosensee::Page id: #{id}, title: #{title}>"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
# require 'uri'
|
5
|
+
|
6
|
+
module Cosensee
|
7
|
+
# To search for pages
|
8
|
+
class PageStore
|
9
|
+
def initialize(project:)
|
10
|
+
@project = project
|
11
|
+
@pages = project.pages
|
12
|
+
@pages_by_title = nil
|
13
|
+
@linking_pages = nil
|
14
|
+
@linked_pages = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def pages_by_title
|
18
|
+
@pages_by_title ||= create_title_index(@pages)
|
19
|
+
end
|
20
|
+
|
21
|
+
def linking_pages
|
22
|
+
setup_link_indexes unless @linking_pages
|
23
|
+
|
24
|
+
@linking_pages
|
25
|
+
end
|
26
|
+
|
27
|
+
def linked_pages
|
28
|
+
setup_link_indexes unless @linked_pages
|
29
|
+
|
30
|
+
@linked_pages
|
31
|
+
end
|
32
|
+
|
33
|
+
def title_exist?(title)
|
34
|
+
!!pages_by_title[title]
|
35
|
+
end
|
36
|
+
|
37
|
+
def orphan_page_titles
|
38
|
+
linked_pages.keys.reject { |title| title_exist?(title) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_title_index(pages)
|
42
|
+
pages.each_with_object({}) do |page, hash|
|
43
|
+
hash[page.title] = page
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_link_pages_by_title(title)
|
48
|
+
pages = linking_pages[title] + linked_pages[title]
|
49
|
+
pages.sort_by(&:updated)
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_link_indexes
|
53
|
+
@linking_pages = Hash.new { |h, k| h[k] = [] }
|
54
|
+
@linked_pages = Hash.new { |h, k| h[k] = [] }
|
55
|
+
@project.pages.each do |page|
|
56
|
+
page.linking_page_titles.each do |linking_title|
|
57
|
+
@linking_pages[page.title] << pages_by_title[linking_title] if pages_by_title[linking_title]
|
58
|
+
@linked_pages[linking_title] << page
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cosensee
|
4
|
+
# parse a line
|
5
|
+
class ParsedBracket
|
6
|
+
def initialize(content: [])
|
7
|
+
@content = content
|
8
|
+
@parsed = false
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :content
|
12
|
+
|
13
|
+
def parsed?
|
14
|
+
@parsed
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(**attributes)
|
18
|
+
attributes.each do |key, value|
|
19
|
+
# Check if the key is a valid accessor
|
20
|
+
raise ArgumentError, "Attribute #{key} is not allowed to be updated." unless self.class.method_defined?("#{key}=")
|
21
|
+
|
22
|
+
public_send("#{key}=", value)
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def single_text?
|
29
|
+
content.size == 1 && content.first.is_a?(String)
|
30
|
+
end
|
31
|
+
|
32
|
+
def first_content
|
33
|
+
content.first
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
other.is_a?(Cosensee::ParsedBracket) &&
|
38
|
+
other.content == content &&
|
39
|
+
other.parsed? == parsed?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cosensee
|
4
|
+
# parse a line
|
5
|
+
class ParsedLine
|
6
|
+
def initialize(indent: nil, line_content: nil, content: [], rest: nil, parsed: false)
|
7
|
+
@indent = indent
|
8
|
+
@line_content = line_content
|
9
|
+
@content = content
|
10
|
+
@rest = rest
|
11
|
+
@parsed = parsed
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :indent, :line_content, :content, :rest, :parsed
|
15
|
+
|
16
|
+
def codeblock?
|
17
|
+
line_content.is_a?(Cosensee::Codeblock)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parsed?
|
21
|
+
@parsed
|
22
|
+
end
|
23
|
+
|
24
|
+
def rest?
|
25
|
+
!!rest
|
26
|
+
end
|
27
|
+
|
28
|
+
def line_content?
|
29
|
+
!!line_content
|
30
|
+
end
|
31
|
+
|
32
|
+
def indent_level
|
33
|
+
indent.level
|
34
|
+
end
|
35
|
+
|
36
|
+
def match(pattern)
|
37
|
+
rest.match(pattern)
|
38
|
+
end
|
39
|
+
|
40
|
+
def split_rest_by(str)
|
41
|
+
rest.split(str, -1)
|
42
|
+
end
|
43
|
+
|
44
|
+
def update(**attributes)
|
45
|
+
attributes.each do |key, value|
|
46
|
+
# Check if the key is a valid accessor
|
47
|
+
raise ArgumentError, "Attribute #{key} is not allowed to be updated." unless self.class.method_defined?("#{key}=")
|
48
|
+
|
49
|
+
public_send("#{key}=", value)
|
50
|
+
end
|
51
|
+
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def internal_links
|
56
|
+
content.select { |c| c.is_a?(Cosensee::InternalLinkBracket) || c.is_a?(Cosensee::HashTag) }.map(&:anchor)
|
57
|
+
end
|
58
|
+
|
59
|
+
def raw
|
60
|
+
if line_content?
|
61
|
+
"#{indent.raw}#{line_content.raw}"
|
62
|
+
else
|
63
|
+
"#{indent.raw}#{content.map(&:to_s).join}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def ==(other)
|
68
|
+
other.is_a?(Cosensee::ParsedLine) &&
|
69
|
+
other.indent == indent &&
|
70
|
+
other.line_content == line_content &&
|
71
|
+
other.content == content &&
|
72
|
+
other.rest == rest &&
|
73
|
+
other.parsed? == parsed?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Cosensee
|
6
|
+
# for Project
|
7
|
+
class Project
|
8
|
+
def self.parse(source)
|
9
|
+
json = JSON.parse(source, symbolize_names: true)
|
10
|
+
name, display_name, exported, users, pages = json.values_at(:name, :displayName, :exported, :users, :pages)
|
11
|
+
Cosensee::Project.new(name:, display_name:, exported:, users:, pages:, source:)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse_file(filename)
|
15
|
+
src = File.read(filename)
|
16
|
+
parse(src)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(name:, exported:, users:, pages:, source:, **kwargs)
|
20
|
+
@name = name
|
21
|
+
@display_name = if kwargs.keys.size == 1 && kwargs.key?(:display_name)
|
22
|
+
kwargs[:display_name]
|
23
|
+
elsif kwargs.keys.size == 1 && kwargs.key?(:displayName)
|
24
|
+
kwargs[:displayName]
|
25
|
+
else
|
26
|
+
raise Cosensee::Error, 'Cosensee::User.new need an argument :display_name or :displayName'
|
27
|
+
end
|
28
|
+
@users = Cosensee::User.from_array(users)
|
29
|
+
@pages = Cosensee::Page.from_array(pages)
|
30
|
+
@exported = Time.at(exported)
|
31
|
+
@source = source
|
32
|
+
|
33
|
+
@page_store = PageStore.new(project: self)
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :name, :display_name, :users, :pages, :exported, :source, :page_store
|
37
|
+
|
38
|
+
def to_obj
|
39
|
+
{ name:, displayName: display_name, exported: exported.to_i, users:, pages: }
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_json(*)
|
43
|
+
to_obj.to_json(*)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Cosensee
|
6
|
+
# for quote
|
7
|
+
Quote = Data.define(:content, :raw, :mark) do
|
8
|
+
alias_method :to_s, :raw
|
9
|
+
|
10
|
+
def replace_content(new_content)
|
11
|
+
Quote.new(content: new_content, raw:, mark:)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_obj
|
15
|
+
content
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_json(*)
|
19
|
+
to_obj.to_json(*)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cosensee
|
4
|
+
class TailwindRenderer
|
5
|
+
Code = Data.define(:content) do
|
6
|
+
include HtmlEncodable
|
7
|
+
|
8
|
+
def render
|
9
|
+
code = content.content
|
10
|
+
%(<code class="bg-gray-100 text-red-500 px-1 py-0.5 rounded">#{escape_html(code)}</code>)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cosensee
|
4
|
+
class TailwindRenderer
|
5
|
+
# connect parsed lines to a code block
|
6
|
+
class CodeblockBuilder
|
7
|
+
include HtmlEncodable
|
8
|
+
|
9
|
+
def initialize(parsed_line)
|
10
|
+
@first_line = parsed_line
|
11
|
+
@base_indent_level = parsed_line.indent_level
|
12
|
+
@lines = []
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :first_line, :lines, :base_indent_level
|
16
|
+
|
17
|
+
def append(parsed_line)
|
18
|
+
@lines << parsed_line
|
19
|
+
end
|
20
|
+
|
21
|
+
def continued_line?(parsed_line)
|
22
|
+
parsed_line.indent_level >= base_indent_level
|
23
|
+
end
|
24
|
+
|
25
|
+
def render
|
26
|
+
indent_level = base_indent_level * 2
|
27
|
+
title = first_line.line_content.content
|
28
|
+
<<~HTML_BLOCK
|
29
|
+
<div class="relative pl-[#{indent_level}rem]">
|
30
|
+
<div class="bg-orange-300 text-gray-900 px-4 py-1 rounded-t-lg font-mono text-sm">#{title}</div>
|
31
|
+
<div class="px-4 bg-gray-300 text-gray-900 rounded-b-lg shadow-md"><pre class="overflow-x-auto"><code class="block font-mono text-sm leading-relaxed">#{escape_html(lines.map(&:raw).join("\n"))}</code></pre></div>
|
32
|
+
</div>
|
33
|
+
HTML_BLOCK
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Cosensee
|
6
|
+
class TailwindRenderer
|
7
|
+
CommandLine = Data.define(:content) do
|
8
|
+
include HtmlEncodable
|
9
|
+
|
10
|
+
def render
|
11
|
+
code = content.content
|
12
|
+
prompt = content.prompt
|
13
|
+
%(<code class="bg-gray-100 text-gray-800 px-4"><span class="text-red-400">#{prompt}</span>#{escape_html(code)}</code>)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|