docyard 1.3.0 → 1.4.1
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 +4 -4
- data/CHANGELOG.md +21 -1
- data/lib/docyard/build/sitemap_generator.rb +2 -2
- data/lib/docyard/builder.rb +3 -3
- data/lib/docyard/components/aliases.rb +2 -0
- data/lib/docyard/components/processors/code_block_annotation_preprocessor.rb +149 -0
- data/lib/docyard/components/processors/code_block_processor.rb +21 -11
- data/lib/docyard/components/processors/variables_processor.rb +56 -0
- data/lib/docyard/components/support/code_block/annotation_list_parser.rb +84 -0
- data/lib/docyard/components/support/code_block/line_wrapper.rb +25 -1
- data/lib/docyard/components/support/code_block/patterns.rb +11 -0
- data/lib/docyard/config/schema/definition.rb +2 -1
- data/lib/docyard/config.rb +3 -1
- data/lib/docyard/navigation/sidebar/config_builder.rb +1 -1
- data/lib/docyard/rendering/markdown.rb +2 -0
- data/lib/docyard/templates/assets/css/components/code-annotation.css +265 -0
- data/lib/docyard/templates/assets/css/variables.css +2 -0
- data/lib/docyard/templates/assets/js/components/code-annotation.js +136 -0
- data/lib/docyard/templates/assets/js/hot-reload.js +1 -0
- data/lib/docyard/templates/partials/_code_block.html.erb +1 -1
- data/lib/docyard/version.rb +1 -1
- metadata +34 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c34625ed1c0aee98891a94ac6d144f9660aa8493492e7f2a20dec2a344e38103
|
|
4
|
+
data.tar.gz: 271198813cf0eb7f88154976c927d47ff521ce5ba365cb0c335d6976f18d1eda
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '092ac5fb44e695fc1271eeb96e8f781d3258303431ea57dee56b3f7ceb6a8b83b54ba5e5000c8ea12d5fbc3eb99cb22fed380ca9b14de2b3408abf4645ff77e7'
|
|
7
|
+
data.tar.gz: 68ac38bb44491ebf564a489c3d14a4ba2ea945bd0b62cc2dc9433178f97e5961a01526945087a8b9778f3deaef37e6c7481a4ec903172a741b34f04a96bffe73
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.1] - 2026-11-03
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `robots.txt` now uses an absolute Sitemap URL when `url` is configured (#165, #166)
|
|
14
|
+
- `sitemap.xml` entries now use absolute URLs when `url` is configured, as required by the sitemaps protocol (#169)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Widened dependency ranges to support newer major versions: `rouge` (< 6), `puma` (< 9), and `parallel` (< 3) (#159, #161, #162)
|
|
18
|
+
|
|
19
|
+
## [1.4.0] - 2026-02-22
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **Code Annotations** - Clickable inline markers in code blocks with popover explanations using `// (1)` syntax and a matching ordered list (#156)
|
|
23
|
+
- **Variables** - Template variables with `{{ variable }}` syntax, defined in `docyard.yml` under `variables:` (#155)
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
- Added Code Annotations section to Code Blocks page
|
|
27
|
+
- Added Variables page with usage examples and configuration reference
|
|
28
|
+
|
|
10
29
|
## [1.3.0] - 2026-02-17
|
|
11
30
|
|
|
12
31
|
### Added
|
|
@@ -275,7 +294,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
275
294
|
- Initial gem structure
|
|
276
295
|
- Project scaffolding
|
|
277
296
|
|
|
278
|
-
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.
|
|
297
|
+
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.4.0...HEAD
|
|
298
|
+
[1.4.0]: https://github.com/sanifhimani/docyard/compare/v1.3.0...v1.4.0
|
|
279
299
|
[1.3.0]: https://github.com/sanifhimani/docyard/compare/v1.2.0...v1.3.0
|
|
280
300
|
[1.2.0]: https://github.com/sanifhimani/docyard/compare/v1.1.0...v1.2.0
|
|
281
301
|
[1.1.0]: https://github.com/sanifhimani/docyard/compare/v1.0.2...v1.1.0
|
|
@@ -36,8 +36,8 @@ module Docyard
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def build_sitemap(urls)
|
|
39
|
-
base = config.build.base
|
|
40
|
-
base = base.
|
|
39
|
+
base = config.url || config.build.base
|
|
40
|
+
base = base.chomp("/")
|
|
41
41
|
|
|
42
42
|
xml = ['<?xml version="1.0" encoding="UTF-8"?>']
|
|
43
43
|
xml << '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
|
data/lib/docyard/builder.rb
CHANGED
|
@@ -141,14 +141,14 @@ module Docyard
|
|
|
141
141
|
end
|
|
142
142
|
|
|
143
143
|
def robots_txt_content
|
|
144
|
-
base = config.build.base
|
|
145
|
-
base =
|
|
144
|
+
base = config.url || config.build.base
|
|
145
|
+
base = base.chomp("/")
|
|
146
146
|
|
|
147
147
|
<<~ROBOTS
|
|
148
148
|
User-agent: *
|
|
149
149
|
Allow: /
|
|
150
150
|
|
|
151
|
-
Sitemap: #{base}sitemap.xml
|
|
151
|
+
Sitemap: #{base}/sitemap.xml
|
|
152
152
|
ROBOTS
|
|
153
153
|
end
|
|
154
154
|
end
|
|
@@ -10,6 +10,7 @@ module Docyard
|
|
|
10
10
|
CalloutProcessor = Processors::CalloutProcessor
|
|
11
11
|
CodeBlockProcessor = Processors::CodeBlockProcessor
|
|
12
12
|
CodeGroupProcessor = Processors::CodeGroupProcessor
|
|
13
|
+
CodeBlockAnnotationPreprocessor = Processors::CodeBlockAnnotationPreprocessor
|
|
13
14
|
CodeBlockDiffPreprocessor = Processors::CodeBlockDiffPreprocessor
|
|
14
15
|
CodeBlockFocusPreprocessor = Processors::CodeBlockFocusPreprocessor
|
|
15
16
|
CodeBlockOptionsPreprocessor = Processors::CodeBlockOptionsPreprocessor
|
|
@@ -25,6 +26,7 @@ module Docyard
|
|
|
25
26
|
TableWrapperProcessor = Processors::TableWrapperProcessor
|
|
26
27
|
TabsProcessor = Processors::TabsProcessor
|
|
27
28
|
TooltipProcessor = Processors::TooltipProcessor
|
|
29
|
+
VariablesProcessor = Processors::VariablesProcessor
|
|
28
30
|
|
|
29
31
|
CodeBlockFeatureExtractor = Support::CodeBlock::FeatureExtractor
|
|
30
32
|
CodeBlockLineWrapper = Support::CodeBlock::LineWrapper
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "kramdown"
|
|
4
|
+
require "kramdown-parser-gfm"
|
|
5
|
+
require_relative "../base_processor"
|
|
6
|
+
require_relative "../support/code_block/patterns"
|
|
7
|
+
require_relative "../support/code_block/annotation_list_parser"
|
|
8
|
+
|
|
9
|
+
module Docyard
|
|
10
|
+
module Components
|
|
11
|
+
module Processors
|
|
12
|
+
class CodeBlockAnnotationPreprocessor < BaseProcessor
|
|
13
|
+
include Support::CodeBlock::Patterns
|
|
14
|
+
|
|
15
|
+
self.priority = 8
|
|
16
|
+
|
|
17
|
+
CODE_BLOCK_REGEX = /^```(\w*).*?\n(.*?)^```/m
|
|
18
|
+
TABS_BLOCK_REGEX = /^:::tabs[ \t]*\n.*?^:::[ \t]*$/m
|
|
19
|
+
CODE_GROUP_REGEX = /^:::code-group[ \t]*\n.*?^:::[ \t]*$/m
|
|
20
|
+
|
|
21
|
+
ListParser = Support::CodeBlock::AnnotationListParser
|
|
22
|
+
|
|
23
|
+
def preprocess(content)
|
|
24
|
+
initialize_state
|
|
25
|
+
@skip_ranges = find_skip_ranges(content)
|
|
26
|
+
process_content(content)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def initialize_state
|
|
32
|
+
context[:code_block_annotation_markers] ||= []
|
|
33
|
+
context[:code_block_annotation_content] ||= []
|
|
34
|
+
@block_index = 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def process_content(content)
|
|
38
|
+
result = +""
|
|
39
|
+
last_end = 0
|
|
40
|
+
|
|
41
|
+
content.scan(CODE_BLOCK_REGEX) do
|
|
42
|
+
match = Regexp.last_match
|
|
43
|
+
next if match.begin(0) < last_end
|
|
44
|
+
|
|
45
|
+
result << content[last_end...match.begin(0)]
|
|
46
|
+
processed, new_end = process_match(match, content)
|
|
47
|
+
result << processed
|
|
48
|
+
last_end = new_end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
result << content[last_end..]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def process_match(match, content)
|
|
55
|
+
if inside_skip_range?(match.begin(0))
|
|
56
|
+
[match[0], match.end(0)]
|
|
57
|
+
else
|
|
58
|
+
process_annotated_block(match, content)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def process_annotated_block(match, content)
|
|
63
|
+
markers = extract_annotation_markers(match[2])
|
|
64
|
+
code_end = match.end(0)
|
|
65
|
+
|
|
66
|
+
if markers.any?
|
|
67
|
+
process_with_list(match, content, markers, code_end)
|
|
68
|
+
else
|
|
69
|
+
store_empty_markers
|
|
70
|
+
@block_index += 1
|
|
71
|
+
[match[0], code_end]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def process_with_list(match, content, markers, code_end)
|
|
76
|
+
list_result = ListParser.find_after_code_block(content, code_end)
|
|
77
|
+
|
|
78
|
+
if list_result
|
|
79
|
+
store_markers_and_content(markers, list_result[:items])
|
|
80
|
+
cleaned_code = strip_annotation_markers(match[2])
|
|
81
|
+
@block_index += 1
|
|
82
|
+
["#{match[0].sub(match[2], cleaned_code)}\n", list_result[:end_position]]
|
|
83
|
+
else
|
|
84
|
+
store_empty_markers
|
|
85
|
+
@block_index += 1
|
|
86
|
+
[match[0], code_end]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def store_markers_and_content(markers, list_items)
|
|
91
|
+
context[:code_block_annotation_markers][@block_index] = markers
|
|
92
|
+
context[:code_block_annotation_content][@block_index] = render_annotation_content(list_items)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def store_empty_markers
|
|
96
|
+
context[:code_block_annotation_markers][@block_index] = {}
|
|
97
|
+
context[:code_block_annotation_content][@block_index] = {}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def extract_annotation_markers(code_content)
|
|
101
|
+
markers = {}
|
|
102
|
+
code_content.lines.each_with_index do |line, index|
|
|
103
|
+
next unless (match = line.match(ANNOTATION_MARKER_PATTERN))
|
|
104
|
+
|
|
105
|
+
num = match.captures.compact.first.to_i
|
|
106
|
+
markers[index + 1] = num
|
|
107
|
+
end
|
|
108
|
+
markers
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def strip_annotation_markers(code_content)
|
|
112
|
+
code_content.lines.map { |line| strip_single_marker(line) }.join
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def strip_single_marker(line)
|
|
116
|
+
return line unless line.match?(ANNOTATION_MARKER_PATTERN)
|
|
117
|
+
|
|
118
|
+
stripped = line.sub(ANNOTATION_MARKER_PATTERN, "")
|
|
119
|
+
stripped.end_with?("\n") ? stripped : "#{stripped}\n"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def render_annotation_content(list_items)
|
|
123
|
+
list_items.transform_values { |markdown_text| render_markdown(markdown_text) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def render_markdown(text)
|
|
127
|
+
Kramdown::Document.new(text, input: "GFM", hard_wrap: false).to_html.strip
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def inside_skip_range?(position)
|
|
131
|
+
@skip_ranges.any? { |range| range.cover?(position) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def find_skip_ranges(content)
|
|
135
|
+
find_ranges(content, TABS_BLOCK_REGEX) + find_ranges(content, CODE_GROUP_REGEX)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def find_ranges(content, pattern)
|
|
139
|
+
ranges = []
|
|
140
|
+
content.scan(pattern) do
|
|
141
|
+
match = Regexp.last_match
|
|
142
|
+
ranges << (match.begin(0)...match.end(0))
|
|
143
|
+
end
|
|
144
|
+
ranges
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -33,11 +33,17 @@ module Docyard
|
|
|
33
33
|
def initialize_postprocess_state(html)
|
|
34
34
|
@block_index = 0
|
|
35
35
|
@options = context[:code_block_options] || []
|
|
36
|
+
initialize_line_feature_state
|
|
37
|
+
@tabs_ranges = TabsRangeFinder.find_ranges(html)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize_line_feature_state
|
|
36
41
|
@diff_lines = context[:code_block_diff_lines] || []
|
|
37
42
|
@focus_lines = context[:code_block_focus_lines] || []
|
|
38
43
|
@error_lines = context[:code_block_error_lines] || []
|
|
39
44
|
@warning_lines = context[:code_block_warning_lines] || []
|
|
40
|
-
@
|
|
45
|
+
@annotation_markers = context[:code_block_annotation_markers] || []
|
|
46
|
+
@annotation_content = context[:code_block_annotation_content] || []
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
def process_all_highlight_blocks(html)
|
|
@@ -99,25 +105,28 @@ module Docyard
|
|
|
99
105
|
|
|
100
106
|
def build_block_data(code_text, opts, show_line_numbers, start_line, title_data)
|
|
101
107
|
{
|
|
102
|
-
text: code_text,
|
|
103
|
-
|
|
108
|
+
text: code_text, highlights: opts[:highlights],
|
|
109
|
+
show_line_numbers: show_line_numbers, start_line: start_line,
|
|
110
|
+
line_numbers: show_line_numbers ? LineNumbers.generate_numbers(code_text, start_line) : [],
|
|
111
|
+
title: title_data[:title], icon: title_data[:icon], icon_source: title_data[:icon_source]
|
|
112
|
+
}.merge(current_line_features)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def current_line_features
|
|
116
|
+
{
|
|
104
117
|
diff_lines: @diff_lines[@block_index] || {},
|
|
105
118
|
focus_lines: @focus_lines[@block_index] || {},
|
|
106
119
|
error_lines: @error_lines[@block_index] || {},
|
|
107
120
|
warning_lines: @warning_lines[@block_index] || {},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
start_line: start_line,
|
|
111
|
-
title: title_data[:title],
|
|
112
|
-
icon: title_data[:icon],
|
|
113
|
-
icon_source: title_data[:icon_source]
|
|
121
|
+
annotation_markers: @annotation_markers[@block_index] || {},
|
|
122
|
+
annotation_content: @annotation_content[@block_index] || {}
|
|
114
123
|
}
|
|
115
124
|
end
|
|
116
125
|
|
|
117
126
|
def process_html_for_highlighting(original_html, block_data)
|
|
118
127
|
needs_wrapping = block_data[:highlights].any? || block_data[:diff_lines].any? ||
|
|
119
128
|
block_data[:focus_lines].any? || block_data[:error_lines].any? ||
|
|
120
|
-
block_data[:warning_lines].any?
|
|
129
|
+
block_data[:warning_lines].any? || block_data[:annotation_markers].any?
|
|
121
130
|
return original_html unless needs_wrapping
|
|
122
131
|
|
|
123
132
|
wrap_code_block(original_html, block_data)
|
|
@@ -150,7 +159,8 @@ module Docyard
|
|
|
150
159
|
def line_feature_locals(block_data)
|
|
151
160
|
{ highlights: block_data[:highlights], diff_lines: block_data[:diff_lines],
|
|
152
161
|
focus_lines: block_data[:focus_lines], error_lines: block_data[:error_lines],
|
|
153
|
-
warning_lines: block_data[:warning_lines]
|
|
162
|
+
warning_lines: block_data[:warning_lines], annotation_markers: block_data[:annotation_markers],
|
|
163
|
+
annotation_content: block_data[:annotation_content] }
|
|
154
164
|
end
|
|
155
165
|
|
|
156
166
|
def title_locals(block_data)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_processor"
|
|
4
|
+
require_relative "../support/markdown_code_block_helper"
|
|
5
|
+
|
|
6
|
+
module Docyard
|
|
7
|
+
module Components
|
|
8
|
+
module Processors
|
|
9
|
+
class VariablesProcessor < BaseProcessor
|
|
10
|
+
include Support::MarkdownCodeBlockHelper
|
|
11
|
+
|
|
12
|
+
VARIABLE_PATTERN = /\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/
|
|
13
|
+
VARS_SUFFIX_PATTERN = /^(`{3,}|~{3,})(\S+)-vars(.*)/
|
|
14
|
+
|
|
15
|
+
self.priority = 1
|
|
16
|
+
|
|
17
|
+
def preprocess(content)
|
|
18
|
+
variables = context.dig(:config, "variables") || {}
|
|
19
|
+
return content if variables.empty?
|
|
20
|
+
|
|
21
|
+
segments = split_by_code_blocks(content)
|
|
22
|
+
segments.map { |segment| process_segment(segment, variables) }.join
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def process_segment(segment, variables)
|
|
28
|
+
return substitute_variables(segment[:content], variables) if segment[:type] == :text
|
|
29
|
+
|
|
30
|
+
match = segment[:content].match(VARS_SUFFIX_PATTERN)
|
|
31
|
+
return segment[:content] unless match
|
|
32
|
+
|
|
33
|
+
stripped = segment[:content].sub("#{match[2]}-vars", match[2])
|
|
34
|
+
substitute_variables(stripped, variables)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def substitute_variables(content, variables)
|
|
38
|
+
content.gsub(VARIABLE_PATTERN) do |original|
|
|
39
|
+
key = Regexp.last_match(1)
|
|
40
|
+
value = resolve_variable(key, variables)
|
|
41
|
+
value.nil? ? original : value.to_s
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def resolve_variable(key, variables)
|
|
46
|
+
keys = key.split(".")
|
|
47
|
+
keys.reduce(variables) do |current, k|
|
|
48
|
+
return nil unless current.is_a?(Hash)
|
|
49
|
+
|
|
50
|
+
current[k]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Components
|
|
5
|
+
module Support
|
|
6
|
+
module CodeBlock
|
|
7
|
+
module AnnotationListParser
|
|
8
|
+
ORDERED_LIST_ITEM = /\A(\d+)\.\s+(.*)/
|
|
9
|
+
CONTINUATION_LINE = /\A\s{2,}(\S.*)/
|
|
10
|
+
BLANK_LINE = /\A\s*\z/
|
|
11
|
+
LIST_START = /\A(\s*\n)*(\d+\.\s+)/m
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def parse(text)
|
|
16
|
+
state = { items: {}, current_num: nil, current_lines: [] }
|
|
17
|
+
catch(:done) { text.each_line { |line| consume_line(state, line) } }
|
|
18
|
+
finalize(state)
|
|
19
|
+
state[:items]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def find_after_code_block(content, position)
|
|
23
|
+
rest = content[position..]
|
|
24
|
+
return nil unless rest
|
|
25
|
+
|
|
26
|
+
preamble = rest.match(LIST_START)
|
|
27
|
+
return nil unless preamble
|
|
28
|
+
|
|
29
|
+
list_start = preamble.begin(2)
|
|
30
|
+
list_text = rest[list_start..]
|
|
31
|
+
parsed = parse_with_extent(list_text)
|
|
32
|
+
return nil if parsed[:items].empty?
|
|
33
|
+
|
|
34
|
+
{ text: parsed[:consumed], end_position: position + list_start + parsed[:length], items: parsed[:items] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse_with_extent(text)
|
|
38
|
+
state = { items: {}, current_num: nil, current_lines: [] }
|
|
39
|
+
consumed_length = 0
|
|
40
|
+
|
|
41
|
+
catch(:done) do
|
|
42
|
+
text.each_line do |line|
|
|
43
|
+
consume_line(state, line)
|
|
44
|
+
consumed_length += line.length
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
finalize(state)
|
|
49
|
+
{ items: state[:items], consumed: text[0...consumed_length], length: consumed_length }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def consume_line(state, line)
|
|
53
|
+
if (match = line.match(ORDERED_LIST_ITEM))
|
|
54
|
+
start_new_item(state, match)
|
|
55
|
+
elsif state[:current_num] && (cont = line.match(CONTINUATION_LINE))
|
|
56
|
+
state[:current_lines] << cont[1].rstrip
|
|
57
|
+
elsif state[:current_num] && line.match?(BLANK_LINE)
|
|
58
|
+
state[:current_lines] << ""
|
|
59
|
+
else
|
|
60
|
+
finalize(state)
|
|
61
|
+
throw :done
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def start_new_item(state, match)
|
|
66
|
+
finalize(state)
|
|
67
|
+
state[:current_num] = match[1].to_i
|
|
68
|
+
state[:current_lines] = [match[2].rstrip]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def finalize(state)
|
|
72
|
+
return unless state[:current_num]
|
|
73
|
+
|
|
74
|
+
state[:items][state[:current_num]] = state[:current_lines].join("\n").strip
|
|
75
|
+
state[:current_num] = nil
|
|
76
|
+
state[:current_lines] = []
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private_class_method :consume_line, :start_new_item, :finalize, :parse_with_extent
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -24,10 +24,18 @@ module Docyard
|
|
|
24
24
|
source_line = index + 1
|
|
25
25
|
display_line = block_data[:start_line] + index
|
|
26
26
|
classes = build_line_classes(source_line, display_line, block_data)
|
|
27
|
-
|
|
27
|
+
annotation = annotation_badge(source_line, block_data)
|
|
28
|
+
line_with_badge = insert_before_newline(line, annotation)
|
|
29
|
+
%(<span class="#{classes}">#{line_with_badge}</span>)
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
|
|
33
|
+
def insert_before_newline(line, annotation)
|
|
34
|
+
return line if annotation.empty?
|
|
35
|
+
|
|
36
|
+
line.end_with?("\n") ? "#{line.chomp}#{annotation}\n" : "#{line}#{annotation}"
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
DIFF_CLASSES = { addition: "docyard-code-line--diff-add", deletion: "docyard-code-line--diff-remove" }.freeze
|
|
32
40
|
|
|
33
41
|
def build_line_classes(source_line, display_line, block_data)
|
|
@@ -43,6 +51,22 @@ module Docyard
|
|
|
43
51
|
("docyard-code-line--warning" if block_data[:warning_lines][source_line])
|
|
44
52
|
].compact
|
|
45
53
|
end
|
|
54
|
+
|
|
55
|
+
ANNOTATION_ICON = "<i class=\"ph ph-plus-circle\" aria-hidden=\"true\"></i>"
|
|
56
|
+
|
|
57
|
+
def annotation_badge(source_line, block_data)
|
|
58
|
+
markers = block_data[:annotation_markers] || {}
|
|
59
|
+
num = markers[source_line]
|
|
60
|
+
return "" unless num
|
|
61
|
+
|
|
62
|
+
content = (block_data[:annotation_content] || {})[num]
|
|
63
|
+
return "" unless content
|
|
64
|
+
|
|
65
|
+
escaped = content.gsub("&", "&").gsub("<", "<").gsub(">", ">").gsub('"', """)
|
|
66
|
+
%(<button class="docyard-code-annotation" data-annotation-id="#{num}") +
|
|
67
|
+
%( data-annotation-content="#{escaped}" type="button" aria-label="Annotation #{num}">) +
|
|
68
|
+
%(#{ANNOTATION_ICON}</button>)
|
|
69
|
+
end
|
|
46
70
|
end
|
|
47
71
|
end
|
|
48
72
|
end
|
|
@@ -48,6 +48,17 @@ module Docyard
|
|
|
48
48
|
;\s*\[!code\s+warning\]
|
|
49
49
|
)[^\S\n]*
|
|
50
50
|
}x
|
|
51
|
+
|
|
52
|
+
ANNOTATION_MARKER_PATTERN = %r{
|
|
53
|
+
(?:
|
|
54
|
+
//\s*\((\d+)\)\s*$ |
|
|
55
|
+
\#\s*\((\d+)\)\s*$ |
|
|
56
|
+
/\*\s*\((\d+)\)\s*\*/\s*$ |
|
|
57
|
+
--\s*\((\d+)\)\s*$ |
|
|
58
|
+
<!--\s*\((\d+)\)\s*-->\s*$ |
|
|
59
|
+
;\s*\((\d+)\)\s*$
|
|
60
|
+
)
|
|
61
|
+
}x
|
|
51
62
|
end
|
|
52
63
|
end
|
|
53
64
|
end
|
|
@@ -23,7 +23,8 @@ module Docyard
|
|
|
23
23
|
repo: REPO_SCHEMA,
|
|
24
24
|
analytics: ANALYTICS_SCHEMA,
|
|
25
25
|
feedback: FEEDBACK_SCHEMA,
|
|
26
|
-
social_cards: SOCIAL_CARDS_SCHEMA
|
|
26
|
+
social_cards: SOCIAL_CARDS_SCHEMA,
|
|
27
|
+
variables: { type: :hash, allow_extra_keys: true, keys: {} }
|
|
27
28
|
}.freeze
|
|
28
29
|
end
|
|
29
30
|
end
|
data/lib/docyard/config.rb
CHANGED
|
@@ -27,7 +27,8 @@ module Docyard
|
|
|
27
27
|
"last_updated" => true },
|
|
28
28
|
"analytics" => { "google" => nil, "plausible" => nil, "fathom" => nil, "script" => nil },
|
|
29
29
|
"feedback" => { "enabled" => false, "question" => "Was this page helpful?" },
|
|
30
|
-
"social_cards" => { "enabled" => false }
|
|
30
|
+
"social_cards" => { "enabled" => false },
|
|
31
|
+
"variables" => {}
|
|
31
32
|
}.freeze
|
|
32
33
|
|
|
33
34
|
attr_reader :data, :file_path
|
|
@@ -65,6 +66,7 @@ module Docyard
|
|
|
65
66
|
def analytics = @analytics ||= Section.new(data["analytics"])
|
|
66
67
|
def feedback = @feedback ||= Section.new(data["feedback"])
|
|
67
68
|
def social_cards = @social_cards ||= Section.new(data["social_cards"])
|
|
69
|
+
def variables = data["variables"]
|
|
68
70
|
|
|
69
71
|
def announcement
|
|
70
72
|
@announcement ||= data["announcement"] ? Section.new(data["announcement"]) : nil
|
|
@@ -170,7 +170,7 @@ module Docyard
|
|
|
170
170
|
|
|
171
171
|
def normalize_options(options)
|
|
172
172
|
return {} if options.nil?
|
|
173
|
-
return options if options.is_a?(Hash) && options.keys.all?
|
|
173
|
+
return options if options.is_a?(Hash) && options.keys.all?(Symbol)
|
|
174
174
|
|
|
175
175
|
options.transform_keys(&:to_sym)
|
|
176
176
|
end
|
|
@@ -19,6 +19,7 @@ require_relative "../components/processors/include_processor"
|
|
|
19
19
|
require_relative "../components/processors/code_block_options_preprocessor"
|
|
20
20
|
require_relative "../components/processors/code_block_diff_preprocessor"
|
|
21
21
|
require_relative "../components/processors/code_block_focus_preprocessor"
|
|
22
|
+
require_relative "../components/processors/code_block_annotation_preprocessor"
|
|
22
23
|
require_relative "../components/processors/code_block_extended_fence_preprocessor"
|
|
23
24
|
require_relative "../components/processors/code_block_extended_fence_postprocessor"
|
|
24
25
|
require_relative "../components/processors/table_wrapper_processor"
|
|
@@ -29,6 +30,7 @@ require_relative "../components/processors/video_embed_processor"
|
|
|
29
30
|
require_relative "../components/processors/file_tree_processor"
|
|
30
31
|
require_relative "../components/processors/abbreviation_processor"
|
|
31
32
|
require_relative "../components/processors/tooltip_processor"
|
|
33
|
+
require_relative "../components/processors/variables_processor"
|
|
32
34
|
require_relative "../components/processors/table_of_contents_processor"
|
|
33
35
|
require_relative "../components/aliases"
|
|
34
36
|
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
.docyard-code-annotation {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
width: var(--code-annotation-size);
|
|
7
|
+
height: var(--code-annotation-size);
|
|
8
|
+
margin-left: var(--spacing-1);
|
|
9
|
+
padding: 0;
|
|
10
|
+
border: none;
|
|
11
|
+
border-radius: var(--radius-4xl);
|
|
12
|
+
background: none;
|
|
13
|
+
color: var(--primary);
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
vertical-align: middle;
|
|
16
|
+
opacity: 0.7;
|
|
17
|
+
transition: opacity 150ms ease, transform 150ms ease;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.docyard-code-annotation::before {
|
|
21
|
+
content: "";
|
|
22
|
+
position: absolute;
|
|
23
|
+
inset: -0.375em;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.docyard-code-annotation i[class*="ph-"] {
|
|
27
|
+
font-size: 1.25em;
|
|
28
|
+
vertical-align: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@media (hover: hover) {
|
|
32
|
+
.docyard-code-annotation:hover {
|
|
33
|
+
opacity: 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.docyard-code-annotation:active {
|
|
38
|
+
transform: scale(0.92);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.docyard-code-annotation.is-active {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.docyard-code-annotation:focus-visible {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
outline: 1.5px solid var(--primary);
|
|
48
|
+
outline-offset: 1px;
|
|
49
|
+
border-radius: 2px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.docyard-code-block--has-annotations .docyard-code-line {
|
|
53
|
+
padding-right: calc(var(--code-annotation-size) + var(--spacing-1));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.docyard-code-annotation-popover {
|
|
57
|
+
position: absolute;
|
|
58
|
+
z-index: var(--z-tooltip);
|
|
59
|
+
width: max-content;
|
|
60
|
+
max-width: 24rem;
|
|
61
|
+
max-height: 20rem;
|
|
62
|
+
overflow-y: auto;
|
|
63
|
+
padding: var(--spacing-3) var(--spacing-4);
|
|
64
|
+
background: var(--popover);
|
|
65
|
+
color: oklch(from var(--popover-foreground) l c h / 85%);
|
|
66
|
+
border: 1px solid var(--border);
|
|
67
|
+
border-radius: var(--radius-lg);
|
|
68
|
+
box-shadow:
|
|
69
|
+
0 1px 2px oklch(from var(--foreground) l c h / 3%),
|
|
70
|
+
0 4px 12px oklch(from var(--foreground) l c h / 6%),
|
|
71
|
+
0 16px 32px -8px oklch(from var(--foreground) l c h / 8%);
|
|
72
|
+
pointer-events: none;
|
|
73
|
+
opacity: 0;
|
|
74
|
+
transform: translateY(4px) scale(0.96);
|
|
75
|
+
transform-origin: var(--arrow-left, 16px) top;
|
|
76
|
+
transition:
|
|
77
|
+
opacity 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
78
|
+
transform 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.docyard-code-annotation-popover.is-visible {
|
|
82
|
+
opacity: 1;
|
|
83
|
+
transform: translateY(0) scale(1);
|
|
84
|
+
pointer-events: auto;
|
|
85
|
+
transition:
|
|
86
|
+
opacity 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
|
87
|
+
transform 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.docyard-code-annotation-popover.is-above {
|
|
91
|
+
transform: translateY(-4px) scale(0.96);
|
|
92
|
+
transform-origin: var(--arrow-left, 16px) bottom;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.docyard-code-annotation-popover.is-above.is-visible {
|
|
96
|
+
transform: translateY(0) scale(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.docyard-code-annotation-popover::after {
|
|
100
|
+
content: "";
|
|
101
|
+
position: absolute;
|
|
102
|
+
top: -5px;
|
|
103
|
+
left: var(--arrow-left, 16px);
|
|
104
|
+
width: 9px;
|
|
105
|
+
height: 9px;
|
|
106
|
+
background: var(--popover);
|
|
107
|
+
border-top: 1px solid var(--border);
|
|
108
|
+
border-left: 1px solid var(--border);
|
|
109
|
+
border-radius: 1px 0 0 0;
|
|
110
|
+
transform: rotate(45deg);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.docyard-code-annotation-popover.is-above::after {
|
|
114
|
+
top: auto;
|
|
115
|
+
bottom: -5px;
|
|
116
|
+
border-top: none;
|
|
117
|
+
border-left: none;
|
|
118
|
+
border-bottom: 1px solid var(--border);
|
|
119
|
+
border-right: 1px solid var(--border);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.docyard-code-annotation-popover p {
|
|
123
|
+
margin: 0;
|
|
124
|
+
font-size: var(--text-sm);
|
|
125
|
+
line-height: var(--leading-relaxed);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.docyard-code-annotation-popover * + p,
|
|
129
|
+
.docyard-code-annotation-popover * + ul,
|
|
130
|
+
.docyard-code-annotation-popover * + ol,
|
|
131
|
+
.docyard-code-annotation-popover * + blockquote {
|
|
132
|
+
margin-top: var(--spacing-2);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.docyard-code-annotation-popover * + .table-wrapper,
|
|
136
|
+
.docyard-code-annotation-popover * + table {
|
|
137
|
+
margin-top: var(--spacing-2);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.docyard-code-annotation-popover ul,
|
|
141
|
+
.docyard-code-annotation-popover ol {
|
|
142
|
+
margin: 0;
|
|
143
|
+
padding-left: var(--spacing-6);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.docyard-code-annotation-popover ul {
|
|
147
|
+
list-style-type: disc;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.docyard-code-annotation-popover ol {
|
|
151
|
+
list-style-type: decimal;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.docyard-code-annotation-popover li {
|
|
155
|
+
margin: var(--spacing-1) 0;
|
|
156
|
+
padding-left: var(--spacing-1);
|
|
157
|
+
font-size: var(--text-sm);
|
|
158
|
+
line-height: var(--leading-relaxed);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.docyard-code-annotation-popover li::marker {
|
|
162
|
+
color: var(--muted-foreground);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.docyard-code-annotation-popover table {
|
|
166
|
+
width: 100%;
|
|
167
|
+
border-spacing: 0;
|
|
168
|
+
border-collapse: separate;
|
|
169
|
+
font-size: var(--text-sm);
|
|
170
|
+
margin: 0;
|
|
171
|
+
border: 1px solid var(--table-border);
|
|
172
|
+
border-radius: var(--radius-lg);
|
|
173
|
+
overflow: hidden;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.docyard-code-annotation-popover thead {
|
|
177
|
+
background-color: var(--table-header-bg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.docyard-code-annotation-popover th {
|
|
181
|
+
padding: var(--spacing-2) var(--spacing-3);
|
|
182
|
+
text-align: left;
|
|
183
|
+
font-weight: var(--font-semibold);
|
|
184
|
+
font-size: var(--text-xs);
|
|
185
|
+
color: var(--popover-foreground);
|
|
186
|
+
vertical-align: middle;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.docyard-code-annotation-popover td {
|
|
190
|
+
padding: var(--spacing-2) var(--spacing-3);
|
|
191
|
+
font-size: var(--text-xs);
|
|
192
|
+
vertical-align: middle;
|
|
193
|
+
border-top: 1px solid var(--table-row-border);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.docyard-code-annotation-popover blockquote {
|
|
197
|
+
margin: 0;
|
|
198
|
+
padding-left: var(--spacing-6);
|
|
199
|
+
border-left: 4px solid var(--border);
|
|
200
|
+
color: var(--muted-foreground);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.docyard-code-annotation-popover blockquote p {
|
|
204
|
+
margin: var(--spacing-1) 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.docyard-code-annotation-popover code {
|
|
208
|
+
padding: 0.125rem 0.5rem;
|
|
209
|
+
background-color: oklch(from var(--muted) l c h / 80%);
|
|
210
|
+
color: var(--popover-foreground);
|
|
211
|
+
border-radius: var(--radius-sm);
|
|
212
|
+
font-size: 0.875em;
|
|
213
|
+
font-weight: var(--font-medium);
|
|
214
|
+
font-variant-ligatures: none;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.docyard-code-annotation-popover strong {
|
|
218
|
+
font-weight: var(--font-semibold);
|
|
219
|
+
color: var(--popover-foreground);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.docyard-code-annotation-popover a {
|
|
223
|
+
color: var(--popover-foreground);
|
|
224
|
+
font-weight: var(--font-semibold);
|
|
225
|
+
text-decoration: none;
|
|
226
|
+
border-bottom: 1px solid oklch(from var(--primary) l c h / 40%);
|
|
227
|
+
transition: border-bottom var(--transition-fast);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.docyard-code-annotation-popover a:hover {
|
|
231
|
+
border-bottom: 2px solid var(--primary);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@media (max-width: 640px) {
|
|
235
|
+
.docyard-code-annotation-popover {
|
|
236
|
+
max-width: 16rem;
|
|
237
|
+
padding: var(--spacing-2-5) var(--spacing-3);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.docyard-code-annotation-popover p,
|
|
241
|
+
.docyard-code-annotation-popover li {
|
|
242
|
+
font-size: var(--text-xs);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@media (prefers-reduced-motion: reduce) {
|
|
247
|
+
.docyard-code-annotation,
|
|
248
|
+
.docyard-code-annotation-popover {
|
|
249
|
+
transition: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.docyard-code-annotation:active {
|
|
253
|
+
transform: none;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.docyard-code-annotation-popover,
|
|
257
|
+
.docyard-code-annotation-popover.is-above {
|
|
258
|
+
transform: none;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.docyard-code-annotation-popover.is-visible,
|
|
262
|
+
.docyard-code-annotation-popover.is-above.is-visible {
|
|
263
|
+
transform: none;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -84,6 +84,8 @@
|
|
|
84
84
|
--callout-danger-background: rgba(254, 242, 242, 0.5);
|
|
85
85
|
--callout-danger-border: rgba(239, 68, 68, 0.2);
|
|
86
86
|
|
|
87
|
+
--code-annotation-size: 1.25em;
|
|
88
|
+
|
|
87
89
|
--code-block-bg: oklab(0 0 0 / 0.03);
|
|
88
90
|
--code-block-border: oklab(0 0 0 / 0.06);
|
|
89
91
|
--code-block-header-bg: oklab(0 0 0 / 0.02);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
var annotationPopover = null;
|
|
2
|
+
var activeAnnotationButton = null;
|
|
3
|
+
|
|
4
|
+
function initCodeAnnotations(root) {
|
|
5
|
+
if (typeof root === 'undefined') root = document;
|
|
6
|
+
var buttons = root.querySelectorAll('.docyard-code-annotation');
|
|
7
|
+
if (buttons.length === 0) return;
|
|
8
|
+
|
|
9
|
+
if (!annotationPopover) {
|
|
10
|
+
annotationPopover = document.createElement('div');
|
|
11
|
+
annotationPopover.className = 'docyard-code-annotation-popover';
|
|
12
|
+
document.body.appendChild(annotationPopover);
|
|
13
|
+
|
|
14
|
+
document.addEventListener('click', function(e) {
|
|
15
|
+
if (activeAnnotationButton &&
|
|
16
|
+
!annotationPopover.contains(e.target) &&
|
|
17
|
+
!e.target.closest('.docyard-code-annotation')) {
|
|
18
|
+
hideAnnotationPopover();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
document.addEventListener('keydown', function(e) {
|
|
23
|
+
if (e.key === 'Escape' && activeAnnotationButton) {
|
|
24
|
+
hideAnnotationPopover();
|
|
25
|
+
activeAnnotationButton.focus();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
buttons.forEach(function(button) {
|
|
31
|
+
if (button.hasAttribute('data-annotation-initialized')) return;
|
|
32
|
+
button.setAttribute('data-annotation-initialized', 'true');
|
|
33
|
+
|
|
34
|
+
button.addEventListener('click', function(e) {
|
|
35
|
+
e.stopPropagation();
|
|
36
|
+
if (activeAnnotationButton === button) {
|
|
37
|
+
hideAnnotationPopover();
|
|
38
|
+
} else {
|
|
39
|
+
showAnnotationPopover(button);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setAnnotationIcon(button, name, animate) {
|
|
46
|
+
var icon = button.querySelector('i[class*="ph-"]');
|
|
47
|
+
if (!icon) return;
|
|
48
|
+
if (!animate) {
|
|
49
|
+
icon.className = 'ph ph-' + name;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
icon.style.transition = 'opacity 100ms ease, transform 100ms ease';
|
|
53
|
+
icon.style.opacity = '0';
|
|
54
|
+
icon.style.transform = 'scale(0.5)';
|
|
55
|
+
setTimeout(function() {
|
|
56
|
+
icon.className = 'ph ph-' + name;
|
|
57
|
+
icon.style.opacity = '1';
|
|
58
|
+
icon.style.transform = 'scale(1)';
|
|
59
|
+
}, 100);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function showAnnotationPopover(button) {
|
|
63
|
+
if (activeAnnotationButton) {
|
|
64
|
+
setAnnotationIcon(activeAnnotationButton, 'plus-circle', true);
|
|
65
|
+
activeAnnotationButton.classList.remove('is-active');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
activeAnnotationButton = button;
|
|
69
|
+
button.classList.add('is-active');
|
|
70
|
+
setAnnotationIcon(button, 'x-circle', true);
|
|
71
|
+
annotationPopover.innerHTML = button.getAttribute('data-annotation-content');
|
|
72
|
+
|
|
73
|
+
annotationPopover.classList.remove('is-above');
|
|
74
|
+
annotationPopover.style.visibility = 'hidden';
|
|
75
|
+
annotationPopover.classList.add('is-visible');
|
|
76
|
+
|
|
77
|
+
requestAnimationFrame(function() {
|
|
78
|
+
positionPopover(button);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function positionPopover(button) {
|
|
83
|
+
var rect = button.getBoundingClientRect();
|
|
84
|
+
var scrollX = window.scrollX;
|
|
85
|
+
var scrollY = window.scrollY;
|
|
86
|
+
var popoverRect = annotationPopover.getBoundingClientRect();
|
|
87
|
+
var gap = 6;
|
|
88
|
+
var padding = 12;
|
|
89
|
+
var viewportWidth = window.innerWidth;
|
|
90
|
+
var viewportHeight = window.innerHeight;
|
|
91
|
+
|
|
92
|
+
var left = rect.left + scrollX + (rect.width / 2) - (popoverRect.width / 2);
|
|
93
|
+
var spaceBelow = viewportHeight - rect.bottom;
|
|
94
|
+
var spaceAbove = rect.top;
|
|
95
|
+
var openAbove = spaceBelow < popoverRect.height + gap + padding && spaceAbove > spaceBelow;
|
|
96
|
+
|
|
97
|
+
var top;
|
|
98
|
+
if (openAbove) {
|
|
99
|
+
top = rect.top + scrollY - popoverRect.height - gap;
|
|
100
|
+
annotationPopover.classList.add('is-above');
|
|
101
|
+
} else {
|
|
102
|
+
top = rect.bottom + scrollY + gap;
|
|
103
|
+
annotationPopover.classList.remove('is-above');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (left < padding) {
|
|
107
|
+
left = padding;
|
|
108
|
+
} else if (left + popoverRect.width > viewportWidth - padding) {
|
|
109
|
+
left = viewportWidth - popoverRect.width - padding;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
var arrowLeft = rect.left + scrollX + (rect.width / 2) - left;
|
|
113
|
+
annotationPopover.style.setProperty('--arrow-left', Math.max(12, Math.min(arrowLeft, popoverRect.width - 12)) + 'px');
|
|
114
|
+
annotationPopover.style.left = left + 'px';
|
|
115
|
+
annotationPopover.style.top = top + 'px';
|
|
116
|
+
annotationPopover.style.visibility = 'visible';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function hideAnnotationPopover() {
|
|
120
|
+
if (!annotationPopover) return;
|
|
121
|
+
if (activeAnnotationButton) {
|
|
122
|
+
setAnnotationIcon(activeAnnotationButton, 'plus-circle', true);
|
|
123
|
+
activeAnnotationButton.classList.remove('is-active');
|
|
124
|
+
}
|
|
125
|
+
annotationPopover.classList.remove('is-visible');
|
|
126
|
+
activeAnnotationButton = null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (document.readyState === 'loading') {
|
|
130
|
+
document.addEventListener('DOMContentLoaded', function() { initCodeAnnotations(); });
|
|
131
|
+
} else {
|
|
132
|
+
initCodeAnnotations();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
window.docyard = window.docyard || {};
|
|
136
|
+
window.docyard.initCodeAnnotations = initCodeAnnotations;
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
if (docyard.initAbbreviations) docyard.initAbbreviations(container);
|
|
60
60
|
if (docyard.initLightbox) docyard.initLightbox(container);
|
|
61
61
|
if (docyard.initTooltips) docyard.initTooltips(container);
|
|
62
|
+
if (docyard.initCodeAnnotations) docyard.initCodeAnnotations(container);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
function reloadContent() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<% has_diff = @diff_lines&.any? %><% has_focus = @focus_lines&.any? %><% has_error = @error_lines&.any? %><% has_warning = @warning_lines&.any? %><% has_title = !@title.nil? && !@title.empty? %><div class="docyard-code-block<%= ' docyard-code-block--line-numbers' if @show_line_numbers %><%= ' docyard-code-block--highlighted' if @highlights&.any? %><%= ' docyard-code-block--diff' if has_diff %><%= ' docyard-code-block--has-focus' if has_focus %><%= ' docyard-code-block--has-error' if has_error %><%= ' docyard-code-block--has-warning' if has_warning %><%= ' docyard-code-block--titled' if has_title %>" data-pagefind-ignore>
|
|
1
|
+
<% has_diff = @diff_lines&.any? %><% has_focus = @focus_lines&.any? %><% has_error = @error_lines&.any? %><% has_warning = @warning_lines&.any? %><% has_annotations = @annotation_markers&.any? %><% has_title = !@title.nil? && !@title.empty? %><div class="docyard-code-block<%= ' docyard-code-block--line-numbers' if @show_line_numbers %><%= ' docyard-code-block--highlighted' if @highlights&.any? %><%= ' docyard-code-block--diff' if has_diff %><%= ' docyard-code-block--has-focus' if has_focus %><%= ' docyard-code-block--has-error' if has_error %><%= ' docyard-code-block--has-warning' if has_warning %><%= ' docyard-code-block--has-annotations' if has_annotations %><%= ' docyard-code-block--titled' if has_title %>" data-pagefind-ignore>
|
|
2
2
|
<% if has_title %>
|
|
3
3
|
<div class="docyard-code-block__header">
|
|
4
4
|
<% if @icon %>
|
data/lib/docyard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docyard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sanif Himani
|
|
@@ -83,30 +83,42 @@ dependencies:
|
|
|
83
83
|
name: parallel
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
|
-
- - "
|
|
86
|
+
- - ">="
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
88
|
version: '1.26'
|
|
89
|
+
- - "<"
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '3.0'
|
|
89
92
|
type: :runtime
|
|
90
93
|
prerelease: false
|
|
91
94
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
95
|
requirements:
|
|
93
|
-
- - "
|
|
96
|
+
- - ">="
|
|
94
97
|
- !ruby/object:Gem::Version
|
|
95
98
|
version: '1.26'
|
|
99
|
+
- - "<"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '3.0'
|
|
96
102
|
- !ruby/object:Gem::Dependency
|
|
97
103
|
name: puma
|
|
98
104
|
requirement: !ruby/object:Gem::Requirement
|
|
99
105
|
requirements:
|
|
100
|
-
- - "
|
|
106
|
+
- - ">="
|
|
101
107
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '7
|
|
108
|
+
version: '7'
|
|
109
|
+
- - "<"
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '9'
|
|
103
112
|
type: :runtime
|
|
104
113
|
prerelease: false
|
|
105
114
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
115
|
requirements:
|
|
107
|
-
- - "
|
|
116
|
+
- - ">="
|
|
108
117
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '7
|
|
118
|
+
version: '7'
|
|
119
|
+
- - "<"
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '9'
|
|
110
122
|
- !ruby/object:Gem::Dependency
|
|
111
123
|
name: rack
|
|
112
124
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -125,16 +137,22 @@ dependencies:
|
|
|
125
137
|
name: rouge
|
|
126
138
|
requirement: !ruby/object:Gem::Requirement
|
|
127
139
|
requirements:
|
|
128
|
-
- - "
|
|
140
|
+
- - ">="
|
|
129
141
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '4
|
|
142
|
+
version: '4'
|
|
143
|
+
- - "<"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '6'
|
|
131
146
|
type: :runtime
|
|
132
147
|
prerelease: false
|
|
133
148
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
149
|
requirements:
|
|
135
|
-
- - "
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '4'
|
|
153
|
+
- - "<"
|
|
136
154
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '
|
|
155
|
+
version: '6'
|
|
138
156
|
- !ruby/object:Gem::Dependency
|
|
139
157
|
name: terser
|
|
140
158
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -199,6 +217,7 @@ files:
|
|
|
199
217
|
- lib/docyard/components/processors/badge_processor.rb
|
|
200
218
|
- lib/docyard/components/processors/callout_processor.rb
|
|
201
219
|
- lib/docyard/components/processors/cards_processor.rb
|
|
220
|
+
- lib/docyard/components/processors/code_block_annotation_preprocessor.rb
|
|
202
221
|
- lib/docyard/components/processors/code_block_diff_preprocessor.rb
|
|
203
222
|
- lib/docyard/components/processors/code_block_extended_fence_postprocessor.rb
|
|
204
223
|
- lib/docyard/components/processors/code_block_extended_fence_preprocessor.rb
|
|
@@ -218,8 +237,10 @@ files:
|
|
|
218
237
|
- lib/docyard/components/processors/table_wrapper_processor.rb
|
|
219
238
|
- lib/docyard/components/processors/tabs_processor.rb
|
|
220
239
|
- lib/docyard/components/processors/tooltip_processor.rb
|
|
240
|
+
- lib/docyard/components/processors/variables_processor.rb
|
|
221
241
|
- lib/docyard/components/processors/video_embed_processor.rb
|
|
222
242
|
- lib/docyard/components/registry.rb
|
|
243
|
+
- lib/docyard/components/support/code_block/annotation_list_parser.rb
|
|
223
244
|
- lib/docyard/components/support/code_block/feature_extractor.rb
|
|
224
245
|
- lib/docyard/components/support/code_block/icon_detector.rb
|
|
225
246
|
- lib/docyard/components/support/code_block/line_number_resolver.rb
|
|
@@ -330,6 +351,7 @@ files:
|
|
|
330
351
|
- lib/docyard/templates/assets/css/components/breadcrumbs.css
|
|
331
352
|
- lib/docyard/templates/assets/css/components/callout.css
|
|
332
353
|
- lib/docyard/templates/assets/css/components/cards.css
|
|
354
|
+
- lib/docyard/templates/assets/css/components/code-annotation.css
|
|
333
355
|
- lib/docyard/templates/assets/css/components/code-block.css
|
|
334
356
|
- lib/docyard/templates/assets/css/components/code-group.css
|
|
335
357
|
- lib/docyard/templates/assets/css/components/feedback.css
|
|
@@ -363,6 +385,7 @@ files:
|
|
|
363
385
|
- lib/docyard/templates/assets/fonts/Inter-Variable.woff2
|
|
364
386
|
- lib/docyard/templates/assets/js/components/abbreviation.js
|
|
365
387
|
- lib/docyard/templates/assets/js/components/banner.js
|
|
388
|
+
- lib/docyard/templates/assets/js/components/code-annotation.js
|
|
366
389
|
- lib/docyard/templates/assets/js/components/code-block.js
|
|
367
390
|
- lib/docyard/templates/assets/js/components/code-group.js
|
|
368
391
|
- lib/docyard/templates/assets/js/components/copy-page.js
|