Almirah 0.4.1 → 0.4.2
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/lib/almirah/doc_fabric.rb +1 -0
- data/lib/almirah/doc_items/code_block.rb +1 -1
- data/lib/almirah/doc_items/heading.rb +22 -4
- data/lib/almirah/doc_items/image.rb +8 -1
- data/lib/almirah/doc_items/markdown_table.rb +1 -1
- data/lib/almirah/doc_items/text_line.rb +26 -5
- data/lib/almirah/doc_types/decision.rb +7 -1
- data/lib/almirah/doc_types/decisions_overview.rb +2 -1
- data/lib/almirah/doc_types/traceability.rb +2 -2
- data/lib/almirah/dom/doc_section.rb +1 -1
- data/lib/almirah/dom/document.rb +1 -1
- data/lib/almirah/html_safe.rb +39 -0
- data/lib/almirah/link_registry.rb +21 -6
- data/lib/almirah/templates/css/main.css +8 -1
- data/lib/almirah/templates/scripts/main.js +4 -1
- data/lib/almirah/templates/scripts/orama_search.js +17 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5221af43605bcaab2815ac5c940d53188394a92f8f8fadbbd2def016d1d52328
|
|
4
|
+
data.tar.gz: dd1d271774fe527d93512134b70cca3471048ff2a0859327c813eb75ebd35b54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6cf6a8d5892855c7b8752a2a3d447fec79dcf89a6a796b931709b2207ceeaddaede5cf7ac262ce13ab8b47aa081ead25fd225f353fff3afb84bc01dfaf2dfbdd
|
|
7
|
+
data.tar.gz: 9c0c6cffe8a41a19fc6778bcb0a3210f5a0ca3402b058d6b5f011b0434805fd11035be76a9ec6a742485839fe7523480d043923e582a2f0367c4fadb93922473
|
data/lib/almirah/doc_fabric.rb
CHANGED
|
@@ -57,8 +57,26 @@ class Heading < Paragraph
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# As get_section_info but with the author-supplied text HTML-escaped for safe
|
|
61
|
+
# interpolation into rendered HTML (headings, TOC, traceability). ADR-188/SRS-096.
|
|
62
|
+
def get_section_info_html
|
|
63
|
+
if level.zero? # Doc Title
|
|
64
|
+
escape_text(@text)
|
|
65
|
+
else
|
|
66
|
+
"#{@section_number} #{escape_text(@text)}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
60
70
|
def get_anchor_text
|
|
61
|
-
"#{@section_number}-#{
|
|
71
|
+
"#{@section_number}-#{anchor_slug}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The anchor is a generated identifier emitted into name/href attributes, so it
|
|
75
|
+
# must not carry the HTML-significant characters that author heading text may
|
|
76
|
+
# contain (ADR-188). Stripping them keeps the anchor inert and self-consistent
|
|
77
|
+
# across the heading, its self-link, and the table of contents.
|
|
78
|
+
def anchor_slug
|
|
79
|
+
getTextWithoutSpaces.gsub(/[<>"'&]/, '')
|
|
62
80
|
end
|
|
63
81
|
|
|
64
82
|
def get_markdown_anchor_text
|
|
@@ -72,10 +90,10 @@ class Heading < Paragraph
|
|
|
72
90
|
@@html_table_render_in_progress = false
|
|
73
91
|
end
|
|
74
92
|
heading_level = level.to_s
|
|
75
|
-
heading_text =
|
|
93
|
+
heading_text = get_section_info_html
|
|
76
94
|
if level.zero?
|
|
77
|
-
heading_level = 1.to_s
|
|
78
|
-
heading_text = @text
|
|
95
|
+
heading_level = 1.to_s # Render Doc Title as a regular h1
|
|
96
|
+
heading_text = escape_text(@text) # Doc Title does not have a section number
|
|
79
97
|
end
|
|
80
98
|
s += "<a name=\"#{@anchor_id}\"></a>\n"
|
|
81
99
|
s += "<h#{heading_level}> #{heading_text} <a href=\"\##{@anchor_id}\" class=\"heading_anchor\">"
|
|
@@ -21,7 +21,14 @@ class Image < DocItem
|
|
|
21
21
|
@@html_table_render_in_progress = false
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
alt = escape_attr(@text)
|
|
25
|
+
src = safe_url(@path)
|
|
26
|
+
if src.nil? # disallowed scheme: render inert rather than emitting it (ADR-188, SRS-098)
|
|
27
|
+
s += "<p style=\"margin-top: 15px;\">[image: #{alt}]"
|
|
28
|
+
return s
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
s += "<p style=\"margin-top: 15px;\"><img src=\"#{escape_attr(src)}\" alt=\"#{alt}\" "
|
|
25
32
|
s += "href=\"javascript:void(0)\" onclick=\"image_OnClick(this)\">"
|
|
26
33
|
return s
|
|
27
34
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'cgi'
|
|
2
2
|
require 'uri'
|
|
3
3
|
require_relative '../relative_url'
|
|
4
|
+
require_relative '../html_safe'
|
|
4
5
|
|
|
5
6
|
class TextLineToken
|
|
6
7
|
attr_accessor :value
|
|
@@ -217,6 +218,12 @@ class TextLineParser
|
|
|
217
218
|
end
|
|
218
219
|
|
|
219
220
|
class TextLineBuilderContext
|
|
221
|
+
# Literal (non-markup) text run. Subclasses encode it for the HTML context;
|
|
222
|
+
# the base context leaves it untouched for plain reconstruction/unit tests.
|
|
223
|
+
def literal_text(str)
|
|
224
|
+
str
|
|
225
|
+
end
|
|
226
|
+
|
|
220
227
|
def italic(str)
|
|
221
228
|
str
|
|
222
229
|
end
|
|
@@ -363,7 +370,11 @@ class TextLineBuilder
|
|
|
363
370
|
tii += 1
|
|
364
371
|
end
|
|
365
372
|
if is_found
|
|
366
|
-
|
|
373
|
+
# URL is reconstructed raw (not via restore) so scheme classification
|
|
374
|
+
# and file-path resolution see the original characters; link() applies
|
|
375
|
+
# attribute escaping and the scheme allow-list (ADR-188).
|
|
376
|
+
raw_url = (sub_list_url_address || []).map(&:value).join
|
|
377
|
+
result += @builder_context.link(restore(sub_list_url_text), raw_url)
|
|
367
378
|
else
|
|
368
379
|
result += '['
|
|
369
380
|
ti = ti_starting_position + 1
|
|
@@ -373,7 +384,7 @@ class TextLineBuilder
|
|
|
373
384
|
result += @builder_context.inline_code(token_list[ti].value)
|
|
374
385
|
ti += 1
|
|
375
386
|
when 'TextLineToken', 'ParentheseLeft', 'ParentheseRight', 'SquareBracketRight', 'DoubleSquareBracketRight'
|
|
376
|
-
result += token_list[ti].value
|
|
387
|
+
result += @builder_context.literal_text(token_list[ti].value)
|
|
377
388
|
ti += 1
|
|
378
389
|
else
|
|
379
390
|
ti += 1
|
|
@@ -384,6 +395,8 @@ class TextLineBuilder
|
|
|
384
395
|
end
|
|
385
396
|
|
|
386
397
|
class TextLine < TextLineBuilderContext
|
|
398
|
+
include HtmlSafe
|
|
399
|
+
|
|
387
400
|
@@link_registry = nil # rubocop:disable Style/ClassVars
|
|
388
401
|
@@broken_links = [] # rubocop:disable Style/ClassVars
|
|
389
402
|
|
|
@@ -423,6 +436,11 @@ class TextLine < TextLineBuilderContext
|
|
|
423
436
|
tlb.restore(tlp.tokenize(str))
|
|
424
437
|
end
|
|
425
438
|
|
|
439
|
+
# Literal text run, HTML-escaped for element content (ADR-188, SRS-096).
|
|
440
|
+
def literal_text(str)
|
|
441
|
+
escape_text(str)
|
|
442
|
+
end
|
|
443
|
+
|
|
426
444
|
def italic(str)
|
|
427
445
|
"<i>#{str}</i>"
|
|
428
446
|
end
|
|
@@ -445,12 +463,15 @@ class TextLine < TextLineBuilderContext
|
|
|
445
463
|
case kind
|
|
446
464
|
when :internal
|
|
447
465
|
href = RelativeUrl.between(owner_document.output_rel_path, target.output_rel_path, fragment: fragment)
|
|
448
|
-
"<a href=\"#{href}\" class=\"external\">#{link_text}</a>"
|
|
466
|
+
"<a href=\"#{escape_attr(href)}\" class=\"external\">#{link_text}</a>"
|
|
449
467
|
when :broken
|
|
450
468
|
TextLine.record_broken_link(owner_document, raw)
|
|
451
|
-
"<a href=\"#{raw}\" class=\"broken_link\" title=\"Unresolved cross-document link\">#{link_text}</a>"
|
|
469
|
+
"<a href=\"#{escape_attr(raw)}\" class=\"broken_link\" title=\"Unresolved cross-document link\">#{link_text}</a>"
|
|
452
470
|
else
|
|
453
|
-
|
|
471
|
+
url = safe_url(raw)
|
|
472
|
+
return link_text if url.nil? # disallowed scheme: render inert (ADR-188, SRS-098)
|
|
473
|
+
|
|
474
|
+
"<a target=\"_blank\" rel=\"noopener\" href=\"#{escape_attr(url)}\" class=\"external\">#{link_text}</a>"
|
|
454
475
|
end
|
|
455
476
|
end
|
|
456
477
|
|
|
@@ -7,7 +7,7 @@ require_relative '../doc_items/markdown_table'
|
|
|
7
7
|
|
|
8
8
|
class Decision < PersistentDocument # rubocop:disable Style/Documentation,Metrics/ClassLength
|
|
9
9
|
attr_accessor :path, :sequence_number, :record_type, :html_rel_path, :root_prefix, :current_status,
|
|
10
|
-
:start_date, :target_release_version, :specifications_path, :wrong_links_hash
|
|
10
|
+
:start_date, :target_date, :target_release_version, :specifications_path, :wrong_links_hash
|
|
11
11
|
|
|
12
12
|
def initialize(file_path)
|
|
13
13
|
super
|
|
@@ -16,6 +16,7 @@ class Decision < PersistentDocument # rubocop:disable Style/Documentation,Metric
|
|
|
16
16
|
assign_id_parts(stem)
|
|
17
17
|
@current_status = nil
|
|
18
18
|
@start_date = nil
|
|
19
|
+
@target_date = nil
|
|
19
20
|
@target_release_version = nil
|
|
20
21
|
@wrong_links_hash = {}
|
|
21
22
|
end
|
|
@@ -49,6 +50,11 @@ class Decision < PersistentDocument # rubocop:disable Style/Documentation,Metric
|
|
|
49
50
|
@start_date = dates.min
|
|
50
51
|
end
|
|
51
52
|
|
|
53
|
+
def extract_target_date
|
|
54
|
+
dates = collect_dates('Status', 'Date') + collect_dates('Scope', 'Target Date')
|
|
55
|
+
@target_date = dates.max
|
|
56
|
+
end
|
|
57
|
+
|
|
52
58
|
def extract_target_release_version
|
|
53
59
|
@target_release_version = lookup_cell(
|
|
54
60
|
section_name: 'Software Versions',
|
|
@@ -54,7 +54,8 @@ class DecisionsOverview < BaseDocument # rubocop:disable Style/Documentation,Met
|
|
|
54
54
|
s += "\t\t<td class=\"item_text\" style='padding: 5px;'>#{title_html}</td>\n"
|
|
55
55
|
start_date_html = doc.start_date ? doc.start_date.strftime('%d-%m-%Y') : ''
|
|
56
56
|
s += "\t\t<td class=\"item_meta\">#{start_date_html}</td>\n"
|
|
57
|
-
|
|
57
|
+
target_date_html = doc.target_date ? doc.target_date.strftime('%d-%m-%Y') : ''
|
|
58
|
+
s += "\t\t<td class=\"item_meta\">#{target_date_html}</td>\n"
|
|
58
59
|
s += "\t\t<td class=\"item_meta\">#{doc.target_release_version}</td>\n"
|
|
59
60
|
s += "\t\t<td class=\"item_meta\"></td>\n"
|
|
60
61
|
s += "</tr>\n"
|
|
@@ -75,7 +75,7 @@ class Traceability < BaseDocument
|
|
|
75
75
|
top_item.down_links.each do |bottom_item|
|
|
76
76
|
id_color = "style='background-color: ##{bottom_item.parent_doc.color};'"
|
|
77
77
|
bottom_f_text = bottom_item.format_string( bottom_item.text )
|
|
78
|
-
document_section = bottom_item.parent_heading.
|
|
78
|
+
document_section = bottom_item.parent_heading.get_section_info_html
|
|
79
79
|
s += "\t<tr>\n"
|
|
80
80
|
s += "\t\t<td class=\"item_id\"><a href=\"./../#{top_item.parent_doc.id}/#{top_item.parent_doc.id}.html##{top_item.id}\" class=\"external\">#{top_item.id}</a></td>\n"
|
|
81
81
|
s += "\t\t<td class=\"item_text\" style='width: 34%;'>#{top_f_text}</td>\n"
|
|
@@ -105,7 +105,7 @@ class Traceability < BaseDocument
|
|
|
105
105
|
if bottom_item.parent_doc.id == @bottom_doc.id
|
|
106
106
|
|
|
107
107
|
bottom_f_text = bottom_item.format_string( bottom_item.text )
|
|
108
|
-
document_section = bottom_item.parent_heading.
|
|
108
|
+
document_section = bottom_item.parent_heading.get_section_info_html
|
|
109
109
|
|
|
110
110
|
s += "\t<tr>\n"
|
|
111
111
|
s += "\t\t<td class=\"item_id\" #{id_color}><a href=\"./../#{top_item.parent_doc.id}/#{top_item.parent_doc.id}.html##{top_item.id}\" class=\"external\">#{top_item.id}</a></td>\n"
|
|
@@ -11,7 +11,7 @@ class DocSection
|
|
|
11
11
|
s = ''
|
|
12
12
|
s += "\t<li onclick=\"nav_toggle_expand_list(this, event)\">" \
|
|
13
13
|
'<span class="fa-li"><i class="fa fa-minus-square-o"> </i></span>'
|
|
14
|
-
s += "<a href=\"##{@heading.anchor_id}\">#{@heading.
|
|
14
|
+
s += "<a href=\"##{@heading.anchor_id}\">#{@heading.get_section_info_html}</a>\n"
|
|
15
15
|
unless @sections.empty?
|
|
16
16
|
s += "\t\t<ul class=\"fa-ul\">\n"
|
|
17
17
|
@sections.each do |sub_section|
|
data/lib/almirah/dom/document.rb
CHANGED
|
@@ -64,7 +64,7 @@ class Document
|
|
|
64
64
|
|
|
65
65
|
def section_tree_to_html
|
|
66
66
|
s = ''
|
|
67
|
-
s += "<a href=\"##{@root_section.heading.anchor_id}\">#{@root_section.heading.
|
|
67
|
+
s += "<a href=\"##{@root_section.heading.anchor_id}\">#{@root_section.heading.get_section_info_html}</a>\n"
|
|
68
68
|
unless @root_section.sections.empty?
|
|
69
69
|
s += "\t<ul class=\"fa-ul\" style=\"margin-top: 2px;\">\n"
|
|
70
70
|
@root_section.sections.each do |sub_section|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
|
|
5
|
+
# Shared HTML output-encoding helpers (ADR-188, SRS-096/097/098).
|
|
6
|
+
#
|
|
7
|
+
# Author-written Markdown is untrusted text and must be encoded for its HTML
|
|
8
|
+
# context at the point of output. These helpers are the single mechanism every
|
|
9
|
+
# renderer routes through, so coverage cannot drift item-by-item the way it did
|
|
10
|
+
# when only inline code and wiki-link text were escaped.
|
|
11
|
+
module HtmlSafe
|
|
12
|
+
# URL schemes permitted in link/image targets. Anything else (notably
|
|
13
|
+
# javascript:, data:, vbscript:) is treated as unsafe and rendered inert.
|
|
14
|
+
ALLOWED_URL_SCHEMES = %w[http https mailto].freeze
|
|
15
|
+
|
|
16
|
+
# Escapes literal text rendered into element content (the five characters:
|
|
17
|
+
# & < > " '). Used for paragraph, heading, blockquote, table-cell and fenced
|
|
18
|
+
# code block text. SRS-096.
|
|
19
|
+
def escape_text(str)
|
|
20
|
+
CGI.escapeHTML(str.to_s)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Escapes a value interpolated into a quoted HTML attribute so it cannot
|
|
24
|
+
# terminate the attribute or introduce new attributes/elements. SRS-097.
|
|
25
|
+
def escape_attr(str)
|
|
26
|
+
CGI.escapeHTML(str.to_s)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns the URL when it is a relative/anchor reference or carries an allowed
|
|
30
|
+
# scheme; returns nil for any other scheme so the caller can render the
|
|
31
|
+
# link/image inert. SRS-098.
|
|
32
|
+
def safe_url(raw)
|
|
33
|
+
url = raw.to_s.strip
|
|
34
|
+
scheme = url[/\A([a-z][a-z0-9+.-]*):/i, 1]
|
|
35
|
+
return url if scheme.nil? # relative path or anchor reference
|
|
36
|
+
|
|
37
|
+
ALLOWED_URL_SCHEMES.include?(scheme.downcase) ? url : nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -17,14 +17,14 @@ class LinkRegistry
|
|
|
17
17
|
# Each registered document is expected to already carry an output_rel_path
|
|
18
18
|
# (its generated page, relative to the build root).
|
|
19
19
|
def register(doc)
|
|
20
|
-
|
|
21
|
-
if @by_id.key?(key) && !@by_id[key].equal?(doc)
|
|
22
|
-
@collisions << key
|
|
23
|
-
else
|
|
24
|
-
@by_id[key] = doc
|
|
25
|
-
end
|
|
20
|
+
register_id(doc.id.to_s.downcase, doc)
|
|
26
21
|
return unless doc.respond_to?(:path) && doc.path
|
|
27
22
|
|
|
23
|
+
# Also index by the filename stem so a document resolves by filename, not
|
|
24
|
+
# only by id (SRS-090). For specs/protocols the stem equals the id; for
|
|
25
|
+
# decision records the id is the truncated <letters>-<digits> prefix, so the
|
|
26
|
+
# full stem (e.g. "adr-170-introduce-decision-records") is a distinct alias.
|
|
27
|
+
register_id(File.basename(doc.path, '.*').downcase, doc)
|
|
28
28
|
@by_source[File.expand_path(doc.path)] = doc
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -35,4 +35,19 @@ class LinkRegistry
|
|
|
35
35
|
def find_by_source(source_path)
|
|
36
36
|
@by_source[File.expand_path(source_path)]
|
|
37
37
|
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Adds an id/stem key for a document, recording a collision only when the key
|
|
42
|
+
# already maps to a different document. Registering the same document under
|
|
43
|
+
# both its id and its filename stem is expected and not a collision.
|
|
44
|
+
def register_id(key, doc)
|
|
45
|
+
return if key.empty?
|
|
46
|
+
|
|
47
|
+
if @by_id.key?(key) && !@by_id[key].equal?(doc)
|
|
48
|
+
@collisions << key
|
|
49
|
+
else
|
|
50
|
+
@by_id[key] = doc
|
|
51
|
+
end
|
|
52
|
+
end
|
|
38
53
|
end
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
html {
|
|
2
|
+
/* Leave room for the fixed #top_nav so in-page anchor links
|
|
3
|
+
are not hidden behind it when navigating to them. */
|
|
4
|
+
scroll-padding-top: 40px;
|
|
5
|
+
}
|
|
1
6
|
body {
|
|
2
7
|
font-family: Verdana, sans-serif;
|
|
3
8
|
font-size: 12px;
|
|
@@ -224,7 +229,9 @@ img{
|
|
|
224
229
|
background: #EEEEEE;
|
|
225
230
|
border: 1px solid #ddd;
|
|
226
231
|
position: fixed;
|
|
227
|
-
|
|
232
|
+
top: 0; /* Anchor top and bottom so the pane spans exactly the */
|
|
233
|
+
bottom: 0; /* viewport; padding/border stay inside the border box so */
|
|
234
|
+
/* overflow-y scroll can reach the last items. */
|
|
228
235
|
visibility: hidden;
|
|
229
236
|
z-index: 1;
|
|
230
237
|
overflow-y: auto;
|
|
@@ -74,7 +74,10 @@ function openNav() {
|
|
|
74
74
|
|
|
75
75
|
modal.style.display = "block";
|
|
76
76
|
modalImg.src = clicked.src;
|
|
77
|
-
|
|
77
|
+
// Author-supplied alt text: insert as plain text, never as HTML (ADR-188).
|
|
78
|
+
// Reading clicked.alt yields the decoded value, so innerHTML here would
|
|
79
|
+
// re-parse author markup as live DOM (DOM-based XSS).
|
|
80
|
+
captionText.textContent = clicked.alt;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
function modal_close_OnClick(clicked){
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
// Do search only on the Index Page
|
|
2
2
|
import { create, search, insert } from 'https://unpkg.com/@orama/orama@3.1.18/dist/browser/index.js'
|
|
3
3
|
|
|
4
|
+
// Author-derived values are treated as untrusted (ADR-188). Every result field
|
|
5
|
+
// is inserted with safe DOM interfaces (createTextNode / property assignment) and
|
|
6
|
+
// never parsed as HTML, and any URL is admitted only if it is a relative reference
|
|
7
|
+
// or uses an allowed scheme; otherwise the link is rendered inert.
|
|
8
|
+
const ALLOWED_URL_SCHEMES = ['http', 'https', 'mailto'];
|
|
9
|
+
function safeUrl(url) {
|
|
10
|
+
const s = (url == null ? '' : String(url)).trim();
|
|
11
|
+
const m = s.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
12
|
+
if (!m) return s; // relative path or anchor
|
|
13
|
+
return ALLOWED_URL_SCHEMES.includes(m[1].toLowerCase()) ? s : '#';
|
|
14
|
+
}
|
|
15
|
+
|
|
4
16
|
// Create DB
|
|
5
17
|
const db = await create({
|
|
6
18
|
schema: {
|
|
@@ -87,19 +99,20 @@ async function search_onKeyUp(){
|
|
|
87
99
|
let cell = document.createElement("td");
|
|
88
100
|
let i = document.createElement("i");
|
|
89
101
|
i.classList.add("fa","fa-file-text-o");
|
|
90
|
-
|
|
102
|
+
if (/^[0-9a-fA-F]{3,8}$/.test(doc_color)) {
|
|
103
|
+
i.style.backgroundColor = "#" + doc_color;
|
|
104
|
+
}
|
|
91
105
|
cell.appendChild(i);
|
|
92
106
|
let textnode = document.createTextNode("\xa0" + doc_title);
|
|
93
107
|
cell.appendChild(textnode);
|
|
94
108
|
row.appendChild(cell)
|
|
95
109
|
cell = document.createElement("td");
|
|
96
110
|
|
|
97
|
-
const a = document.createElement('a');
|
|
111
|
+
const a = document.createElement('a');
|
|
98
112
|
const link = document.createTextNode(heading_text)
|
|
99
113
|
a.appendChild(link);
|
|
100
114
|
a.title = heading_text;
|
|
101
|
-
a.href = heading_url;
|
|
102
|
-
document.body.appendChild(a);
|
|
115
|
+
a.href = safeUrl(heading_url);
|
|
103
116
|
|
|
104
117
|
cell.appendChild(a);
|
|
105
118
|
row.appendChild(cell)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: Almirah
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oleksandr Ivanov
|
|
@@ -90,6 +90,7 @@ files:
|
|
|
90
90
|
- lib/almirah/doc_types/traceability.rb
|
|
91
91
|
- lib/almirah/dom/doc_section.rb
|
|
92
92
|
- lib/almirah/dom/document.rb
|
|
93
|
+
- lib/almirah/html_safe.rb
|
|
93
94
|
- lib/almirah/link_registry.rb
|
|
94
95
|
- lib/almirah/navigation_pane.rb
|
|
95
96
|
- lib/almirah/project.rb
|