cortav  Check-in [76fe4885f4]

Overview
Comment:add xref blocks, minor refactors
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 76fe4885f4e440299021f7299bc42f5919f80cdb3d9ccf77b254c50ed29812b8
User & Date: lexi on 2022-09-10 03:11:19
Other Links: manifest | tags
Context
2022-09-10
03:16
defuck xref structure check-in: 0ef3dd0c77 user: lexi tags: trunk
03:11
add xref blocks, minor refactors check-in: 76fe4885f4 user: lexi tags: trunk
01:02
add path class; add URI support for HTML link output check-in: 8c11f3b669 user: lexi tags: trunk
Changes

Modified cortav.ct from [54b40e50db] to [fb6019b4ad].

   118    118   * [`$[$macro] [$arg1]|[$arg2]|[$argn]…] invokes a block-level macro with the supplied arguments, and can be followed by a property override definition list the same way embed and resource lines can. note that while both [`$[$id]] and [`&[$id]] can be used to instantiate resources of type [`text/x.cortav], there is a critical difference: [`$[$id]] renders out the sub-document separately each time it is named, allowing for parameter expansion and for context variables to be overridden for each invocation. by contrast, [`&[$id]] can only insert copies of the same render; no parameters can be passed and context variables will be expanded to their value at the time the resource was defined.
   119    119   ** [`$mymacro arg 1|arg 2|arg 3]
   120    120   * [*horizontal rule] ([`\---]): inserts a horizontal rule or other context break; does not end the section. must be followed by newline. underlines can also be used in place of dashes ([`___], [`-_-], [`__-__-__] etc), as can horizontal unicode box drawing characters ([`─ ━ ┈] etc).
   121    121   * [*page break] ([`\^^]): for formats that support pagination, like EPUB or HTML (when printed), indicates that the rest of the current page should be blank. for formats that do not, extra margins will be inserted. does not create a new section
   122    122   * [*page rule] ([`\^-^]): inserts a page break for formats that support them, and a horizontal rule for formats that do not. does not create a new section. comprised of any number of horizontal rule characters surrounded by a pair of carets (e.g. [`^-^] [`^_^] [`^----^] [`^__--^] [`^┈┈┈┈┈^])
   123    123   * [*table cells] ([`+ |]): see [>ex.tab table examples].
   124    124   * [*equations] ([`=]): block-level equations can be inserted with the [`=] sequence
   125         -* [*cross-references] ([`=>] [`⇒]): inserts a block-level link. uses the same syntax as span links ([`⇒[$ident] [$styled-text]]). can be followed by a caption to add a longer descriptive text. especially useful for gemtext output. ident can be omitted to cross-reference, for example, a physical book.
          125  +* [*cross-references] ([`=>] [`⇒]): inserts a block-level link. has two forms for the sake of gemtext compatibility. [$styled-text] is a descriptive text of the destination. especially useful for menus and gemtext output.
          126  +** the cortav syntax is [`=>[$ident] [$styled-text]], where [$ident] is an identifier; links to the same destination as [` \[>[$ident] [$styled-text]\]] would
          127  +** the compatibility syntax is [`=> [$uri] [$styled-text]] (note the space before [$uri]!). instead of taking an identifier for an object in the document, it directly accepts a URI. note that this is not formally equivalent to gemtext's link syntax, which also allows paths in place of URIs; [`cortav] does not. the gemtext line ["=> /somewhere] would need to be expressed as ["=> file:/somewhere], and ["=> /somewhere?key=val] as ["http:/somewhere?key=val] (or ["gemini:/somewhere?key=val], if the result is to be served over a gemini server).
   126    128   * [*empty lines] (that is, lines consisting of nothing but whitespace) constitute a [!break], which terminates multiline objects that do not have a dedicated termination sequence, for example lists and asides.
   127    129   
   128    130   ##onspans styled text
   129    131   most blocks contain a sequence of spans. these spans are produced by interpreting a stream of [*styled-text] following the control sequence. styled-text is a sequence of codepoints potentially interspersed with escapes. an escape is formed by an open square bracket [`\[] followed by a [*span control sequence], and arguments for that sequence like more styled-text. escapes can be nested.
   130    132   
   131    133   * strong {obj *|styled-text}: causes its text to stand out from the narrative, generally rendered as bold or a brighter color.
   132    134   * emphatic {obj !|styled-text}: indicates that its text should be spoken with emphasis, generally rendered as italics

Modified cortav.lua from [08a1a9b804] to [c03c132cce].

  1028   1028   			flush()
  1029   1029   			buf.header = c == '+'
  1030   1030   		elseif c == ':' then
  1031   1031   			local lst = l:sub(p.byte-#c,p.byte-#c)
  1032   1032   			local nxt = l:sub(p.next.byte,p.next.byte)
  1033   1033   			if lst == '|' or lst == '+' and l:sub(p.byte-2,p.byte-2) ~= '\\' then
  1034   1034   				buf.align = 'left'
  1035         -			elseif nxt == '|' or nxt == '|' then
         1035  +			elseif nxt == '|' or nxt == '+' then
  1036   1036   				if buf.align == 'left' then
  1037   1037   					buf.align = 'center'
  1038   1038   				else
  1039   1039   					buf.align = 'right'
  1040   1040   				end
  1041   1041   			else
  1042   1042   				buf.str = buf.str .. c
................................................................................
  1076   1076   		j:hook('block_table_insert', c, tbl, l)
  1077   1077   		j:hook('block_table_row_insert', c, tbl, tbl.rows[1], l)
  1078   1078   	end
  1079   1079   end
  1080   1080   
  1081   1081   local function insert_link_block(seq)
  1082   1082   	return blockwrap(function(s,c)
  1083         -		local r = s:sub(#seq):gsub('^%s+','') -- chomp
  1084         -		local uri, txt = r:match('^([^%s]*)%s*(.*)$')
         1083  +		local r = s:sub(#seq+1)
         1084  +		local k, uri, txt = r:match('^(%s*)([^%s]*)%s*(.*)$')
  1085   1085   		return {
  1086         -			uri = ss.uri(uri);
  1087         -			label = ct.parse_span(txt, c);
         1086  +			kind = 'link';
         1087  +			uri = (k~='') and ss.uri(uri) or nil;
         1088  +			ref = (k=='') and uri or nil;
         1089  +			spans = ct.parse_span(txt, c);
  1088   1090   		}
  1089   1091   	end)
  1090   1092   end
  1091   1093   
  1092   1094   ct.ctlseqs = {
  1093   1095   	{seq = '.', fn = insert_paragraph};
  1094   1096   	{seq = '¶', fn = insert_paragraph};

Modified render/html.lua from [78b49cf252] to [8ab06e9795].

   401    401   			h1 {
   402    402   				page-break-before: always;
   403    403   			}
   404    404   			h1,h2,h3,h4,h5,h6 {
   405    405   				page-break-after: avoid;
   406    406   			}
   407    407   		]];
          408  +		linkBlock = [[
          409  +			a[href].link {
          410  +				position: relative;
          411  +				display: block;
          412  +				padding: .5em;
          413  +				padding-right: 1.5em;
          414  +				border: 1px solid @tone(0.2 30);
          415  +				background: @tone(0.05 30);
          416  +				font-size: 1.1em;
          417  +				margin: 0 1em;
          418  +				text-decoration: none;
          419  +				color: @tone(0.8 30);
          420  +			}
          421  +			a[href].link + a[href].link {
          422  +				margin-top: -1px;
          423  +			}
          424  +			a[href].link:hover {
          425  +				border-color: @tone(0.3 30);
          426  +				background: @tone(0.2 30);
          427  +				color: @tone(0.95 30);
          428  +			}
          429  +			a[href].link:hover + a[href].link {
          430  +				margin-top: 0;
          431  +				border-top: none;
          432  +			}
          433  +			a[href].link::after {
          434  +				display: block;
          435  +				position: absolute;
          436  +				right: .5em;
          437  +				content: "→";
          438  +				top: 50%;
          439  +				margin-left: 1em;
          440  +				font-size: 1.8em;
          441  +				transform: translateY(-50%);
          442  +				color: @tone(0.3 30);
          443  +			}
          444  +			a[href].link:hover::after {
          445  +				color: @tone(0.7 30);
          446  +			}
          447  +		]];
   408    448   	}
   409    449   
   410    450   	local stylesNeeded = {
   411    451   		flags = {};
   412    452   		order = {};
   413    453   	}
   414    454   	local function addStyle(sty)
................................................................................
   447    487   
   448    488   	local renderJob = doc:job('render_html', nil, render_state_handle)
   449    489   	doc.stage.job = renderJob;
   450    490   
   451    491   	local runhook = function(h, ...)
   452    492   		return renderJob:hook(h, render_state_handle, ...)
   453    493   	end
          494  +
          495  +	local function htmlURI(uri)
          496  +		local family = uri:canfetch()
          497  +		if family == 'file' then
          498  +			if uri.namespace == 'localhost' then
          499  +				-- emit an actual file url
          500  +				return 'file://' .. uri:construct('path','frag')
          501  +			elseif uri.namespace == nil then
          502  +				-- this is gonna be tricky. first we establish the location
          503  +				-- of the CWD/asset base relative to the output file (if any;
          504  +				-- assume equivalent otherwise) then express the difference
          505  +				-- as a directory prefix.
          506  +				-- jk tho for now we just emit the path+frag sadlol TODO
          507  +				if uri.path == nil and uri.frag then
          508  +					-- file:#sec links to #sec within the current document
          509  +					return uri:part 'frag'
          510  +				else
          511  +					return uri:construct('path','frag')
          512  +				end
          513  +			else
          514  +				b.origin:fail('file: URI namespace must be empty or “localhost” for HTML links; others are not meaningful (offending URI: “%s”)', uri.raw)
          515  +			end
          516  +		elseif family == 'http' then
          517  +			local sc = 'http'
          518  +			if uri.class[1] == 'https' or uri.class[2] == 'tls' then
          519  +				sc = 'https'
          520  +			end
          521  +			if uri.namespace == nil and uri.auth == nil and uri.svc == nil then
          522  +				-- omit the scheme so we can use a relative path
          523  +				return uri:construct('path','query','frag')
          524  +			else
          525  +				uri.class = {sc}
          526  +				return tostring(uri)
          527  +			end
          528  +		else return tostring(uri) end
          529  +	end
          530  +
          531  +	local function idLink(id,b)
          532  +		local dest_o, _, dest_s = b.origin:ref(id)
          533  +		if dest_o == nil then
          534  +			-- link is to the section itself
          535  +			return '#' .. getSafeID(dest_s)
          536  +		else
          537  +			if type(dest_o) == 'table' then
          538  +				return '#' .. getSafeID(dest_o)
          539  +			else -- URI in reference
          540  +				return htmlURI(ss.uri(dest_o))
          541  +			end
          542  +		end
          543  +	end
   454    544   
   455    545   	local tagproc do
   456    546   		local html_open = function(t,attrs)
   457    547   			if attrs then
   458    548   				return t .. ss.reduce(function(a,b) return a..b end, '',
   459    549   					ss.map(function(v,k)
   460    550   						if v == true
................................................................................
   611    701   		end
   612    702   
   613    703   		function span_renderers.raw(v,b,s)
   614    704   			return htmlSpan(v.spans, b, s)
   615    705   		end
   616    706   
   617    707   		function span_renderers.link(sp,b,s)
   618         -			local dest_o, _, dest_s = b.origin:ref(sp.ref)
   619         -			local href
   620         -			if dest_o == nil then
   621         -				-- link is to the section itself
   622         -				href = '#' .. getSafeID(dest_s)
   623         -			else
   624         --- 				if sp.addr then href = sp.addr else
   625         -				if type(dest_o) == 'table' then
   626         -					href = '#' .. getSafeID(dest_o)
   627         -				else -- URI in reference
   628         -					local uri = ss.uri(dest_o)
   629         -					if uri.class[1] == 'file'
   630         -					or uri.class[1] == 'asset' then
   631         -						if uri.namespace == 'localhost' then
   632         -							-- emit an actual file url
   633         -							href = 'file://' .. uri:construct('path','frag')
   634         -						elseif uri.namespace == nil then
   635         -							-- this is gonna be tricky. first we establish the location
   636         -							-- of the CWD/asset base relative to the output file (if any;
   637         -							-- assume equivalent otherwise) then express the difference
   638         -							-- as a directory prefix.
   639         -							-- jk tho for now we just emit the path+frag sadlol TODO
   640         -							href = uri:construct('path','frag')
   641         -						else
   642         -							b.origin:fail('file: URI namespace must be empty or “localhost” for HTML links; others are not meaningful (offending URI: “%s”)', dest_o)
   643         -						end
   644         -					elseif uri:canfetch() == 'http' then
   645         -						local sc = 'http'
   646         -						if uri.class[1] == 'https' or uri.class[2] == 'tls' then
   647         -							sc = 'https'
   648         -						end
   649         -						if uri.namespace == nil and uri.auth == nil and uri.svc == nil then
   650         -							-- omit the scheme so we can use a relative path
   651         -							href = uri:construct('path','query','frag')
   652         -						else
   653         -							uri.class = {sc}
   654         -							href = tostring(uri)
   655         -						end
   656         -					else href = tostring(uri) end
   657         -				end
   658         -			end
   659         -			return tag('a',{href=href},next(sp.spans) and htmlSpan(sp.spans,b,s) or href)
          708  +			local href = idLink(sp.ref,b)
          709  +			return tag('a',{href=href}, next(sp.spans) and htmlSpan(sp.spans,b,s) or href)
   660    710   		end
   661    711   
   662    712   		span_renderers['line-break'] = function(sp,b,s)
   663    713   			return elt('br')
   664    714   		end
   665    715   
   666    716   		function span_renderers.macro(m,b,s)
................................................................................
   773    823   				else
   774    824   					-- handle other uses of labels here
   775    825   				end
   776    826   			end;
   777    827   			['list-item'] = function(b,s)
   778    828   				return tag('li', nil, sr.htmlSpan(b.spans, b, s), b)
   779    829   			end;
          830  +			link = function(b,s)
          831  +				addStyle 'linkBlock'
          832  +				local href
          833  +				if b.uri then
          834  +					href = htmlURI(b.uri)
          835  +				elseif b.ref then
          836  +					href = idLink(b.ref, b)
          837  +				end
          838  +				local sp = sr.htmlSpan(b.spans, b, s)
          839  +				return tag('div', {},
          840  +					catenate{tag('a',{class='link', href=href},sp)})
          841  +			end;
   780    842   			table = function(b,s)
   781    843   				local tb = {}
   782    844   				for i, r in ipairs(b.rows) do
   783    845   					local row = {}
   784    846   					for i, c in ipairs(r) do
   785    847   						table.insert(row, tag(c.header and 'th' or 'td',
   786    848   						{align=c.align}, sr.htmlSpan(c.spans, b)))
................................................................................
   863    925   
   864    926   		function block_renderers.embed(b,s)
   865    927   			local obj
   866    928   			if b.rsrc
   867    929   				then obj = b.rsrc
   868    930   				else obj = b.origin:ref(b.ref)
   869    931   			end
   870         -			local function htmlURI(u)
   871         -				local family = u:canfetch()
   872         -				if  family == 'file' or
   873         -					(family == 'http' and u.namespace == nil) then
   874         -					-- TODO asset:
   875         -					return u.path
   876         -				else
   877         -					return tostring(u)
   878         -				end
   879         -			end
   880    932   			local function uriForSource(s)
   881    933   				if s.mode == 'link' or s.mode == 'auto' then
   882    934   					return htmlURI(s.uri)
   883    935   				elseif s.mode == 'embed' then
   884    936   					local mime = s.mime:clone()
   885    937   					mime.opts = {}
   886    938   					return string.format('data:%s;base64,%s', mime, ss.str.b64e(s.raw))