cosensee 0.6.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/.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
|