Skip to content

Commit b5d1b5f

Browse files
committed
always outline external links
1 parent 05952cb commit b5d1b5f

File tree

5 files changed

+77
-10
lines changed

5 files changed

+77
-10
lines changed

Sources/MarkdownRendering/HTML/HTML.OutputStreamableMarkdown.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ protocol _HTMLOutputStreamableMarkdown:HTML.OutputStreamable
2020
/// Returns the value for an attribute identified by the given reference.
2121
/// If the witness returns nil, the renderer will omit the attribute.
2222
///
23+
/// The witness can change the attribute that will be rendered by modifying the argument.
24+
/// This is useful for replacing ``Markdown.Bytecode.Attribute/href`` with
25+
/// ``Markdown.Bytecode.Attribute/external``
26+
///
2327
/// This can be used to influence the behavior of the special syntax
2428
/// highlight contexts.
25-
func load(_ reference:Int, for attribute:Markdown.Bytecode.Attribute) -> String?
29+
func load(_ reference:Int, for attribute:inout Markdown.Bytecode.Attribute) -> String?
2630

2731
/// Writes arbitrary content to the provided HTML output, identified by
2832
/// the given reference.
@@ -32,7 +36,7 @@ extension HTML.OutputStreamableMarkdown
3236
{
3337
/// Returns nil.
3438
@inlinable public
35-
func load(_ reference:Int, for attribute:Markdown.Bytecode.Attribute) -> String?
39+
func load(_ reference:Int, for attribute:inout Markdown.Bytecode.Attribute) -> String?
3640
{
3741
nil
3842
}
@@ -83,10 +87,10 @@ extension HTML.OutputStreamableMarkdown
8387
case .attribute(let attribute, nil):
8488
attributes.flush(beginning: attribute)
8589

86-
case .attribute(let attribute, let reference?):
90+
case .attribute(var attribute, let reference?):
8791
attributes.flush()
8892

89-
if let value:String = self.load(reference, for: attribute)
93+
if let value:String = self.load(reference, for: &attribute)
9094
{
9195
attributes.list.append(value: value, as: attribute)
9296
}

Sources/SwiftinitPages/Sections/CodeSection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ struct CodeSection
2222
}
2323
extension CodeSection:HTML.OutputStreamableMarkdown
2424
{
25-
func load(_ reference:Int, for attribute:Markdown.Bytecode.Attribute) -> String?
25+
func load(_ reference:Int, for attribute:inout Markdown.Bytecode.Attribute) -> String?
2626
{
2727
switch attribute
2828
{

Sources/SwiftinitPages/Sections/ProseSection.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extension ProseSection
3232
}
3333
extension ProseSection:HTML.OutputStreamableMarkdown
3434
{
35-
func load(_ reference:Int, for attribute:Markdown.Bytecode.Attribute) -> String?
35+
func load(_ reference:Int, for attribute:inout Markdown.Bytecode.Attribute) -> String?
3636
{
3737
guard self.outlines.indices.contains(reference)
3838
else
@@ -45,6 +45,23 @@ extension ProseSection:HTML.OutputStreamableMarkdown
4545
case .text(let text):
4646
return text
4747

48+
case .link(https: let url, safe: let safe):
49+
switch attribute
50+
{
51+
case .href:
52+
if !safe
53+
{
54+
attribute = .external
55+
}
56+
fallthrough
57+
58+
case .external:
59+
return "https://\(url)"
60+
61+
default:
62+
return nil
63+
}
64+
4865
case .path(_, let scalars):
4966
guard
5067
let target:Unidoc.Scalar = scalars.last
@@ -84,6 +101,10 @@ extension ProseSection:HTML.OutputStreamableMarkdown
84101

85102
switch self.outlines[reference]
86103
{
104+
case .link:
105+
// No reason this should ever appear here.
106+
return
107+
87108
case .text(let text):
88109
html[.code] = text
89110

@@ -114,6 +135,10 @@ extension ProseSection:TextOutputStreamableMarkdown
114135
}
115136
switch self.outlines[reference]
116137
{
138+
case .link:
139+
// No reason this should ever appear here.
140+
return
141+
117142
case .text(let text):
118143
utf8 += text.utf8
119144

Sources/UnidocLinker/Resolver/Unidoc.Resolver.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,29 @@ extension Unidoc.Resolver
110110
}
111111

112112
case .unresolved(let unresolved):
113+
if case .web = unresolved.type
114+
{
115+
let domain:Substring = unresolved.link.prefix { $0 != "/" }
116+
// We will follow links to Apple, GitHub, and reputable open-source indexes.
117+
let safe:Bool = switch domain
118+
{
119+
case "developer.apple.com": true
120+
case "www.freebsd.org": true
121+
case "github.com": true
122+
case "tools.ietf.org": true
123+
case "man7.org": true
124+
case "developer.mozilla.org": true
125+
case "docs.scala-lang.org": true
126+
case "swiftinit.org": true
127+
case "forums.swift.org": true
128+
case "swift.org": true
129+
case "en.wikipedia.org": true
130+
default: false
131+
}
132+
133+
return .link(https: unresolved.link, safe: safe)
134+
}
135+
113136
guard
114137
let (codelink, resolution):
115138
(Codelink, CodelinkResolver<Unidoc.Scalar>.Overload.Target?) =

Sources/UnidocRecords/Volumes/Passages/Unidoc.Outline.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ extension Unidoc
66
@frozen public
77
enum Outline:Equatable, Sendable
88
{
9+
/// An external web link. The string does not contain the URL scheme.
10+
case link(https:String, safe:Bool)
911
case path(String, [Unidoc.Scalar])
1012
case text(String)
1113
}
@@ -15,6 +17,8 @@ extension Unidoc.Outline
1517
@frozen public
1618
enum CodingKey:String, Sendable
1719
{
20+
case link_safe = "H"
21+
case link_url = "U"
1822
case display = "T"
1923
case scalars = "s"
2024
}
@@ -26,6 +30,10 @@ extension Unidoc.Outline:BSONDocumentEncodable
2630
{
2731
switch self
2832
{
33+
case .link(https: let url, let safe):
34+
bson[.link_safe] = safe ? true : nil
35+
bson[.link_url] = url
36+
2937
case .path(let string, let scalars):
3038
bson[.scalars] = scalars
3139
bson[.display] = string
@@ -40,11 +48,18 @@ extension Unidoc.Outline:BSONDocumentDecodable
4048
@inlinable public
4149
init(bson:BSON.DocumentDecoder<CodingKey>) throws
4250
{
43-
let display:String = try bson[.display].decode()
44-
switch try bson[.scalars]?.decode(to: [Unidoc.Scalar].self)
51+
if let display:String = try bson[.display]?.decode()
52+
{
53+
switch try bson[.scalars]?.decode(to: [Unidoc.Scalar].self)
54+
{
55+
case let scalars?: self = .path(display, scalars)
56+
case nil: self = .text(display)
57+
}
58+
}
59+
else
4560
{
46-
case let scalars?: self = .path(display, scalars)
47-
case nil: self = .text(display)
61+
self = .link(https: try bson[.link_url].decode(),
62+
safe: try bson[.link_safe]?.decode() ?? false)
4863
}
4964
}
5065
}

0 commit comments

Comments
 (0)