/
footnotes.ts
150 lines (137 loc) · 4.33 KB
/
footnotes.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
type footnoteObject = {
id: string,
quote: string,
url: string,
title: string,
dl: string,
}
function HypothesisFootnotes() {
installFootnotesContainer();
let hypDotIsUrls = findAndDecorateHypDotIsUrls()
let hypIsResults = getHypIsResults(hypDotIsUrls)
settle(hypIsResults.promises)
.then( () => {
for (let i = 0; i < hypDotIsUrls.length; i++ ) {
let hypIsUrl = hypDotIsUrls[i]
let footnoteObject = hypIsResults.footnoteObjects[hypIsUrl] as footnoteObject
makeFootnoteFromObject(i, footnoteObject)
}
})
}
/*
Scan for hyp.is urls
Decorate with superscripts pointing to (yet-to-be-generated) footnotes
Return array of hyp.is urls
*/
function findAndDecorateHypDotIsUrls() {
let hypDotIsUrls:string[] = [];
let directLinks:NodeListOf<HTMLLinkElement> = document.querySelectorAll('a[href^="https://hyp.is"]')
for (let i = 0; i < directLinks.length; i++) {
let directLink = directLinks[i]
let href = directLink.href as string
let url = "https://" + href.match(/(hyp.is\/[^\/]+)/)[1]
let id = href.match(/hyp.is\/([^\/]+)/)[1]
hypDotIsUrls.push(url)
var num = i + 1
directLink.outerHTML += `
<a name="_fn_${id}"></a>
<sup>(<a title="visit footnote" href="#fn_${id}">${num}</a>)</sup>`
}
return hypDotIsUrls;
}
/*
Retrieve annotations from hyp_is urls
Construct footnote objects from them
Return an array of promises, and an object whose keys are hyp.is links and values are footnote objects
created when the promise to fetch each hyp.is link is resolved.
*/
function getHypIsResults(hypDotIsUrls:string[]) {
let promises:Promise<any>[] = []
let footnoteObjects:any = {}
for (let i = 0; i < hypDotIsUrls.length; i++) {
let url = hypDotIsUrls[i]
let id = url.match(/hyp.is\/([^\/]+)/)[1]
let options:any = {
method: "GET",
url: "https://hypothes.is/api/annotations/" + id
}
promises.push(
hlib.httpRequest(options)
.then(function(data:any) {
let row = hlib.parseAnnotation(JSON.parse(data.response))
let dl = "https://hyp.is/" + row.id
let footnoteObject:footnoteObject = {
id: row.id,
quote: row.quote,
url: row.url,
title: row.title,
dl: dl
}
footnoteObjects[dl] = footnoteObject
})
)
}
return {
promises: promises,
footnoteObjects: footnoteObjects
}
}
/*
Construct a footnote from a footnote object
Add it to the page
*/
function makeFootnoteFromObject(num:number, obj:footnoteObject) {
num += 1
var div = document.createElement("div")
// div.style['font-size'] = 'smaller';
div.id = "fn_" + obj.id
div.innerHTML = `
<a name="fn_{$obj.id}">
<p class="footnote" style="font-size:smaller">${num}
<a target="_blank" href="${obj.dl}">${obj.title}</a>
<a title="see in context" href="#_fn_${obj.id}">⏎</a>
</p>
<blockquote style="font-family:italic">
${obj.quote}
</blockquote>`
let body = document.querySelector('body') as HTMLElement
body.appendChild(div)
}
function installFootnotesContainer() {
let footnotesContainer = document.createElement('div')
footnotesContainer.setAttribute("id", "footnotesContainer")
let footnotesHeader = document.createElement("h2")
footnotesHeader.innerHTML = "Footnotes"
footnotesContainer.appendChild(footnotesHeader)
footnotesElement().appendChild(footnotesContainer)
}
function footnotesElement() : HTMLElement {
let element = document.querySelector('#HypothesisFootnotes') as HTMLElement
if (element) {
return element
}
else {
return document.querySelector('body') as HTMLElement
}
}
/*
A page may include many hyp.is urls. The script makes a corresponding number
of Hypothesis API requests. Without this wrapper, if any request should fail,
Promise.all will immediately reject. This ensures that such a rejection won't
prevent a full set of responses.
*/
function settle(promises:Promise<any>[]) {
let alwaysFulfilled = promises.map(function(p) {
return p.then(
function onFulfilled(value) {
return { state: "fulfilled", value: value };
},
function onRejected(reason) {
console.log("settle: rejected promise", reason);
return { state: "rejected", reason: reason };
}
);
});
return Promise.all(alwaysFulfilled);
}
HypothesisFootnotes();