cortav  Diff

Differences From Artifact [ed743c91e7]:

To Artifact [706a61f3d9]:


1
2
3


























4
5
6
7
8


9



10



11
12










































































































































local ct = require 'cortav'
local ss = require 'sirsem'



























ct.ext.install {
	id = 'toc';
	desc = 'provides a table of contents for HTML renderer plus generic fallback';
	version = ss.version {0,1; 'devel'};
	contributors = {{name='lexi hale', handle='velartrill', mail='lexi@hale.su', homepage='https://hale.su'}};


	directive = function(words)







	end;
}













































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





>
>
|
>
>
>

>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
local ct = require 'cortav'
local ss = require 'sirsem'

local css_toc = [[

]]

local css_toc_fixed = [[
	@media (min-width: calc(@[width]:[100vw] + 20em)) {
		ol.toc {
			position: fixed;
			padding-top: 1em; padding-bottom: 1em;
			padding-right: 1em;
			margin-top: 0; margin-bottom: 0;
			right: 0; top: 0; bottom: 0;
			max-width: calc(50vw - ((@[width]:[0]) / 2) - 3.5em);
			overflow-y: auto;
		}
		@media (max-width: calc(@[width]:[100vw] + 30em)) {
			ol.toc {
				max-width: calc(100vw - ((@[width]:[0])) - 9.5em);
			}
			body {
				margin-left: 5em;
			}
		}
	}
]]

ct.ext.install {
	id = 'toc';
	desc = 'provides a table of contents for HTML renderer plus generic fallback';
	version = ss.version {0,1; 'devel'};
	contributors = {{name='lexi hale', handle='velartrill', mail='lexi@hale.su', homepage='https://hale.su'}};
	default = true; -- on unless inhibited
	hook = {
		doc_init = function(job)
			print('initing doc:toc',job.doc)
			job.state.toc_custom_position = false
		end;

		render_html_init = function(job, render)
			render.stylesets.toc = css_toc
			render.stylesets.tocFixed = css_toc_fixed
		end;

		render_html_ir_assemble = function(job, render, ir)
			-- the custom position state is part of the document job,
			-- but rendering is a separate job, so we need to get the
			-- state of this extension in the parent job, which is
			-- done with the job:unwind(depth) call. unwind is a method
			-- of the delegate we access the job through which gives us
			-- direct access to the job state of this extension; unwind
			-- climbs the jobtree and constructs a similar delegate for
			-- the nth parent. note that this will only work if the
			-- current extension hasn't been excluded by predicate from
			-- the nth parent!
			if not job:unwind(1).state.toc_custom_position then
				-- TODO insert %toc end of first section
			end
		end;
	};
	directives = {
		mark = function (job, ctx, words) 
			local _, _, text = words(2)
			ctx:insert {kind = 'anchor', _toc_label = ct.parse_span(text,ctx)}
		end;
		name = function (job, ctx, words) 
			local _, _, id, text = words(3)
			ctx:insert {kind = 'anchor', id=id, _toc_label = ct.parse_span(text,ctx)}
		end;
		[true] = function (job, ctx, words) 
			local _, op, val = words(2)
			if op == nil then
				local toc = {kind='toc'}
				ctx:insert(toc)
				-- same deal here -- directives are processed as part of
				-- the parse job, which is forked off the document job,
				-- so we need to climb the jobstack
				job:unwind(1).state.toc_custom_position = true
				job:hook('ext_toc_position', ctx, toc)
			else
				ctx:fail 'bad %toc directive'
			end
		end;
	};
	render = {
		toc = {
			html = function(job, renderer, block, section)
				-- “tagproc” contains the functions that determine what kind
				-- of data our abstract tags will be transformed into. this
				-- is needed to that plain text, HTML, and HTML IR can be
				-- produced from the same functions just by varying the
				-- proc set.
				-- 
				-- “astproc” contains the functions that determine what form
				-- our span arrays (and blocks, but not relevant here) will
				-- be transformed into, and is analogous to “tagproc”
				local tag = renderer.tagproc.tag;
				local elt = renderer.tagproc.elt;
				local catenate = renderer.tagproc.catenate;
				local sr = renderer.astproc.span_renderers;
				local getSafeID = renderer.state.obj_htmlid;
				
				-- toplevel HTML IR
				local lst = {tag = 'ol', attrs={class='toc'}, nodes={}}

				-- "renderer.state" contains the stateglob of the renderer
				-- itself, not to be confused with the "state" parameter
				-- which contains this extension's share of the job state
				-- we use it to activate the stylesets we injected earlier
				renderer.state.stylesets_active.toc = true
				if renderer.state.opts['width'] then
					renderer.state.stylesets_active.tocFixed = true
				end

				-- assemble a tree of links from the document section
				-- structure. this is tricky, because we need a tree, but
				-- all we have is a flat list with depth values attached to
				-- each node.
				local stack = {lst}
				local top = function() return stack[#stack] end
				-- job.doc is the document the render job is bound to, and
				-- its secorder field is a list of all the doc's sections in
				-- the order they occur ("doc.sections" is a hashmap from name
				-- to section object)
				local all = job.doc.secorder

				for i, sec in ipairs(all) do
					if sec.heading_node then -- does this section have a label?
						local ent = tag('li',nil,
							 catenate{tag('a', {href='#'..getSafeID(sec)},
								sr.htmlSpan(sec.heading_node.spans, sec.heading_node, sec))})
						if sec.depth > #stack then
							local n = {tag = 'ol', attrs={}, nodes={ent}}
							table.insert(top().nodes[#top().nodes].nodes, n)
							table.insert(stack, n)
						else
							if sec.depth < #stack then
								for j=#stack,sec.depth+1,-1 do stack[j] = nil end
							end
							table.insert(top().nodes, ent)
						end

						-- now we need to assemble a list of items within the
						-- section worthy of an entry on their own. currently
						-- this is only anchors created with %toc mark|name
						local innerlinks = {}
						local noteworthy = { anchor = true }
						for j, block in pairs(sec.blocks) do
							if noteworthy[block.kind] then
								local label = ss.coalesce(block._toc_label, block.label, block.spans)
								if label then
									table.insert(innerlinks, {
										id = renderer.state.obj_htmlid(block);
										label = label;
										block = block;
									})
								end
							end
						end

						if next(innerlinks) then
							local n = {tag = 'ol', attrs = {}, nodes = {}}
							for i, l in ipairs(innerlinks) do
								local nn = {
									tag = 'a';
									attrs = {href = '#' .. l.id};
									nodes = {sr.htmlSpan(l.label, l.block, sec)};
								}
								table.insert(n.nodes, {tag = 'li', attrs = {}, nodes={nn}})
							end
							table.insert(ent.nodes, n)
						end
            print(ss.dump(ent))
					end
				end
				return lst
			end;

			[true] = function() end; -- fallback // convert to different node types
		};
	};
}