├── .gitignore ├── LICENSE ├── README.md ├── _extensions └── custom-numbered-blocks │ ├── _extension.yml │ ├── custom-numbered-blocks.lua │ ├── custom-numbered-blocks.lua.bak │ └── style │ ├── foldbox.css │ ├── foldbox.lua │ └── foldbox.tex ├── doc ├── example.html ├── example.pdf └── example_files │ └── libs │ ├── bootstrap │ ├── bootstrap-icons.css │ ├── bootstrap-icons.woff │ ├── bootstrap.min.css │ └── bootstrap.min.js │ ├── clipboard │ └── clipboard.min.js │ ├── quarto-contrib │ └── foldbox │ │ └── foldbox.css │ └── quarto-html │ ├── anchor.min.js │ ├── popper.min.js │ ├── quarto-syntax-highlighting.css │ ├── quarto.js │ ├── tippy.css │ └── tippy.umd.min.js └── example.qmd /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.pdf 3 | !doc/*.* 4 | *_files/ 5 | !doc/*_files/ 6 | *.json 7 | .Rproj.user 8 | /.luarc.json 9 | list-of-*.qmd 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ute Hahn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Numbered Blocks Extension for Quarto 2 | 3 | This extension provides user defined custom div classes (environments) that come with numbering, such as theorems, examples, exercises. Numbered blocks can be cross referenced. 4 | 5 | - By default, the div's text block is enclosed in a collapsible box, similar to quarto callouts. 6 | - Groups of classes can be defined that share style and numbering. 7 | - Lists-of-classes can be extracted, such as a list of all theorems. It is also possible to generate a list for a group of classes. 8 | 9 | The filter supports output formats pdf and html. 10 | 11 | 12 | ![image](https://github.com/ute/custom-numbered-blocks/assets/5145859/8b69f761-fcf8-44fe-b2ee-2626f59548c9) 13 | 14 | ## Status 15 | 16 | Seems that Quarto 1.3 handles pdf books differently from Quarto 1.2. If chapters contain additional level 1 heading, this messes up numbering in Quarto 1.3 pdf books. I will likely fix that soon. 17 | 18 | There may come changes to the yaml-UI for lists-of-classes, also soon ;-). 19 | 20 | 21 | ## Installing 22 | 23 | ```bash 24 | quarto add ute/custom-numbered-blocks 25 | ``` 26 | 27 | This will install the extension under the `_extensions` subdirectory. 28 | If you're using version control, you will want to check in this directory. 29 | 30 | ## Using 31 | 32 | Usage is illustrated more comprehensively in `example.qmd`. 33 | 34 | ### Defining and using a user defined class 35 | To specify a new class of numbered div blocks, `Example`, say, add yaml code: 36 | ```yaml 37 | custom-numbered-blocks: 38 | classes: 39 | Example: default 40 | ``` 41 | Use the class in a fenced dive. Title can be provided as a header immediately after div. 42 | ``` 43 | ::: Example 44 | ### the best possible example, ever 45 | here is some exemplary text 46 | ::: 47 | ``` 48 | 49 | ### Change default options for a class 50 | The default style renders as a collapsible box with title button, similar to quarto callouts. It comes with a small close button bottom right. You can change the following options in yaml or individually in the div specification: 51 | - `colors` : an array of two hex rgb color codes, for title button color and frame color. `colors: [a08080, 500000]` would give a pink title button and dark red frame. 52 | - `collapse`: boolean, default `true`. Initial state of the collapsible box. 53 | - `label`: the label to print before the number (string). 54 | - `boxstyle`: set to `foldbox.simple` for a boxed environment without close button. There will quite likely come more options in a future version. 55 | - `listin`: register for a [list-of](#lists-of-listin-version) 56 | 57 | ### Groups of classes with joint counter and joint default style 58 | Jointly counted block classes are specified by yaml option `groups`. These groups can also have a common default style. For each class, option `group` indicates membership. 59 | 60 | **Example**: we want to jointly count theorems, lemmas and propositions, and render boxes with initially open status, but propositions should be collapsed: 61 | ```yaml 62 | custom-numbered-blocks: 63 | groups: 64 | thmlike: 65 | collapse: false 66 | classes: 67 | Theorem: 68 | group: thmlike 69 | Proposition: 70 | group: thmlike 71 | collapse: true 72 | Lemma: 73 | group: thmlike 74 | ``` 75 | 76 | ### Lists-of ("listin" version) 77 | To generate a list of all divs belonging to a class, `Example`, say, add key listin to the class and give the name of the list. The same can be done for groups of classes. This will produce a file `list-of-`name`.qmd` that contains headers and references to the respective blocks. The following code will generage files `list-of-allthingsmath.qmd` and `list-of-examples.qmd`: 78 | 79 | ```yaml 80 | custom-numbered-blocks 81 | groups: 82 | thmlike: 83 | collapse: false 84 | listin: [allthingsmath] 85 | Example: 86 | listin: [examples, allthingsmath] 87 | ``` 88 | 89 | ## Example 90 | 91 | Here is the source code for a (not so) minimal example: [example.qmd](https://ute.github.io/custom-numbered-blocks/example.qmd). And here's the rendered [example.html](https://ute.github.io/custom-numbered-blocks/doc/example.html) and [example.pdf](https://ute.github.io/custom-numbered-blocks/doc/example.pdf) 92 | 93 | ## Limitations 94 | - References to bibliography in the title are not resolved, see [this issue by ntq2022](https://github.com/ute/custom-numbered-blocks/issues/7). This is due to the sequence of processing references. Pull requests are welcome - I am not sure 95 | if I will have time to dig into this in the nearer future. 96 | - Cross-reference labels that are interpretable for Quarto, such as labels starting with `thm-` or `fig-`, cannot be used with this extension, since they will be processed by Quarto. This results in unexpected output, see [this issue by gyu-eun-lee](https://github.com/ute/custom-numbered-blocks/issues/8). 97 | 98 | ## Workarounds and precautions to avoid clashes with other extensions 99 | - If you use [parse-latex](https://github.com/tarleb/parse-latex), make sure that custom-numbered-blocks comes first in the filter pipeline to process LaTeX references (`\ref`). 100 | - Further headers within custom numbered blocks will mess up indentation of paragraphs following that block. To avoid that, include headers in a div, for example 101 | ```markdown 102 | ::: {.myblock} 103 | ### heading of my custom block 104 | blabla 105 | 106 | ::::{} 107 | ### new header 108 | :::: 109 | other stuff 110 | ``` 111 | -------------------------------------------------------------------------------- /_extensions/custom-numbered-blocks/_extension.yml: -------------------------------------------------------------------------------- 1 | title: Custom-numbered-blocks 2 | author: Ute Hahn 3 | version: 0.1.3 4 | quarto-required: ">=1.2.0" 5 | contributes: 6 | filters: 7 | - custom-numbered-blocks.lua 8 | 9 | -------------------------------------------------------------------------------- /_extensions/custom-numbered-blocks/custom-numbered-blocks.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | MIT License 3 | 4 | Copyright (c) 2023 Ute Hahn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ]]-- 24 | -- pre-pre-release 25 | -- 26 | -- partial rewrite, complete later 27 | 28 | -- nice rename function learned from shafayetShafee :-) 29 | local str = pandoc.utils.stringify 30 | local pout = quarto.log.output 31 | 32 | -- important quasi global variables 33 | 34 | local ishtml = quarto.doc.is_format("html") 35 | local ispdf = quarto.doc.is_format("pdf") 36 | local fmt="" 37 | if ishtml then fmt="html" elseif ispdf then fmt = "pdf" end 38 | 39 | -- TODO encapsulate stylez into a doc thing or so 40 | -- maybe later allow various versions concurrently. 41 | -- this could probably be problematic because of option clashes in rendered header? 42 | 43 | local stylename="foldbox" 44 | 45 | local stylez = require("style/"..stylename) 46 | 47 | 48 | --- TODO: better encapsulation (luxury :-P) 49 | 50 | fbx={ -- global table, holds information for processing fboxes 51 | xreffile = "._xref.json" -- default name, set to lastfile in initial meta analysis 52 | } 53 | 54 | 55 | -- utility functions --- 56 | local function DeInline(tbl) 57 | local result ={} 58 | for i, v in pairs(tbl) do 59 | pdtype = pandoc.utils.type(v) 60 | if pdtype == "Inlines" or pdtype =="boolean" 61 | then 62 | result[i] = str(v) 63 | elseif pdtype == "List" then result[i] = DeInline(v) 64 | end 65 | end 66 | return(result) 67 | end 68 | 69 | local function tablecontains(tbl, val) 70 | if tbl ~= nil and val ~= nil then 71 | for _, v in ipairs(tbl) do 72 | if val == v then return true end 73 | end 74 | end 75 | return false 76 | end 77 | 78 | local function ifelse(condition, iftrue, iffalse) 79 | if condition then return iftrue else return iffalse end 80 | end 81 | 82 | local function replaceifnil(existvalue, replacevalue) 83 | if existvalue ~= nil then return existvalue else return replacevalue end 84 | end 85 | 86 | local function replaceifempty(existvalue, replacevalue) 87 | if existvalue == nil or existvalue=="" then return replacevalue else return existvalue end 88 | end 89 | 90 | 91 | local function updateTable (oldtbl, newtbl, ignorekeys) 92 | local result = {} 93 | -- copy old attributes 94 | for k, v in pairs(oldtbl) do result[k] = v end 95 | if newtbl ~= nil then if type(newtbl) == "table" then 96 | if newtbl[1] == nil then -- it is an ok table with key value pairs 97 | for k, v in pairs(newtbl) do 98 | if not(tablecontains(ignorekeys, k)) then 99 | result[k] = v 100 | end 101 | end 102 | -- special: set reflabel to label if not given in attribs 103 | -- if newattribs["reflabel"] == nil then result.reflabel = result.label end 104 | -- TODO: do this elsewhere 105 | end 106 | end 107 | end 108 | return(result) 109 | end 110 | 111 | 112 | 113 | ---- init step --------------------------- 114 | --- init = require ("fbx1") 115 | 116 | -- find chapter number and file name 117 | -- returns a table with keyed entries 118 | -- processedfile: string, 119 | -- ishtmlbook: boolean, 120 | -- chapno: string (at least if ishtmlbook), 121 | -- unnumbered: boolean - initial state of section / chapter 122 | -- if the user has given a chapno yaml entry, then unnumbered = false 123 | 124 | -- !!! for pdf, the workflow is very different! --- 125 | -- also find out if lastfile of a book 126 | 127 | -- find first and last file of a book, and chapter number of that file 128 | local function chapterinfo(book, fname) 129 | local first = "" 130 | local last = "" 131 | local chapno = nil 132 | local info = {} 133 | --if book.render then 134 | for _, v in pairs(book.render) do 135 | if str(v.type) == "chapter" then 136 | last = pandoc.path.split_extension(str(v.file)) 137 | if first == "" then first = last end 138 | if last == fname then chapno = v.number end 139 | end 140 | end 141 | info.islast = (fname == last) 142 | info.isfirst = (fname == first) 143 | info.lastchapter = last 144 | info.chapno = chapno 145 | return(info) 146 | end 147 | 148 | local function Meta_findChapterNumber(meta) 149 | local processedfile = pandoc.path.split_extension(PANDOC_STATE.output_file) 150 | fbx.isbook = meta.book ~= nil 151 | fbx.ishtmlbook = meta.book ~= nil and not quarto.doc.is_format("pdf") 152 | fbx.processedfile = processedfile 153 | 154 | fbx.output_file = PANDOC_STATE.output_file 155 | -- pout(" now in "..processedfile.." later becomes ".. str(fbx.output_file)) 156 | 157 | fbx.isfirstfile = not fbx.ishtmlbook 158 | fbx.islastfile = not fbx.ishtmlbook 159 | if fbx.isbook then 160 | local chinfo = chapterinfo(meta.book, processedfile) 161 | if fbx.ishtmlbook then 162 | fbx.xreffile= "._htmlbook_xref.json" 163 | else 164 | fbx.xreffile= "._pdfbook_xref.json" 165 | -- fbx.xreffile= "._"..chinfo.lastchapter.."_xref.json" 166 | end 167 | fbx.isfirstfile = chinfo.isfirst 168 | fbx.islastfile = chinfo.islast 169 | 170 | fbx.unnumbered = false 171 | -- user set chapter number overrides information from meta 172 | if meta.chapno then 173 | fbx.chapno = str(meta.chapno) 174 | else 175 | if chinfo.chapno ~= nil then 176 | fbx.chapno = str(chinfo.chapno) 177 | else 178 | fbx.chapno = "" 179 | fbx.unnumbered = true 180 | end 181 | end 182 | else -- not a book. 183 | fbx.xreffile ="._"..processedfile.."_xref.json" 184 | fbx.chapno = "" 185 | fbx.unnumbered = true 186 | end 187 | end 188 | 189 | local function makeKnownClassDetector(knownclasses) 190 | return function(div) 191 | for _, cls in pairs(div.classes) do 192 | if tablecontains(knownclasses, cls) then return str(cls) end 193 | end 194 | return nil 195 | end 196 | end 197 | 198 | local function Meta_initClassDefaults (meta) 199 | -- do we want to prefix fbx numbers with section numbers? 200 | local cunumbl = meta["custom-numbered-blocks"] 201 | fbx.knownclasses = {} 202 | fbx.lists = {} 203 | --[[ TODO later 204 | if meta.fbx_number_within_sections then 205 | fbx.number_within_sections = meta.fbx_number_within_sections 206 | else 207 | fbx.number_within_sections = false 208 | end 209 | --]] 210 | -- prepare information for numbering fboxes by class 211 | -- fbx.knownClasses ={} 212 | fbx.classDefaults ={} 213 | local groupDefaults = {default = stylez.defaultOptions} -- not needed later 214 | fbx.counter = {unnumbered = 0} -- counter for unnumbered divs 215 | -- ! unnumbered not for classes that have unnumbered as default ! 216 | -- fbx.counterx = {} 217 | if cunumbl.classes == nil then 218 | print("== @%!& == Warning == &!%@ ==\n wrong format for fboxes yaml: classes needed") 219 | return 220 | end 221 | 222 | -- simplified copy of yaml data: inlines to string 223 | if cunumbl.groups then 224 | for key, val in pairs(cunumbl.groups) do 225 | local ginfo = DeInline(val) 226 | --[[ 227 | pout("==== before after ======="); pout(ginfo) 228 | if ginfo.boxstyle then 229 | local mainstyle, substyle = ginfo.boxstyle:match "([^.]*).(.*)" 230 | -- pout("main "..mainstyle.." :: "..substyle) 231 | -- TODO: here account for multiple styles 232 | end 233 | --]]-- 234 | ginfo = updateTable(stylez.defaultOptions, ginfo) 235 | --fbx. 236 | groupDefaults[key] = ginfo 237 | -- pout("-----group---"); pout(ginfo) 238 | end 239 | end 240 | for key, val in pairs(cunumbl.classes) do 241 | local clinfo = DeInline(val) 242 | -- pout("==== before after ======="); pout(clinfo) 243 | -- classinfo[key] = DeInline(val) 244 | table.insert(fbx.knownclasses, str(key)) 245 | local theGroup = replaceifnil(clinfo.group, "default") 246 | clinfo = updateTable(groupDefaults[theGroup], clinfo) 247 | clinfo.label = replaceifnil(clinfo.label, str(key)) 248 | clinfo.reflabel = replaceifnil(clinfo.reflabel, clinfo.label) 249 | -- assign counter -- 250 | clinfo.cntname = replaceifnil(clinfo.group, str(key)) 251 | fbx.counter[clinfo.cntname] = 0 -- sets the counter up if non existing 252 | fbx.classDefaults[key] = clinfo 253 | -- pout("---class----"); pout(clinfo) 254 | end 255 | fbx.is_cunumblo = makeKnownClassDetector(fbx.knownclasses) 256 | -- gather lists-of and make filenames by going through all classes 257 | for _, val in pairs(fbx.classDefaults) do 258 | -- pout("--classdefault: "..str(key)) 259 | -- pout(val) 260 | if val.listin then 261 | for _,v in ipairs(val.listin) do 262 | fbx.lists[v] = {file = "list-of-"..str(v)..".qmd"} 263 | end 264 | end 265 | end 266 | -- initialize lists 267 | for key, val in pairs(fbx.lists) do 268 | val.contents = ifelse(fbx.isfirstfile, "\\providecommand{\\Pageref}[1]{\\hfill p.\\pageref{#1}}", "") 269 | -- listin approach does not require knownclass, since listin is in classdefaults 270 | end 271 | -- pout(fbx.lists) 272 | --]] 273 | -- document can give the chapter number for books in yaml header 274 | -- this becomes the counter Prefix 275 | end 276 | 277 | local initMeta = function(m) 278 | if m["custom-numbered-blocks"] then 279 | Meta_findChapterNumber(m) 280 | Meta_initClassDefaults(m) 281 | else 282 | print("== @%!& == Warning == &!%@ ==\n missing cunumblo key in yaml") 283 | end 284 | return(m) 285 | end 286 | 287 | ----------------------- oldcode, mostly ------------------------------------- 288 | 289 | 290 | ------- numbering and class attributes ------ 291 | 292 | local function fboxDiv_setAttributes(el, cls, prefix) 293 | local ela = el.attributes -- shortcut 294 | local ClassDef = fbx.classDefaults[cls] 295 | --local unnumbered = ClassDef.numbered == "false" 296 | local numbered = ClassDef.numbered ~= "false" 297 | local tag = ela.tag 298 | local tagged = tag ~= nil 299 | local id = el.identifier 300 | local autoid ="" 301 | -- local titl = ela.title 302 | local cntkey = ClassDef.cntname 303 | local counter = {} 304 | local cnts = 0 305 | local idnumber = "0.0" 306 | 307 | -- set prefix 308 | ela._prefix = prefix 309 | 310 | id = replaceifnil(id ,"") 311 | tag = replaceifnil(tag ,"") 312 | 313 | --- determine if numbered and / or tagged ------ 314 | 315 | if tagged then numbered = false end 316 | if el.classes:includes("unnumbered") then numbered = false end 317 | 318 | if ela.numtag then 319 | tag = ela.numtag 320 | -- print("!!! also hier mal ein numtag.\n") 321 | numbered = true 322 | tagged = true 323 | end 324 | 325 | -- make counts --- 326 | 327 | if not numbered then cntkey = "unnumbered" end 328 | 329 | cnts = fbx.counter[cntkey] +1 330 | fbx.counter[cntkey] = cnts 331 | 332 | idnumber = ifelse(prefix ~= "", prefix .. '.' .. cnts, str(cnts)) 333 | --[[ 334 | if prefix ~="" then idnumber = prefix .. '.' .. cnts 335 | else idnumber = str(cnts) 336 | end 337 | 338 | if numbered then 339 | if not tagged then tag = idnumber 340 | else tag = idnumber.."("..tag..")" 341 | end 342 | end 343 | ]]-- 344 | if numbered then tag = idnumber..ifelse(tagged, "("..tag..")", "" ) end 345 | 346 | if id == "" then 347 | if numbered then 348 | autoid = ela._fbxclass..'-'..tag 349 | else 350 | autoid = ela._fbxclass..'*-'..idnumber 351 | end 352 | -- changed my mind here: always give autoid 353 | else autoid = ela._fbxclass..'-id-'..id 354 | end 355 | 356 | -- do not change identifier el.identifier = id 357 | 358 | ela._autoid = autoid 359 | 360 | ela._tag = tag 361 | 362 | ela._file = fbx.processedfile -- necessary to reference between chapters. At least with quarto 1.3 363 | -- pout("tag: "..tag) 364 | -- pout(ela) 365 | return(el) 366 | end 367 | 368 | -- initial attributes without prefix and counts to allow for inner boxes 369 | 370 | local function fboxDiv_mark_for_processing(div) 371 | local diva=div.attributes 372 | local cls = fbx.is_cunumblo(div) 373 | local ClassDef = fbx.classDefaults[cls] 374 | if(cls) then 375 | diva._process_me = "true" 376 | diva._fbxclass = str(cls) 377 | diva._prefix = "" 378 | diva._tag = "" 379 | diva._collapse = str(replaceifnil(diva.collapse, ClassDef.collapse)) 380 | diva._boxstyle = str(replaceifnil(diva.boxstyle, ClassDef.boxstyle)) 381 | diva._label = str(replaceifnil(diva.label, ClassDef.label)) 382 | diva._reflabel = str(replaceifnil(diva.reflabel, ClassDef.reflabel)) 383 | end 384 | return(div) 385 | end 386 | 387 | 388 | 389 | local function Pandoc_prefix_count(doc) 390 | -- do evt later: non numeric chapernumbers 391 | local secno = 0 392 | local prefix = "0" 393 | if fbx.ishtmlbook then prefix = fbx.chapno end 394 | 395 | -- pout("this is a book?"..str(fbx.ishtmlbook)) 396 | 397 | --- make numbering and prep div blocks --- 398 | --[[------- comment ----------- 399 | quarto (1.2) books allow level 1 headings within a chapter. 400 | This would give a mess for crossreference numbers: e.g. multiple examples 3.1, 401 | from chapter 1 (with 2 l1 headers ) and chapter 3. 402 | Therefore I decided to ignore level 1 headings in chapters. 403 | This can easily be changed, then the crossref is for the last occurence only. 404 | Maybe one day when there is more fine tuning concerning individual numbering depth. 405 | If this happens before quarto 1.4 406 | 407 | --]]---------- end comment ------------ 408 | for i, blk in ipairs(doc.blocks) do 409 | -- print(prefix.."-"..i.." "..blk.t.."\n") 410 | 411 | if blk.t=="Header" and not fbx.ishtmlbook then 412 | if (blk.level == 1) then -- increase prefix 413 | if blk.classes:includes("unnumbered") 414 | then 415 | prefix = "" 416 | else 417 | if blk.attr.attributes.secno then 418 | 419 | prefix = str(blk.attr.attributes.secno) 420 | else 421 | secno = secno + 1 422 | prefix = str(secno) 423 | end 424 | end 425 | -- reset counters in fbx -- 426 | -- this would be more complicated if there are different levels 427 | -- of numbering depth 428 | -- then: add a numdepth variable to fbx with a list of keys 429 | for k in pairs(fbx.counter) do fbx.counter[k]=0 end 430 | end 431 | 432 | -- problem: only the outer divs are captured 433 | elseif blk.t=="Div" then 434 | local known = fbx.is_cunumblo(blk) 435 | if known then 436 | blk = fboxDiv_setAttributes(blk, known, prefix) 437 | end 438 | end 439 | end 440 | return(doc) 441 | end 442 | 443 | -- if no id, get from first included header, if possible 444 | local function Divs_getid(el) 445 | -- local known = getKnownEnv(el.attr.classes) 446 | local ela = el.attributes 447 | local id = el.identifier 448 | 449 | if not ela._process_me then return(el) end 450 | -- pout("--- processing item with id ".. replaceifempty(id, "LEER")) 451 | 452 | if id == nil or id =="" then 453 | -- try in next header 454 | el1 = el.content[1] 455 | if el1.t=="Header" then 456 | -- pout("--- looking at header with id "..el1.identifier) 457 | -- pout("--- still processing item with id ".. replaceifempty(id, "LEER")) 458 | -- pout("replacing id") 459 | id = el1.identifier 460 | el.identifier = id 461 | end 462 | end 463 | if id == nil or id =="" 464 | then 465 | -- pout("immer noch leer") 466 | if ela._autoid ~= nil then 467 | id = ela._autoid 468 | el.identifier = id 469 | end 470 | --else pout("nix autoid in ");pout(ela._autoid) 471 | end 472 | -- pout("resulting el:"); pout(el.attr) 473 | return(el) 474 | end 475 | 476 | 477 | --- utility function: stringify and sanitize math, depends on format --- 478 | local function str_sanimath(theInline, fmt) 479 | local newstring = theInline:walk{ 480 | Math = function(ma) 481 | local mathtxt = str(ma.text) 482 | if fmt == "html" then 483 | return {'\\(' .. mathtxt .. '\\)'} 484 | elseif fmt == "pdf" then 485 | return {'\\(' .. mathtxt .. '\\)'} 486 | elseif fmt == "md" then 487 | return {'$' .. mathtxt .. '$'} 488 | else return {mathtxt} 489 | end 490 | end 491 | } 492 | return str(newstring) 493 | end 494 | 495 | 496 | ----------- title of divs ---------------- 497 | local function Divs_maketitle(el) 498 | -- local known = getKnownEnv(el.attr.classes) 499 | local ela = el.attributes 500 | local titl = ela.title 501 | local mdtitl = replaceifnil(ela.title, "") 502 | local ClassDef = {} 503 | -- local id = el.identifier 504 | 505 | if not ela._process_me then return(el) end 506 | -- pout("--- processing item with id ".. replaceifempty(el.identifier, "LEER")) 507 | 508 | ClassDef = fbx.classDefaults[ela._fbxclass] 509 | 510 | if titl == nil then 511 | el1 = el.content[1] 512 | if el1.t=="Header" then 513 | -- sanitize math inline. depends on format 514 | ela.title = str(el1.content) -- readable version without math 515 | mdtitl = str_sanimath(el1.content, "md") 516 | if ishtml then titl = str_sanimath(el1.content, "html") 517 | elseif ispdf then titl = str_sanimath(el1.content, "pdf") 518 | else titl = mdtitl 519 | end 520 | -- pout("--- looking at header with id "..el1.identifier) 521 | -- pout("--- still processing item with id ".. replaceifempty(id, "LEER")) 522 | --[[ 523 | if id =="" or id == nil then 524 | pout("replacing id") 525 | id = el1.identifier 526 | el.identifier = id 527 | end 528 | ]]-- 529 | table.remove(el.content, 1) 530 | else titl = "" 531 | end 532 | end 533 | ela._title = titl -- keep the title as attribute for pandoc 534 | ela._mdtitle = mdtitl -- for list of 535 | -- replace empty identifier with autoid 536 | -- if el.identifier == "" then el.identifier = ela._autoid end 537 | -- pout("--> sanitarer titel: "..mdtitl) 538 | -- ela._tag = "" 539 | -- pout("resulting el:"); pout(el) 540 | return(el) 541 | end 542 | 543 | ---------------- initialize xref ---------- 544 | -- xrefinit = require ("fbx3") 545 | 546 | -- xref split into prepare and finalize to allow xref in titles (future) 547 | local function Pandoc_preparexref(doc) 548 | -- local xref={} 549 | local id = "" 550 | local cnt = 0 551 | local bla={} 552 | local xinfo={} 553 | local file_autoid = {} 554 | local exists = false 555 | if fbx.xref == nil then fbx.xref ={} end 556 | xref = fbx.xref 557 | cnt = #xref 558 | if cnt > 0 then 559 | for i, xinf in ipairs(xref) do 560 | file_autoid[xinf.file..xinf.autoid] = i 561 | end 562 | -- pout(autoids) 563 | end 564 | for _, blk in ipairs(doc.blocks) do 565 | if blk.attributes then 566 | bla = blk.attributes 567 | if bla._process_me then 568 | --pout("fbox "..blk.attributes._tag) 569 | ------------- an fbox :) ------------ 570 | if blk.identifier == nil then id = "" 571 | else id = blk.identifier end 572 | xinfo = { 573 | id = id, 574 | autoid = bla._autoid, 575 | cls = bla._fbxclass, 576 | label = bla._label, 577 | reflabel = bla._reflabel, 578 | reftag = bla._tag, 579 | refnum = replaceifempty(bla._tag, "??"), 580 | file = pandoc.path.split_extension(fbx.output_file) 581 | } 582 | -- if not xinfo.reftag then xinfo.reftag ="" end 583 | -- if xinfo.refnum == "" then xinfo.refnum ="??" end 584 | --[[ 585 | if bla._tag 586 | then 587 | if bla._tag ~="" then xinfo.refnum = bla._tag else xinfo.reftag="??" end 588 | end 589 | ]]-- 590 | --- check if autoid already exist in database. otherwise update 591 | oldxrefno = file_autoid[xinfo.file..xinfo.autoid] 592 | if oldxrefno == nil then 593 | cnt = cnt+1 594 | bla._xrefno = cnt 595 | table.insert (xref, cnt, xinfo) 596 | else 597 | bla._xrefno = oldxrefno 598 | xref[oldxrefno] = xinfo 599 | end 600 | end 601 | end 602 | end 603 | return(doc) 604 | end 605 | 606 | 607 | local function Pandoc_finalizexref(doc) 608 | xref = fbx.xref -- shortcut 609 | local bla = {} 610 | -- pout("------- finale ----") 611 | for i, blk in ipairs(doc.blocks) do 612 | bla = blk.attributes 613 | --pout(bla) 614 | if bla then 615 | if bla._process_me == "true" then 616 | ------------- an fbox :) ------------ 617 | xindex = tonumber(bla._xrefno) 618 | if xindex then 619 | xref[xindex].neu = true -- mark as new entry 620 | if bla._title then xref[xindex].title = bla._title end 621 | end 622 | 623 | -- else pout("ochje.") 624 | end 625 | end 626 | end 627 | -- pout(xref) 628 | --- write to disc -- 629 | --- check if this was the last file to process --- 630 | return(doc) 631 | end 632 | 633 | 634 | local function Meta_writexref(meta) 635 | local xref = fbx.xref 636 | local xrjson = quarto.json.encode(fbx.xref) 637 | local file = io.open(fbx.xreffile,"w") 638 | if file ~= nil then 639 | file:write(xrjson) 640 | file:close() 641 | end 642 | if fbx.islastfile then 643 | -- pout(fbx.processedfile.." -- nu aufräum! aber zack ---") 644 | for i, v in ipairs(xref) do 645 | if not v.neu then 646 | -- pout("killed") 647 | -- pout(v) 648 | xref[i] = nil 649 | end 650 | end 651 | -- pout("-------- überlebende") 652 | -- pout(xref) 653 | end 654 | end 655 | 656 | 657 | local function Meta_readxref(meta) 658 | local file = io.open(fbx.xreffile,"r") 659 | if file then 660 | local xrfjson = file:read "*a" 661 | file:close() 662 | --[[ 663 | if xrfjson then meta.fbx.xref = quarto.json.decode(xrfjson) 664 | else meta.fbx.xref = {} end 665 | -- pout("eingelesen") 666 | -- pout(meta.fbx.xref) 667 | else meta.fbx.xref ={} 668 | --]]-- 669 | if xrfjson then fbx.xref = quarto.json.decode(xrfjson) 670 | else fbx.xref = {} end 671 | -- pout("eingelesen") 672 | --pout(fbx.xref) 673 | else fbx.xref ={} 674 | end 675 | return(meta) 676 | end 677 | -------------- render ------------------- 678 | -- render = require ("fbx4") 679 | 680 | local tt_from_attributes_id = function(A, id) 681 | --local tyti ="" 682 | --local tt = {} 683 | --if A._tag == "" then tyti = A._label 684 | --else tyti = A._label..' '..A._tag end 685 | -- print("TYTI: === "..tyti) 686 | local thelink = "#"..id 687 | if fbx.ishtmlbook and A._file~=nil then thelink = A._file..".qmd"..thelink end 688 | return {id = id, 689 | type = A._fbxclass, 690 | tag = A._tag, 691 | title = A._title, 692 | typlabel = A._label, 693 | typlabelTag = A._label .. ifelse(A._tag == "",""," "..A._tag), 694 | mdtitle = A._mdtitle, 695 | collapse = A._collapse, 696 | boxstyle = A._boxstyle, 697 | link = thelink 698 | } 699 | -- pout("====nun====");pout(tt) 700 | --return(tt) 701 | end 702 | 703 | insertStylesPandoc = function(doc) 704 | -- if stylez.extractStyleFromYaml then stylez.extractStyleFromYaml() end 705 | if stylez.insertPreamble and (quarto.doc.is_format("html") or quarto.doc.is_format("pdf")) 706 | then stylez.insertPreamble(doc, fbx.classDefaults, fmt) end 707 | return(doc) 708 | end; 709 | 710 | renderDiv = function(thediv) 711 | local A = thediv.attributes 712 | local tt = {} 713 | if A._fbxclass ~= nil then 714 | 715 | collapsstr = str(A._collapse) 716 | tt = tt_from_attributes_id(A, thediv.identifier) 717 | 718 | local fmt='html' 719 | if quarto.doc.is_format("pdf") then fmt = "tex" end; 720 | if #thediv.content > 0 and thediv.content[1].t == "Para" and 721 | thediv.content[#thediv.content].t == "Para" then 722 | table.insert(thediv.content[1].content, 1, 723 | pandoc.RawInline(fmt, stylez.blockStart(tt, fmt))) 724 | table.insert(thediv.content, 725 | pandoc.RawInline(fmt, stylez.blockEnd(tt, fmt))) 726 | else 727 | table.insert(thediv.content, 1, 728 | pandoc.RawBlock(fmt, stylez.blockStart(tt, fmt))) 729 | table.insert(thediv.content, 730 | pandoc.RawBlock(fmt, stylez.blockEnd(tt, fmt))) 731 | end 732 | --]] 733 | end 734 | return(thediv) 735 | end -- function renderDiv 736 | 737 | 738 | ------------- xrefs 739 | -- learned from nameref extension by shafayeedShafee 740 | -- TODO: make everything with walk. Looks so nice 741 | local function resolveref(data) 742 | return { 743 | RawInline = function(el) 744 | local refid = el.text:match("\\ref{(.*)}") 745 | if refid then 746 | if data[refid] then 747 | local href = '#'..refid 748 | if fbx.ishtmlbook then 749 | href = data[refid].file .. '.html' .. href 750 | end 751 | return pandoc.Link(data[refid].refnum, href) 752 | end end 753 | end 754 | } 755 | end 756 | 757 | -- TODO: with filenames for books 758 | 759 | function Pandoc_resolvexref(doc) 760 | local xrefdata = {} 761 | local xref = fbx.xref 762 | for _, xinf in pairs(xref) do 763 | if xinf.id then if xinf.id ~= "" then 764 | xrefdata[xinf.id] = xinf 765 | end end 766 | end 767 | -- pout(xrefdata) 768 | return doc:walk(resolveref(xrefdata)) 769 | end 770 | ----------- 771 | 772 | --- remove all attributes that start with underscore. 773 | -- could theoretically give clashes with filters that need persistent such attributes 774 | function Div_cleanupAttribs (el) 775 | if el.attributes._process_me then 776 | for k, v in pairs(el.attributes) do 777 | if string.sub(k, 1, 1) =="_" then el.attributes[k] = nil end 778 | end 779 | end 780 | return el 781 | end 782 | 783 | -- debugging stuff 784 | --[[ 785 | 786 | local function pandocblocks(doc) 787 | for k,v in pairs(doc.blocks) do 788 | pout(v.t) 789 | end 790 | end 791 | 792 | local function pandocdivs(div) 793 | pout(div.t.." - "..div.identifier) 794 | pout(div.attributes) 795 | end 796 | ]]-- 797 | 798 | local function Pandoc_makeListof(doc) 799 | local tt = {} 800 | local thelists = {} 801 | local zeile = "" 802 | local lstfilemode = ifelse(fbx.isfirstfile, "w", "a") 803 | if not fbx.lists then return(doc) end 804 | for i, blk in ipairs(doc.blocks) do 805 | --[[ -- this may require manual deletion of headers in the list-of.qmd 806 | -- and does in this form not help with html books anyway -- 807 | if blk.t=="Header" then 808 | if blk.level==1 then 809 | zeile = "\n\n## "..str(blk.content).."\n" 810 | --- add to all lists 811 | for _, lst in pairs (fbx.lists) do 812 | lst.contents = lst.contents..zeile 813 | end 814 | end 815 | elseif blk.t=="Div" then 816 | ]]-- 817 | if blk.t=="Div" then 818 | if blk.attributes._process_me then 819 | thelists = fbx.classDefaults[blk.attributes._fbxclass].listin 820 | if thelists ~= nil and thelists ~="" then 821 | tt = tt_from_attributes_id (blk.attributes, blk.identifier) 822 | -- pout("thett------");pout(tt) 823 | -- zeile = ("\n[**"..tt.typtitel.."**](#"..blk.identifier..")"..ifelse(tt.mdtitle=="","",": "..tt.mdtitle).. 824 | -- " \\Pageref{"..blk.identifier.."}\n") 825 | -- TODO: should be like [**R-tip 1.1**](intro.qmd#Rtip-install) 826 | --zeile = ("\n[**"..tt.titeltyp.." \\ref{"..blk.identifier.."}**]" .. 827 | -- ifelse(tt.mdtitle=="","",": "..tt.mdtitle) .. 828 | -- " \\Pageref{"..blk.identifier.."}\n") 829 | zeile = ("\n [**"..tt.typlabelTag.."**](" .. tt.link ..")" .. 830 | ifelse(tt.mdtitle=="","",": "..tt.mdtitle) .. "\\Pageref{".. tt.id .."}\n") 831 | for _, lst in ipairs (thelists) do 832 | fbx.lists[lst].contents = fbx.lists[lst].contents..zeile 833 | end 834 | end 835 | end 836 | end 837 | end 838 | --- write to file --- 839 | for nam, lst in pairs(fbx.lists) do 840 | if lst.file ~= nil then 841 | local file = io.open(lst.file, lstfilemode) 842 | if file then 843 | file:write(lst.contents) 844 | file:close() 845 | else pout("cannot write to file "..lst.file) 846 | end 847 | end 848 | end 849 | return(doc) 850 | end 851 | 852 | return{ 853 | {Meta = initMeta} 854 | --[[ 855 | ,{Pandoc = function(d) 856 | for k, v in pairs(stylez) do 857 | pout(k..": ".. type(v)) 858 | end 859 | end 860 | } 861 | ]]-- 862 | , {Meta = Meta_readxref, Div=fboxDiv_mark_for_processing, 863 | Pandoc = Pandoc_prefix_count} 864 | -- , {Div=pandocdivs, Pandoc=pandocblocks} 865 | --[[ ]] 866 | 867 | , {Div = Divs_getid, Pandoc = Pandoc_preparexref} 868 | , {Pandoc = Pandoc_resolvexref} 869 | , {Div = Divs_maketitle} 870 | , {Pandoc = Pandoc_finalizexref} 871 | , {Meta = Meta_writexref, Pandoc = Pandoc_makeListof} 872 | , {Div = renderDiv} 873 | , {Pandoc = insertStylesPandoc} 874 | , {Div = Div_cleanupAttribs} 875 | --[[ 876 | 877 | -- ]] 878 | } 879 | 880 | -------------------------------------------------------------------------------- /_extensions/custom-numbered-blocks/custom-numbered-blocks.lua.bak: -------------------------------------------------------------------------------- 1 | --[[ 2 | MIT License 3 | 4 | Copyright (c) 2023 Ute Hahn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ]]-- 24 | -- pre-pre-release 25 | -- 26 | -- partial rewrite, complete later 27 | 28 | -- nice rename function learned from shafayetShafee :-) 29 | local str = pandoc.utils.stringify 30 | local pout = quarto.log.output 31 | 32 | -- important quasi global variables 33 | 34 | local ishtml = quarto.doc.is_format("html") 35 | local ispdf = quarto.doc.is_format("pdf") 36 | local fmt="" 37 | if ishtml then fmt="html" elseif ispdf then fmt = "pdf" end 38 | 39 | -- TODO encapsulate stylez into a doc thing or so 40 | -- maybe later allow various versions concurrently. 41 | -- this could probably be problematic because of option clashes in rendered header? 42 | 43 | local stylename="foldbox" 44 | 45 | local stylez = require("style/"..stylename) 46 | 47 | 48 | --- TODO: better encapsulation (luxury :-P) 49 | 50 | fbx={ -- global table, holds information for processing fboxes 51 | xreffile = "._xref.json" -- default name, set to lastfile in initial meta analysis 52 | } 53 | 54 | 55 | -- utility functions --- 56 | local function DeInline(tbl) 57 | local result ={} 58 | for i, v in pairs(tbl) do 59 | pdtype = pandoc.utils.type(v) 60 | if pdtype == "Inlines" or pdtype =="boolean" 61 | then 62 | result[i] = str(v) 63 | elseif pdtype == "List" then result[i] = DeInline(v) 64 | end 65 | end 66 | return(result) 67 | end 68 | 69 | local function tablecontains(tbl, val) 70 | if tbl ~= nil and val ~= nil then 71 | for _, v in ipairs(tbl) do 72 | if val == v then return true end 73 | end 74 | end 75 | return false 76 | end 77 | 78 | local function ifelse(condition, iftrue, iffalse) 79 | if condition then return iftrue else return iffalse end 80 | end 81 | 82 | local function replaceifnil(existvalue, replacevalue) 83 | if existvalue ~= nil then return existvalue else return replacevalue end 84 | end 85 | 86 | local function replaceifempty(existvalue, replacevalue) 87 | if existvalue == nil or existvalue=="" then return replacevalue else return existvalue end 88 | end 89 | 90 | 91 | local function updateTable (oldtbl, newtbl, ignorekeys) 92 | local result = {} 93 | -- copy old attributes 94 | for k, v in pairs(oldtbl) do result[k] = v end 95 | if newtbl ~= nil then if type(newtbl) == "table" then 96 | if newtbl[1] == nil then -- it is an ok table with key value pairs 97 | for k, v in pairs(newtbl) do 98 | if not(tablecontains(ignorekeys, k)) then 99 | result[k] = v 100 | end 101 | end 102 | -- special: set reflabel to label if not given in attribs 103 | -- if newattribs["reflabel"] == nil then result.reflabel = result.label end 104 | -- TODO: do this elsewhere 105 | end 106 | end 107 | end 108 | return(result) 109 | end 110 | 111 | 112 | 113 | ---- init step --------------------------- 114 | --- init = require ("fbx1") 115 | 116 | -- find chapter number and file name 117 | -- returns a table with keyed entries 118 | -- processedfile: string, 119 | -- ishtmlbook: boolean, 120 | -- chapno: string (at least if ishtmlbook), 121 | -- unnumbered: boolean - initial state of section / chapter 122 | -- if the user has given a chapno yaml entry, then unnumbered = false 123 | 124 | -- !!! for pdf, the workflow is very different! --- 125 | -- also find out if lastfile of a book 126 | 127 | -- find first and last file of a book, and chapter number of that file 128 | local function chapterinfo(book, fname) 129 | local first = "" 130 | local last = "" 131 | local chapno = nil 132 | local info = {} 133 | --if book.render then 134 | for _, v in pairs(book.render) do 135 | if str(v.type) == "chapter" then 136 | last = pandoc.path.split_extension(str(v.file)) 137 | if first == "" then first = last end 138 | if last == fname then chapno = v.number end 139 | end 140 | end 141 | info.islast = (fname == last) 142 | info.isfirst = (fname == first) 143 | info.lastchapter = last 144 | info.chapno = chapno 145 | pout("chapter inf:", info) 146 | return(info) 147 | end 148 | 149 | local function Meta_findChapterNumber(meta) 150 | local processedfile = pandoc.path.split_extension(PANDOC_STATE.output_file) 151 | fbx.processedfile = processedfile 152 | fbx.xreffile ="._"..processedfile.."_xref.json" 153 | fbx.output_file = PANDOC_STATE.output_file 154 | -- pout(" now in "..processedfile.." later becomes ".. str(fbx.output_file)) 155 | fbx.ishtmlbook = meta.book ~= nil and not quarto.doc.is_format("pdf") 156 | fbx.isfirstfile = not fbx.ishtmlbook 157 | fbx.islastfile = not fbx.ishtmlbook 158 | if fbx.ishtmlbook then 159 | local chinfo = chapterinfo(meta.book, processedfile) 160 | fbx.xreffile= "._"..chinfo.lastchapter.."_xref.json" 161 | fbx.isfirstfile = chinfo.isfirst 162 | fbx.islastfile = chinfo.islast 163 | 164 | fbx.unnumbered = false 165 | -- user set chapter number overrides information from meta 166 | if meta.chapno then 167 | fbx.chapno = str(meta.chapno) 168 | else 169 | if chinfo.chapno ~= nil then 170 | fbx.chapno = str(chinfo.chapno) 171 | else 172 | fbx.chapno = "" 173 | fbx.unnumbered = true 174 | end 175 | end 176 | else -- not a book. 177 | fbx.chapno = "" 178 | fbx.unnumbered = true 179 | end 180 | end 181 | 182 | local function makeKnownClassDetector(knownclasses) 183 | return function(div) 184 | for _, cls in pairs(div.classes) do 185 | if tablecontains(knownclasses, cls) then return str(cls) end 186 | end 187 | return nil 188 | end 189 | end 190 | 191 | local function Meta_initClassDefaults (meta) 192 | -- do we want to prefix fbx numbers with section numbers? 193 | local cunumbl = meta["custom-numbered-blocks"] 194 | fbx.knownclasses = {} 195 | fbx.lists = {} 196 | --[[ TODO later 197 | if meta.fbx_number_within_sections then 198 | fbx.number_within_sections = meta.fbx_number_within_sections 199 | else 200 | fbx.number_within_sections = false 201 | end 202 | --]] 203 | -- prepare information for numbering fboxes by class 204 | -- fbx.knownClasses ={} 205 | fbx.classDefaults ={} 206 | local groupDefaults = {default = stylez.defaultOptions} -- not needed later 207 | fbx.counter = {unnumbered = 0} -- counter for unnumbered divs 208 | -- ! unnumbered not for classes that have unnumbered as default ! 209 | -- fbx.counterx = {} 210 | if cunumbl.classes == nil then 211 | print("== @%!& == Warning == &!%@ ==\n wrong format for fboxes yaml: classes needed") 212 | return 213 | end 214 | 215 | -- simplified copy of yaml data: inlines to string 216 | if cunumbl.groups then 217 | for key, val in pairs(cunumbl.groups) do 218 | local ginfo = DeInline(val) 219 | --[[ 220 | pout("==== before after ======="); pout(ginfo) 221 | if ginfo.boxstyle then 222 | local mainstyle, substyle = ginfo.boxstyle:match "([^.]*).(.*)" 223 | -- pout("main "..mainstyle.." :: "..substyle) 224 | -- TODO: here account for multiple styles 225 | end 226 | --]]-- 227 | ginfo = updateTable(stylez.defaultOptions, ginfo) 228 | --fbx. 229 | groupDefaults[key] = ginfo 230 | -- pout("-----group---"); pout(ginfo) 231 | end 232 | end 233 | for key, val in pairs(cunumbl.classes) do 234 | local clinfo = DeInline(val) 235 | -- pout("==== before after ======="); pout(clinfo) 236 | -- classinfo[key] = DeInline(val) 237 | table.insert(fbx.knownclasses, str(key)) 238 | local theGroup = replaceifnil(clinfo.group, "default") 239 | clinfo = updateTable(groupDefaults[theGroup], clinfo) 240 | clinfo.label = replaceifnil(clinfo.label, str(key)) 241 | clinfo.reflabel = replaceifnil(clinfo.reflabel, clinfo.label) 242 | -- assign counter -- 243 | clinfo.cntname = replaceifnil(clinfo.group, str(key)) 244 | fbx.counter[clinfo.cntname] = 0 -- sets the counter up if non existing 245 | fbx.classDefaults[key] = clinfo 246 | -- pout("---class----"); pout(clinfo) 247 | end 248 | fbx.is_cunumblo = makeKnownClassDetector(fbx.knownclasses) 249 | -- gather lists-of and make filenames by going through all classes 250 | for _, val in pairs(fbx.classDefaults) do 251 | -- pout("--classdefault: "..str(key)) 252 | -- pout(val) 253 | if val.listin then 254 | for _,v in ipairs(val.listin) do 255 | fbx.lists[v] = {file = "list-of-"..str(v)..".qmd"} 256 | end 257 | end 258 | end 259 | -- initialize lists 260 | for key, val in pairs(fbx.lists) do 261 | val.contents = ifelse(fbx.isfirstfile, "\\providecommand{\\Pageref}[1]{\\hfill p.\\pageref{#1}}", "") 262 | -- listin approach does not require knownclass, since listin is in classdefaults 263 | end 264 | -- pout(fbx.lists) 265 | --]] 266 | -- document can give the chapter number for books in yaml header 267 | -- this becomes the counter Prefix 268 | end 269 | 270 | local initMeta = function(m) 271 | if m["custom-numbered-blocks"] then 272 | Meta_findChapterNumber(m) 273 | Meta_initClassDefaults(m) 274 | else 275 | print("== @%!& == Warning == &!%@ ==\n missing cunumblo key in yaml") 276 | end 277 | return(m) 278 | end 279 | 280 | ----------------------- oldcode, mostly ------------------------------------- 281 | 282 | 283 | ------- numbering and class attributes ------ 284 | 285 | local function fboxDiv_setAttributes(el, cls, prefix) 286 | local ela = el.attributes -- shortcut 287 | local ClassDef = fbx.classDefaults[cls] 288 | --local unnumbered = ClassDef.numbered == "false" 289 | local numbered = ClassDef.numbered ~= "false" 290 | local tag = ela.tag 291 | local tagged = tag ~= nil 292 | local id = el.identifier 293 | local autoid ="" 294 | -- local titl = ela.title 295 | local cntkey = ClassDef.cntname 296 | local counter = {} 297 | local cnts = 0 298 | local idnumber = "0.0" 299 | 300 | -- set prefix 301 | ela._prefix = prefix 302 | 303 | id = replaceifnil(id ,"") 304 | tag = replaceifnil(tag ,"") 305 | 306 | --- determine if numbered and / or tagged ------ 307 | 308 | if tagged then numbered = false end 309 | if el.classes:includes("unnumbered") then numbered = false end 310 | 311 | if ela.numtag then 312 | tag = ela.numtag 313 | -- print("!!! also hier mal ein numtag.\n") 314 | numbered = true 315 | tagged = true 316 | end 317 | 318 | -- make counts --- 319 | 320 | if not numbered then cntkey = "unnumbered" end 321 | 322 | cnts = fbx.counter[cntkey] +1 323 | fbx.counter[cntkey] = cnts 324 | 325 | idnumber = ifelse(prefix ~= "", prefix .. '.' .. cnts, str(cnts)) 326 | --[[ 327 | if prefix ~="" then idnumber = prefix .. '.' .. cnts 328 | else idnumber = str(cnts) 329 | end 330 | 331 | if numbered then 332 | if not tagged then tag = idnumber 333 | else tag = idnumber.."("..tag..")" 334 | end 335 | end 336 | ]]-- 337 | if numbered then tag = idnumber..ifelse(tagged, "("..tag..")", "" ) end 338 | 339 | if id == "" then 340 | if numbered then 341 | autoid = ela._fbxclass..'-'..tag 342 | else 343 | autoid = ela._fbxclass..'*-'..idnumber 344 | end 345 | -- changed my mind here: always give autoid 346 | else autoid = ela._fbxclass..'-id-'..id 347 | end 348 | 349 | -- do not change identifier el.identifier = id 350 | 351 | ela._autoid = autoid 352 | 353 | ela._tag = tag 354 | 355 | ela._file = fbx.processedfile -- necessary to reference between chapters. At least with quarto 1.3 356 | -- pout("tag: "..tag) 357 | -- pout(ela) 358 | return(el) 359 | end 360 | 361 | -- initial attributes without prefix and counts to allow for inner boxes 362 | 363 | local function fboxDiv_mark_for_processing(div) 364 | local diva=div.attributes 365 | local cls = fbx.is_cunumblo(div) 366 | local ClassDef = fbx.classDefaults[cls] 367 | if(cls) then 368 | diva._process_me = "true" 369 | diva._fbxclass = str(cls) 370 | diva._prefix = "" 371 | diva._tag = "" 372 | diva._collapse = str(replaceifnil(diva.collapse, ClassDef.collapse)) 373 | diva._boxstyle = str(replaceifnil(diva.boxstyle, ClassDef.boxstyle)) 374 | diva._label = str(replaceifnil(diva.label, ClassDef.label)) 375 | diva._reflabel = str(replaceifnil(diva.reflabel, ClassDef.reflabel)) 376 | end 377 | return(div) 378 | end 379 | 380 | 381 | 382 | local function Pandoc_prefix_count(doc) 383 | -- do evt later: non numeric chapernumbers 384 | local secno = 0 385 | local prefix = "0" 386 | if fbx.ishtmlbook then prefix = fbx.chapno end 387 | 388 | -- pout("this is a book?"..str(fbx.ishtmlbook)) 389 | 390 | --- make numbering and prep div blocks --- 391 | --[[------- comment ----------- 392 | quarto (1.2) books allow level 1 headings within a chapter. 393 | This would give a mess for crossreference numbers: e.g. multiple examples 3.1, 394 | from chapter 1 (with 2 l1 headers ) and chapter 3. 395 | Therefore I decided to ignore level 1 headings in chapters. 396 | This can easily be changed, then the crossref is for the last occurence only. 397 | Maybe one day when there is more fine tuning concerning individual numbering depth. 398 | If this happens before quarto 1.4 399 | 400 | --]]---------- end comment ------------ 401 | for i, blk in ipairs(doc.blocks) do 402 | -- print(prefix.."-"..i.." "..blk.t.."\n") 403 | 404 | if blk.t=="Header" and not fbx.ishtmlbook then 405 | if (blk.level == 1) then -- increase prefix 406 | if blk.classes:includes("unnumbered") 407 | then 408 | prefix = "" 409 | else 410 | secno = secno + 1 411 | prefix = str(secno) 412 | end 413 | -- reset counters in fbx -- 414 | -- this would be more complicated if there are different levels 415 | -- of numbering depth 416 | -- then: add a numdepth variable to fbx with a list of keys 417 | for k in pairs(fbx.counter) do fbx.counter[k]=0 end 418 | end 419 | 420 | -- problem: only the outer divs are captured 421 | elseif blk.t=="Div" then 422 | local known = fbx.is_cunumblo(blk) 423 | if known then 424 | blk = fboxDiv_setAttributes(blk, known, prefix) 425 | end 426 | end 427 | end 428 | return(doc) 429 | end 430 | 431 | -- if no id, get from first included header, if possible 432 | local function Divs_getid(el) 433 | -- local known = getKnownEnv(el.attr.classes) 434 | local ela = el.attributes 435 | local id = el.identifier 436 | 437 | if not ela._process_me then return(el) end 438 | -- pout("--- processing item with id ".. replaceifempty(id, "LEER")) 439 | 440 | if id == nil or id =="" then 441 | -- try in next header 442 | el1 = el.content[1] 443 | if el1.t=="Header" then 444 | -- pout("--- looking at header with id "..el1.identifier) 445 | -- pout("--- still processing item with id ".. replaceifempty(id, "LEER")) 446 | -- pout("replacing id") 447 | id = el1.identifier 448 | el.identifier = id 449 | end 450 | end 451 | if id == nil or id =="" 452 | then 453 | -- pout("immer noch leer") 454 | if ela._autoid ~= nil then 455 | id = ela._autoid 456 | el.identifier = id 457 | end 458 | --else pout("nix autoid in ");pout(ela._autoid) 459 | end 460 | -- pout("resulting el:"); pout(el.attr) 461 | return(el) 462 | end 463 | 464 | 465 | --- utility function: stringify and sanitize math, depends on format --- 466 | local function str_sanimath(theInline, fmt) 467 | local newstring = theInline:walk{ 468 | Math = function(ma) 469 | local mathtxt = str(ma.text) 470 | if fmt == "html" then 471 | return {'\\(' .. mathtxt .. '\\)'} 472 | elseif fmt == "pdf" then 473 | return {'\\(' .. mathtxt .. '\\)'} 474 | elseif fmt == "md" then 475 | return {'$' .. mathtxt .. '$'} 476 | else return {mathtxt} 477 | end 478 | end 479 | } 480 | return str(newstring) 481 | end 482 | 483 | 484 | ----------- title of divs ---------------- 485 | local function Divs_maketitle(el) 486 | -- local known = getKnownEnv(el.attr.classes) 487 | local ela = el.attributes 488 | local titl = ela.title 489 | local mdtitl = replaceifnil(ela.title, "") 490 | local ClassDef = {} 491 | -- local id = el.identifier 492 | 493 | if not ela._process_me then return(el) end 494 | -- pout("--- processing item with id ".. replaceifempty(el.identifier, "LEER")) 495 | 496 | ClassDef = fbx.classDefaults[ela._fbxclass] 497 | 498 | if titl == nil then 499 | el1 = el.content[1] 500 | if el1.t=="Header" then 501 | -- sanitize math inline. depends on format 502 | ela.title = str(el1.content) -- readable version without math 503 | mdtitl = str_sanimath(el1.content, "md") 504 | if ishtml then titl = str_sanimath(el1.content, "html") 505 | elseif ispdf then titl = str_sanimath(el1.content, "pdf") 506 | else titl = mdtitl 507 | end 508 | -- pout("--- looking at header with id "..el1.identifier) 509 | -- pout("--- still processing item with id ".. replaceifempty(id, "LEER")) 510 | --[[ 511 | if id =="" or id == nil then 512 | pout("replacing id") 513 | id = el1.identifier 514 | el.identifier = id 515 | end 516 | ]]-- 517 | table.remove(el.content, 1) 518 | else titl = "" 519 | end 520 | end 521 | ela._title = titl -- keep the title as attribute for pandoc 522 | ela._mdtitle = mdtitl -- for list of 523 | -- replace empty identifier with autoid 524 | -- if el.identifier == "" then el.identifier = ela._autoid end 525 | -- pout("--> sanitarer titel: "..mdtitl) 526 | -- ela._tag = "" 527 | -- pout("resulting el:"); pout(el) 528 | return(el) 529 | end 530 | 531 | ---------------- initialize xref ---------- 532 | -- xrefinit = require ("fbx3") 533 | 534 | -- xref split into prepare and finalize to allow xref in titles (future) 535 | local function Pandoc_preparexref(doc) 536 | -- local xref={} 537 | local id = "" 538 | local cnt = 0 539 | local bla={} 540 | local xinfo={} 541 | local file_autoid = {} 542 | local exists = false 543 | if fbx.xref == nil then fbx.xref ={} end 544 | xref = fbx.xref 545 | cnt = #xref 546 | if cnt > 0 then 547 | for i, xinf in ipairs(xref) do 548 | file_autoid[xinf.file..xinf.autoid] = i 549 | end 550 | -- pout(autoids) 551 | end 552 | for _, blk in ipairs(doc.blocks) do 553 | if blk.attributes then 554 | bla = blk.attributes 555 | if bla._process_me then 556 | --pout("fbox "..blk.attributes._tag) 557 | ------------- an fbox :) ------------ 558 | if blk.identifier == nil then id = "" 559 | else id = blk.identifier end 560 | xinfo = { 561 | id = id, 562 | autoid = bla._autoid, 563 | cls = bla._fbxclass, 564 | label = bla._label, 565 | reflabel = bla._reflabel, 566 | reftag = bla._tag, 567 | refnum = replaceifempty(bla._tag, "??"), 568 | file = fbx.output_file 569 | } 570 | -- if not xinfo.reftag then xinfo.reftag ="" end 571 | -- if xinfo.refnum == "" then xinfo.refnum ="??" end 572 | --[[ 573 | if bla._tag 574 | then 575 | if bla._tag ~="" then xinfo.refnum = bla._tag else xinfo.reftag="??" end 576 | end 577 | ]]-- 578 | --- check if autoid already exist in database. otherwise update 579 | oldxrefno = file_autoid[xinfo.file..xinfo.autoid] 580 | if oldxrefno == nil then 581 | cnt = cnt+1 582 | bla._xrefno = cnt 583 | table.insert (xref, cnt, xinfo) 584 | else 585 | bla._xrefno = oldxrefno 586 | xref[oldxrefno] = xinfo 587 | end 588 | end 589 | end 590 | end 591 | return(doc) 592 | end 593 | 594 | 595 | local function Pandoc_finalizexref(doc) 596 | xref = fbx.xref -- shortcut 597 | local bla = {} 598 | -- pout("------- finale ----") 599 | for i, blk in ipairs(doc.blocks) do 600 | bla = blk.attributes 601 | --pout(bla) 602 | if bla then 603 | if bla._process_me == "true" then 604 | ------------- an fbox :) ------------ 605 | xindex = tonumber(bla._xrefno) 606 | if xindex then 607 | xref[xindex].neu = true -- mark as new entry 608 | if bla._title then xref[xindex].title = bla._title end 609 | end 610 | 611 | -- else pout("ochje.") 612 | end 613 | end 614 | end 615 | -- pout(xref) 616 | --- write to disc -- 617 | --- check if this was the last file to process --- 618 | return(doc) 619 | end 620 | 621 | 622 | local function Meta_writexref(meta) 623 | local xref = fbx.xref 624 | local xrjson = quarto.json.encode(fbx.xref) 625 | local file = io.open(fbx.xreffile,"w") 626 | if file ~= nil then 627 | file:write(xrjson) 628 | file:close() 629 | end 630 | if fbx.islastfile then 631 | -- pout(fbx.processedfile.." -- nu aufräum! aber zack ---") 632 | for i, v in ipairs(xref) do 633 | if not v.neu then 634 | -- pout("killed") 635 | -- pout(v) 636 | xref[i] = nil 637 | end 638 | end 639 | -- pout("-------- überlebende") 640 | -- pout(xref) 641 | end 642 | end 643 | 644 | 645 | local function Meta_readxref(meta) 646 | local file = io.open(fbx.xreffile,"r") 647 | if file then 648 | local xrfjson = file:read "*a" 649 | file:close() 650 | --[[ 651 | if xrfjson then meta.fbx.xref = quarto.json.decode(xrfjson) 652 | else meta.fbx.xref = {} end 653 | -- pout("eingelesen") 654 | -- pout(meta.fbx.xref) 655 | else meta.fbx.xref ={} 656 | --]]-- 657 | if xrfjson then fbx.xref = quarto.json.decode(xrfjson) 658 | else fbx.xref = {} end 659 | -- pout("eingelesen") 660 | --pout(fbx.xref) 661 | else fbx.xref ={} 662 | end 663 | return(meta) 664 | end 665 | -------------- render ------------------- 666 | -- render = require ("fbx4") 667 | 668 | local tt_from_attributes_id = function(A, id) 669 | --local tyti ="" 670 | --local tt = {} 671 | --if A._tag == "" then tyti = A._label 672 | --else tyti = A._label..' '..A._tag end 673 | -- print("TYTI: === "..tyti) 674 | local thelink = "#"..id 675 | if fbx.ishtmlbook and A._file~=nil then thelink = A._file..".qmd"..thelink end 676 | return {id = id, 677 | type = A._fbxclass, 678 | tag = A._tag, 679 | title = A._title, 680 | typlabel = A._label, 681 | typlabelTag = A._label .. ifelse(A._tag == "",""," "..A._tag), 682 | mdtitle = A._mdtitle, 683 | collapse = A._collapse, 684 | boxstyle = A._boxstyle, 685 | link = thelink 686 | } 687 | -- pout("====nun====");pout(tt) 688 | --return(tt) 689 | end 690 | 691 | insertStylesPandoc = function(doc) 692 | -- if stylez.extractStyleFromYaml then stylez.extractStyleFromYaml() end 693 | if stylez.insertPreamble and (quarto.doc.is_format("html") or quarto.doc.is_format("pdf")) 694 | then stylez.insertPreamble(doc, fbx.classDefaults, fmt) end 695 | return(doc) 696 | end; 697 | 698 | renderDiv = function(thediv) 699 | local A = thediv.attributes 700 | local tt = {} 701 | if A._fbxclass ~= nil then 702 | 703 | collapsstr = str(A._collapse) 704 | tt = tt_from_attributes_id(A, thediv.identifier) 705 | 706 | local fmt='html' 707 | if quarto.doc.is_format("pdf") then fmt = "tex" end; 708 | if #thediv.content > 0 and thediv.content[1].t == "Para" and 709 | thediv.content[#thediv.content].t == "Para" then 710 | table.insert(thediv.content[1].content, 1, 711 | pandoc.RawInline(fmt, stylez.blockStart(tt, fmt))) 712 | table.insert(thediv.content, 713 | pandoc.RawInline(fmt, stylez.blockEnd(tt, fmt))) 714 | else 715 | table.insert(thediv.content, 1, 716 | pandoc.RawBlock(fmt, stylez.blockStart(tt, fmt))) 717 | table.insert(thediv.content, 718 | pandoc.RawBlock(fmt, stylez.blockEnd(tt, fmt))) 719 | end 720 | --]] 721 | end 722 | return(thediv) 723 | end -- function renderDiv 724 | 725 | 726 | ------------- xrefs 727 | -- learned from nameref extension by shafayeedShafee 728 | -- TODO: make everything with walk. Looks so nice 729 | local function resolveref(data) 730 | return { 731 | RawInline = function(el) 732 | local refid = el.text:match("\\ref{(.*)}") 733 | if refid then 734 | if data[refid] then 735 | local href = '#'..refid 736 | if fbx.ishtmlbook then 737 | href = data[refid].file .. href 738 | end 739 | return pandoc.Link(data[refid].refnum, href) 740 | end end 741 | end 742 | } 743 | end 744 | 745 | -- TODO: with filenames for books 746 | 747 | function Pandoc_resolvexref(doc) 748 | local xrefdata = {} 749 | local xref = fbx.xref 750 | for _, xinf in pairs(xref) do 751 | if xinf.id then if xinf.id ~= "" then 752 | xrefdata[xinf.id] = xinf 753 | end end 754 | end 755 | -- pout(xrefdata) 756 | return doc:walk(resolveref(xrefdata)) 757 | end 758 | ----------- 759 | 760 | --- remove all attributes that start with underscore. 761 | -- could theoretically give clashes with filters that need persistent such attributes 762 | function Div_cleanupAttribs (el) 763 | if el.attributes._process_me then 764 | for k, v in pairs(el.attributes) do 765 | if string.sub(k, 1, 1) =="_" then el.attributes[k] = nil end 766 | end 767 | end 768 | return el 769 | end 770 | 771 | -- debugging stuff 772 | --[[ 773 | 774 | local function pandocblocks(doc) 775 | for k,v in pairs(doc.blocks) do 776 | pout(v.t) 777 | end 778 | end 779 | 780 | local function pandocdivs(div) 781 | pout(div.t.." - "..div.identifier) 782 | pout(div.attributes) 783 | end 784 | ]]-- 785 | 786 | local function Pandoc_makeListof(doc) 787 | local tt = {} 788 | local thelists = {} 789 | local zeile = "" 790 | local lstfilemode = ifelse(fbx.isfirstfile, "w", "a") 791 | if not fbx.lists then return(doc) end 792 | for i, blk in ipairs(doc.blocks) do 793 | --[[ -- this may require manual deletion of headers in the list-of.qmd 794 | -- and does in this form not help with html books anyway -- 795 | if blk.t=="Header" then 796 | if blk.level==1 then 797 | zeile = "\n\n## "..str(blk.content).."\n" 798 | --- add to all lists 799 | for _, lst in pairs (fbx.lists) do 800 | lst.contents = lst.contents..zeile 801 | end 802 | end 803 | elseif blk.t=="Div" then 804 | ]]-- 805 | if blk.t=="Div" then 806 | if blk.attributes._process_me then 807 | thelists = fbx.classDefaults[blk.attributes._fbxclass].listin 808 | if thelists ~= nil and thelists ~="" then 809 | tt = tt_from_attributes_id (blk.attributes, blk.identifier) 810 | -- pout("thett------");pout(tt) 811 | -- zeile = ("\n[**"..tt.typtitel.."**](#"..blk.identifier..")"..ifelse(tt.mdtitle=="","",": "..tt.mdtitle).. 812 | -- " \\Pageref{"..blk.identifier.."}\n") 813 | -- TODO: should be like [**R-tip 1.1**](intro.qmd#Rtip-install) 814 | --zeile = ("\n[**"..tt.titeltyp.." \\ref{"..blk.identifier.."}**]" .. 815 | -- ifelse(tt.mdtitle=="","",": "..tt.mdtitle) .. 816 | -- " \\Pageref{"..blk.identifier.."}\n") 817 | zeile = ("\n [**"..tt.typlabelTag.."**](" .. tt.link ..")" .. 818 | ifelse(tt.mdtitle=="","",": "..tt.mdtitle) .. "\\Pageref{".. tt.id .."}\n") 819 | for _, lst in ipairs (thelists) do 820 | fbx.lists[lst].contents = fbx.lists[lst].contents..zeile 821 | end 822 | end 823 | end 824 | end 825 | end 826 | --- write to file --- 827 | for nam, lst in pairs(fbx.lists) do 828 | if lst.file ~= nil then 829 | local file = io.open(lst.file, lstfilemode) 830 | if file then 831 | file:write(lst.contents) 832 | file:close() 833 | else pout("cannot write to file "..lst.file) 834 | end 835 | end 836 | end 837 | return(doc) 838 | end 839 | 840 | return{ 841 | {Meta = initMeta} 842 | --[[ 843 | ,{Pandoc = function(d) 844 | for k, v in pairs(stylez) do 845 | pout(k..": ".. type(v)) 846 | end 847 | end 848 | } 849 | ]]-- 850 | , {Meta = Meta_readxref, Div=fboxDiv_mark_for_processing, 851 | Pandoc = Pandoc_prefix_count} 852 | -- , {Div=pandocdivs, Pandoc=pandocblocks} 853 | --[[ ]] 854 | 855 | , {Div = Divs_getid, Pandoc = Pandoc_preparexref} 856 | , {Pandoc = Pandoc_resolvexref} 857 | , {Div = Divs_maketitle} 858 | , {Pandoc = Pandoc_finalizexref} 859 | , {Meta = Meta_writexref, Pandoc = Pandoc_makeListof} 860 | , {Div = renderDiv} 861 | , {Pandoc = insertStylesPandoc} 862 | , {Div = Div_cleanupAttribs} 863 | --[[ 864 | 865 | -- ]] 866 | } 867 | 868 | -------------------------------------------------------------------------------- /_extensions/custom-numbered-blocks/style/foldbox.css: -------------------------------------------------------------------------------- 1 | .fbx-default{ 2 | --color1: #c7c7d0; 3 | --color2: #a3a3aa; 4 | 5 | --border-color: var(--color2); 6 | --title-color: #1b1b1b; 7 | --title-background-color: var(--color1); 8 | --title-padding: 0.5rem; 9 | --border-radius: .3rem; 10 | 11 | --padding-left: .8rem; /* for title (summary) and content (next div) */ 12 | --padding-content: 0.5rem; 13 | 14 | --thickshadow: 4px 4px 6px #555; 15 | --thinshadow: 1px 1px 2px #555; 16 | --noshadow: 0px 0px 0px #FFF; 17 | } 18 | 19 | 20 | /* for button placement */ 21 | details.fbx-default[open] { 22 | position: relative; 23 | } 24 | 25 | details.fbx-default > summary{ 26 | background-color: var(--title-background-color); 27 | color: var(--title-color); 28 | 29 | cursor: pointer; 30 | 31 | border-radius: var(--border-radius); 32 | border-left: var(--border-radius) solid var(--border-color); 33 | border-bottom: 1px solid var(--border-color); 34 | box-shadow: var(--thinshadow); 35 | 36 | padding: var(--title-padding); 37 | padding-left: var(--padding-left); 38 | } 39 | 40 | details.fbx-default > summary:hover{ 41 | box-shadow: var(--thickshadow); 42 | } 43 | 44 | details.fbx-default[open] > summary{ 45 | border-bottom-left-radius: 0px; 46 | } 47 | 48 | details.fbx-default > div{ 49 | padding: var(--padding-content); 50 | padding-left: var(--padding-left); 51 | padding-bottom: 0; 52 | 53 | border-left: var(--border-radius) solid var(--border-color); 54 | border-top: 0; 55 | margin-top: 0; 56 | border-left: .3rem solid var(--border-color); 57 | border-bottom: 1px solid var(--border-color); 58 | border-bottom-left-radius: .3rem; 59 | } 60 | 61 | /* simple box */ 62 | 63 | details.fbx-simplebox > div{ 64 | padding-bottom: 0; 65 | border-right: 1px solid var(--border-color); 66 | border-bottom-right-radius: .3rem; 67 | } 68 | 69 | 70 | /* ------ closebutton --- */ 71 | 72 | .closebutton{ 73 | --close-text: "▲"; 74 | --close-background-color: #dddddd; 75 | --close-hover-background-color: var(--title-background-color); 76 | --close-hover-color: var(--title-color); 77 | } 78 | 79 | /* adjust this by hand if close button gets in the way: just add
at the end 80 | details.closebutton[open]{ 81 | padding-bottom: 1rem; 82 | } 83 | */ 84 | 85 | details.closebutton[open] > summary::after { 86 | display: button; 87 | content: var(--close-text); 88 | /* 89 | background-color: var(--close-background-color); 90 | */ 91 | border: 1px solid var(--close-background-color); 92 | border-radius: 0.3rem; 93 | padding: .2rem 0.5rem 0.2rem 0.5rem; 94 | box-shadow: var(--thinshadow); 95 | position: absolute; 96 | right: 0;/*1rem;*/ 97 | bottom: .5rem; 98 | } 99 | 100 | details.closebutton[open] > summary:hover::after { 101 | background-color: var(--close-hover-background-color); 102 | border: 1px solid var(--border-color); 103 | color: var(--close-hover-color); 104 | box-shadow: var(--thickshadow); 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /_extensions/custom-numbered-blocks/style/foldbox.lua: -------------------------------------------------------------------------------- 1 | -- nice rename function learned from shafayetShafee :-) 2 | local str = pandoc.utils.stringify 3 | local pout = quarto.log.output 4 | 5 | 6 | return { 7 | 8 | defaultOptions={ 9 | numbered = "true", 10 | boxstyle = "foldbox.default", 11 | collapse = "true", 12 | colors = {"c0c0c0","808080"} 13 | }, 14 | 15 | blockStart = function (tt, fmt) 16 | local Open ="" 17 | local BoxStyle =" fbx-default closebutton" 18 | local texEnv = "fbx" 19 | if #tt.title > 0 then tt.typlabelTag = tt.typlabelTag..": " end 20 | if fmt =="html" then 21 | if tt.collapse =="false" then Open=" open" end 22 | if tt.boxstyle =="foldbox.simple" 23 | then 24 | BoxStyle=" fbx-simplebox fbx-default" 25 | -- Open=" open" do not force override. Chose this in yaml or individually. 26 | -- we would want e.g to have remarks closed by default 27 | end 28 | result = ('
'..''..tt.typlabelTag..''..tt.title .. '
') 29 | return result 30 | 31 | elseif fmt =="tex" then 32 | if tt.boxstyle=="foldbox.simple" then texEnv = "fbxSimple" end 33 | return('\\begin{'..texEnv..'}{'..tt.type..'}{'..tt.typlabelTag..'}{'..tt.title..'}\n'.. 34 | '\\phantomsection\\label{'..tt.id..'}\n') 35 | else 36 | return("
Hallihallo") 37 | end 38 | end, 39 | 40 | blockEnd = function (tt, fmt) 41 | local texEnv = "fbx" 42 | if fmt =="html" then 43 | return('
') 44 | elseif fmt =="tex" then 45 | if tt.boxstyle=="foldbox.simple" then texEnv = "fbxSimple" end 46 | return('\\end{'..texEnv..'}\n') 47 | else return ('ende mit format '..fmt..'=================') 48 | end 49 | end, 50 | 51 | insertPreamble = function(doc, classDefs, fmt) 52 | local ishtml = quarto.doc.is_format("html") 53 | local ispdf = quarto.doc.is_format("pdf") 54 | local StyleCSSTeX = {} 55 | 56 | -- if fmt==nil then pout("=== NIX ======= Format ") else 57 | -- pout("============== Format : "..str(fmt)) end 58 | -- make css or preamble tex for colors 59 | local extractStyleFromMeta = function (fmt) 60 | local result 61 | if classDefs ~= nil then 62 | for cls, options in pairs(classDefs) do 63 | --quarto.log.output(cls) 64 | if options.colors then 65 | -- quarto.log.output(" --> Farben!") 66 | if fmt == "html" then 67 | table.insert(StyleCSSTeX, "."..cls.." {\n") 68 | for i, col in ipairs(options.colors) do 69 | table.insert(StyleCSSTeX, " --color"..i..": #"..col..";\n") 70 | end 71 | table.insert(StyleCSSTeX, "}\n") 72 | elseif fmt == "pdf" then 73 | for i, col in ipairs(options.colors) do 74 | table.insert(StyleCSSTeX, "\\definecolor{"..cls.."-color"..i.."}{HTML}{"..col.."}\n") 75 | end 76 | end 77 | end 78 | end 79 | end 80 | result = pandoc.utils.stringify(StyleCSSTeX) 81 | if fmt == "html" then result = "" end 82 | if fmt == "pdf" then result="%%==== colors from yaml ===%\n"..result.."%=============%\n" end 83 | return(result) 84 | end 85 | 86 | local preamblestuff = extractStyleFromMeta(fmt) 87 | -- quarto.log.output(preamblestuff) 88 | 89 | if fmt == "html" 90 | then 91 | quarto.doc.add_html_dependency({ 92 | name = 'foldbox', 93 | -- version = '0.0.1', 94 | --stylesheets = {'style/'..style..'.css'} 95 | stylesheets = {'style/foldbox.css'} 96 | }) 97 | elseif fmt == "pdf" 98 | then 99 | quarto.doc.use_latex_package("tcolorbox","many") 100 | quarto.doc.include_file("in-header", 'style/foldbox.tex') 101 | end 102 | if preamblestuff then quarto.doc.include_text("in-header", preamblestuff) end 103 | return(doc) 104 | end 105 | } -------------------------------------------------------------------------------- /_extensions/custom-numbered-blocks/style/foldbox.tex: -------------------------------------------------------------------------------- 1 | %%%% ---foldboxy preamble ----- %%%%% 2 | 3 | \definecolor{fbx-default-color1}{HTML}{c7c7d0} 4 | \definecolor{fbx-default-color2}{HTML}{a3a3aa} 5 | 6 | \definecolor{fbox-color1}{HTML}{c7c7d0} 7 | \definecolor{fbox-color2}{HTML}{a3a3aa} 8 | 9 | % arguments: #1 typelabelnummer: #2 titel: #3 10 | \newenvironment{fbx}[3]{\begin{tcolorbox}[enhanced, breakable,% 11 | attach boxed title to top*={xshift=1.4pt}, 12 | boxed title style={boxrule=0.0mm, fuzzy shadow={1pt}{-1pt}{0mm}{0.1mm}{gray}, arc=.3em, rounded corners=east, sharp corners=west}, colframe=#1-color2, colbacktitle=#1-color1, colback = white, coltitle=black, titlerule=0mm, toprule=0pt, bottomrule=.7pt, leftrule=.3em, rightrule=0pt, outer arc=.3em, arc=0pt, sharp corners = east, left=.5em, bottomtitle=1mm, toptitle=1mm,title=\textbf{#2}\hspace{0.5em}{#3}]} 13 | {\end{tcolorbox}} 14 | 15 | % boxed environment with right border 16 | \newenvironment{fbxSimple}[3]{\begin{tcolorbox}[enhanced, breakable,% 17 | attach boxed title to top*={xshift=1.4pt}, 18 | boxed title style={boxrule=0.0mm, fuzzy shadow={1pt}{-1pt}{0mm}{0.1mm}{gray}, arc=.3em, rounded corners=east, sharp corners=west}, colframe=#1-color2, colbacktitle=#1-color1, colback = white, coltitle=black, titlerule=0mm, toprule=0pt, bottomrule=.7pt, leftrule=.3em, rightrule=.7pt, outer arc=.3em, left=.5em, right=.5em, bottomtitle=1mm, toptitle=1mm,title=\textbf{#2}\hspace{0.5em}{#3}]} 19 | {\end{tcolorbox}} 20 | 21 | %%%% --- end foldboxy preamble ----- %%%%% 22 | -------------------------------------------------------------------------------- /doc/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Custom-numbered-blocks Example 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 |
77 | 78 |
79 |
80 |

Custom-numbered-blocks Example

81 |
82 | 83 | 84 | 85 |
86 | 87 | 88 | 89 | 90 |
91 | 92 | 93 |
94 | 95 |
96 |

1 Custom blocks and crossreferencing

97 |

With this filter, you can define custom div classes (environments) that come with numbering, such as theorems, examples, exercises. The filter supports output formats pdf and html.

98 |
99 |
Feature 1.1: Numbering
100 |

Numbering is (currently) within section for single documents, or within chapter for books. Grouped classes share the same counter, and the same default style.

101 |

Numbered custom blocks can be cross-referenced with \ref.

102 |

Default numbering can be switched off for the whole class by setting the numbered to false, or for an individual block by adding the class unnumbered.

103 |

Crossreferences my need a re-run to update.

104 |
105 |

Feature: Boxes can be nested
However, inner boxes are not numbered – it would be hard to put them in a sequence with outer boxes anyway.

106 |
107 |
108 |
109 |
110 |
111 |

Feature 1.2: Block box style
The default style for custom divs is foldbox: a collapsible similar to quarto’s callouts, with a collapse button at the bottom that makes it easier collapse long boxes, and box open to the right. It comes with the variant foldbox.simple, with closed box and no additional close button. (needs a fix for the moment)

112 |
113 |
114 |
115 |
To do 1.1: Custom styles
116 |
    117 |
  • create an API for user defined block styles
  • 118 |
  • provide an example
  • 119 |
  • and documentation
  • 120 |
121 |
122 |
123 |
124 |

Done, may change 1.2: Custom list of blocks
Generate .qmd files that contains a list of selected block classes, intermitted with headers from the document for easy customization and commenting. This way, one can make a list of all definitions, examples, or {theorems, propositions and lemmas} etc., edit it later and attach to the main document. If you edit, make sure to rename the autogenerated list first, otherwise it will get overwritten in the next run and all work is lost …

125 |

Currently, you need to give a key listin for any class or group of classes that should appear in a list of things. The value of this key is an array of names, also if only one list is to be generated. These names are turned into files list-of-name.qmd. I am considering replacing the yaml interface by a sub-key to custom-numbered-classes. This would allow to define arbitrary classes that can be attached to any custom div block, such as .important.

126 |
127 |
128 |
129 |
130 |

2 Pseudomath examples

131 |
132 |

Definition 2.1: F\(\alpha\)ncybox
A box is called f\(\alpha\)ncybox if it looks quite fancy.

133 |

In this context, by fancy we mean that the title of the box appears as a clickable button when rendered as html, where clickable implies that it throws a small shadow that becomes bigger when hovering over it.

134 |
135 |
136 |
137 |

Definition 2.2
A box is called f\(\alpha\)ncybox if it looks quite fancy.

138 |

In this context, by fancy we mean that the title of the box appears as a clickable button when rendered as html, where clickable implies that it throws a small shadow that becomes bigger when hovering over it.

139 |
140 |
141 |
142 |

Corollary 2.3
By Definition 2.1, foldboxes are fancyboxes.

143 |
144 |
145 |
146 |

Conjecture 2.4
Students are lured into clicking on the title and unfolding the fancybox.

147 |
148 |
149 |
150 |

Theorem 2.5
This extension has been written by a teacher who hopes that students read the course notes…

151 |
152 |
153 |

Theorem 2.5 is suggested by Conjecture 2.4, but it cannot be proven theoretically. It does give rise to more conjectures, though. 2.1

154 |
155 |

Conjecture
The teacher mentioned in Theorem 2.5 is a statistician who got addicted to quarto due to James J Balamuta’s web-r extension, and desparately wanted to have a common counter for theorem and alike. She got also convinced that everything is possible in quarto by the many nice extensions from Shafayet Khan Shafee.

156 |
157 |
158 | 182 |
183 | 184 |
185 | 186 | 319 |
320 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /doc/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ute/custom-numbered-blocks/7d524020cd2bf44b7071da2e354b98b6478e1823/doc/example.pdf -------------------------------------------------------------------------------- /doc/example_files/libs/bootstrap/bootstrap-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ute/custom-numbered-blocks/7d524020cd2bf44b7071da2e354b98b6478e1823/doc/example_files/libs/bootstrap/bootstrap-icons.woff -------------------------------------------------------------------------------- /doc/example_files/libs/clipboard/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.10 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 summary{ 26 | background-color: var(--title-background-color); 27 | color: var(--title-color); 28 | 29 | cursor: pointer; 30 | 31 | border-radius: var(--border-radius); 32 | border-left: var(--border-radius) solid var(--border-color); 33 | border-bottom: 1px solid var(--border-color); 34 | box-shadow: var(--thinshadow); 35 | 36 | padding: var(--title-padding); 37 | padding-left: var(--padding-left); 38 | } 39 | 40 | details.fbx-default > summary:hover{ 41 | box-shadow: var(--thickshadow); 42 | } 43 | 44 | details.fbx-default[open] > summary{ 45 | border-bottom-left-radius: 0px; 46 | } 47 | 48 | details.fbx-default > div{ 49 | padding: var(--padding-content); 50 | padding-left: var(--padding-left); 51 | 52 | border-left: var(--border-radius) solid var(--border-color); 53 | border-top: 0; 54 | margin-top: 0; 55 | border-left: .3rem solid var(--border-color); 56 | border-bottom: 1px solid var(--border-color); 57 | border-bottom-left-radius: .3rem; 58 | } 59 | 60 | /* simple box */ 61 | 62 | details.fbx-simplebox > div{ 63 | border-right: 1px solid var(--border-color); 64 | border-bottom-right-radius: .3rem; 65 | } 66 | 67 | 68 | /* ------ closebutton --- */ 69 | 70 | .closebutton{ 71 | --close-text: "▲"; 72 | --close-background-color: #dddddd; 73 | --close-hover-background-color: var(--title-background-color); 74 | --close-hover-color: var(--title-color); 75 | } 76 | 77 | /* adjust this by hand if close button gets in the way: just add
at the end 78 | details.closebutton[open]{ 79 | padding-bottom: 1rem; 80 | } 81 | */ 82 | 83 | details.closebutton[open] > summary::after { 84 | display: button; 85 | content: var(--close-text); 86 | /* 87 | background-color: var(--close-background-color); 88 | */ 89 | border: 1px solid var(--close-background-color); 90 | border-radius: 0.3rem; 91 | padding: 0.2rem 0.5rem 0.2rem 0.5rem; 92 | box-shadow: var(--thinshadow); 93 | position: absolute; 94 | right: 0;/*1rem;*/ 95 | bottom: .5rem; 96 | } 97 | 98 | details.closebutton[open] > summary:hover::after { 99 | background-color: var(--close-hover-background-color); 100 | border: 1px solid var(--border-color); 101 | color: var(--close-hover-color); 102 | box-shadow: var(--thickshadow); 103 | } 104 | 105 | 106 | -------------------------------------------------------------------------------- /doc/example_files/libs/quarto-html/anchor.min.js: -------------------------------------------------------------------------------- 1 | // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat 2 | // 3 | // AnchorJS - v4.3.1 - 2021-04-17 4 | // https://www.bryanbraun.com/anchorjs/ 5 | // Copyright (c) 2021 Bryan Braun; Licensed MIT 6 | // 7 | // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat 8 | !function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(this,function(){"use strict";return function(A){function d(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function w(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],d(this.options),this.isTouchDevice=function(){return Boolean("ontouchstart"in window||window.TouchEvent||window.DocumentTouch&&document instanceof DocumentTouch)},this.add=function(A){var e,t,o,i,n,s,a,c,r,l,h,u,p=[];if(d(this.options),"touch"===(l=this.options.visible)&&(l=this.isTouchDevice()?"always":"hover"),0===(e=w(A=A||"h2, h3, h4, h5, h6")).length)return this;for(null===document.head.querySelector("style.anchorjs")&&((u=document.createElement("style")).className="anchorjs",u.appendChild(document.createTextNode("")),void 0===(A=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(u):document.head.insertBefore(u,A),u.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",u.sheet.cssRules.length),u.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); 9 | // @license-end -------------------------------------------------------------------------------- /doc/example_files/libs/quarto-html/popper.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @popperjs/core v2.11.4 - MIT License 3 | */ 4 | 5 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function q(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[A,D].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=Y(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var pe={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); 6 | 7 | -------------------------------------------------------------------------------- /doc/example_files/libs/quarto-html/quarto-syntax-highlighting.css: -------------------------------------------------------------------------------- 1 | /* quarto syntax highlight colors */ 2 | :root { 3 | --quarto-hl-ot-color: #003B4F; 4 | --quarto-hl-at-color: #657422; 5 | --quarto-hl-ss-color: #20794D; 6 | --quarto-hl-an-color: #5E5E5E; 7 | --quarto-hl-fu-color: #4758AB; 8 | --quarto-hl-st-color: #20794D; 9 | --quarto-hl-cf-color: #003B4F; 10 | --quarto-hl-op-color: #5E5E5E; 11 | --quarto-hl-er-color: #AD0000; 12 | --quarto-hl-bn-color: #AD0000; 13 | --quarto-hl-al-color: #AD0000; 14 | --quarto-hl-va-color: #111111; 15 | --quarto-hl-bu-color: inherit; 16 | --quarto-hl-ex-color: inherit; 17 | --quarto-hl-pp-color: #AD0000; 18 | --quarto-hl-in-color: #5E5E5E; 19 | --quarto-hl-vs-color: #20794D; 20 | --quarto-hl-wa-color: #5E5E5E; 21 | --quarto-hl-do-color: #5E5E5E; 22 | --quarto-hl-im-color: #00769E; 23 | --quarto-hl-ch-color: #20794D; 24 | --quarto-hl-dt-color: #AD0000; 25 | --quarto-hl-fl-color: #AD0000; 26 | --quarto-hl-co-color: #5E5E5E; 27 | --quarto-hl-cv-color: #5E5E5E; 28 | --quarto-hl-cn-color: #8f5902; 29 | --quarto-hl-sc-color: #5E5E5E; 30 | --quarto-hl-dv-color: #AD0000; 31 | --quarto-hl-kw-color: #003B4F; 32 | } 33 | 34 | /* other quarto variables */ 35 | :root { 36 | --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 37 | } 38 | 39 | pre > code.sourceCode > span { 40 | color: #003B4F; 41 | } 42 | 43 | code span { 44 | color: #003B4F; 45 | } 46 | 47 | code.sourceCode > span { 48 | color: #003B4F; 49 | } 50 | 51 | div.sourceCode, 52 | div.sourceCode pre.sourceCode { 53 | color: #003B4F; 54 | } 55 | 56 | code span.ot { 57 | color: #003B4F; 58 | } 59 | 60 | code span.at { 61 | color: #657422; 62 | } 63 | 64 | code span.ss { 65 | color: #20794D; 66 | } 67 | 68 | code span.an { 69 | color: #5E5E5E; 70 | } 71 | 72 | code span.fu { 73 | color: #4758AB; 74 | } 75 | 76 | code span.st { 77 | color: #20794D; 78 | } 79 | 80 | code span.cf { 81 | color: #003B4F; 82 | } 83 | 84 | code span.op { 85 | color: #5E5E5E; 86 | } 87 | 88 | code span.er { 89 | color: #AD0000; 90 | } 91 | 92 | code span.bn { 93 | color: #AD0000; 94 | } 95 | 96 | code span.al { 97 | color: #AD0000; 98 | } 99 | 100 | code span.va { 101 | color: #111111; 102 | } 103 | 104 | code span.pp { 105 | color: #AD0000; 106 | } 107 | 108 | code span.in { 109 | color: #5E5E5E; 110 | } 111 | 112 | code span.vs { 113 | color: #20794D; 114 | } 115 | 116 | code span.wa { 117 | color: #5E5E5E; 118 | font-style: italic; 119 | } 120 | 121 | code span.do { 122 | color: #5E5E5E; 123 | font-style: italic; 124 | } 125 | 126 | code span.im { 127 | color: #00769E; 128 | } 129 | 130 | code span.ch { 131 | color: #20794D; 132 | } 133 | 134 | code span.dt { 135 | color: #AD0000; 136 | } 137 | 138 | code span.fl { 139 | color: #AD0000; 140 | } 141 | 142 | code span.co { 143 | color: #5E5E5E; 144 | } 145 | 146 | code span.cv { 147 | color: #5E5E5E; 148 | font-style: italic; 149 | } 150 | 151 | code span.cn { 152 | color: #8f5902; 153 | } 154 | 155 | code span.sc { 156 | color: #5E5E5E; 157 | } 158 | 159 | code span.dv { 160 | color: #AD0000; 161 | } 162 | 163 | code span.kw { 164 | color: #003B4F; 165 | } 166 | 167 | .prevent-inlining { 168 | content: " { 17 | const sibling = el.previousElementSibling; 18 | if (sibling && sibling.tagName === "A") { 19 | return sibling.classList.contains("active"); 20 | } else { 21 | return false; 22 | } 23 | }; 24 | 25 | // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) 26 | function fireSlideEnter(e) { 27 | const event = window.document.createEvent("Event"); 28 | event.initEvent("slideenter", true, true); 29 | window.document.dispatchEvent(event); 30 | } 31 | const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); 32 | tabs.forEach((tab) => { 33 | tab.addEventListener("shown.bs.tab", fireSlideEnter); 34 | }); 35 | 36 | // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) 37 | document.addEventListener("tabby", fireSlideEnter, false); 38 | 39 | // Track scrolling and mark TOC links as active 40 | // get table of contents and sidebar (bail if we don't have at least one) 41 | const tocLinks = tocEl 42 | ? [...tocEl.querySelectorAll("a[data-scroll-target]")] 43 | : []; 44 | const makeActive = (link) => tocLinks[link].classList.add("active"); 45 | const removeActive = (link) => tocLinks[link].classList.remove("active"); 46 | const removeAllActive = () => 47 | [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); 48 | 49 | // activate the anchor for a section associated with this TOC entry 50 | tocLinks.forEach((link) => { 51 | link.addEventListener("click", () => { 52 | if (link.href.indexOf("#") !== -1) { 53 | const anchor = link.href.split("#")[1]; 54 | const heading = window.document.querySelector( 55 | `[data-anchor-id=${anchor}]` 56 | ); 57 | if (heading) { 58 | // Add the class 59 | heading.classList.add("reveal-anchorjs-link"); 60 | 61 | // function to show the anchor 62 | const handleMouseout = () => { 63 | heading.classList.remove("reveal-anchorjs-link"); 64 | heading.removeEventListener("mouseout", handleMouseout); 65 | }; 66 | 67 | // add a function to clear the anchor when the user mouses out of it 68 | heading.addEventListener("mouseout", handleMouseout); 69 | } 70 | } 71 | }); 72 | }); 73 | 74 | const sections = tocLinks.map((link) => { 75 | const target = link.getAttribute("data-scroll-target"); 76 | if (target.startsWith("#")) { 77 | return window.document.getElementById(decodeURI(`${target.slice(1)}`)); 78 | } else { 79 | return window.document.querySelector(decodeURI(`${target}`)); 80 | } 81 | }); 82 | 83 | const sectionMargin = 200; 84 | let currentActive = 0; 85 | // track whether we've initialized state the first time 86 | let init = false; 87 | 88 | const updateActiveLink = () => { 89 | // The index from bottom to top (e.g. reversed list) 90 | let sectionIndex = -1; 91 | if ( 92 | window.innerHeight + window.pageYOffset >= 93 | window.document.body.offsetHeight 94 | ) { 95 | sectionIndex = 0; 96 | } else { 97 | sectionIndex = [...sections].reverse().findIndex((section) => { 98 | if (section) { 99 | return window.pageYOffset >= section.offsetTop - sectionMargin; 100 | } else { 101 | return false; 102 | } 103 | }); 104 | } 105 | if (sectionIndex > -1) { 106 | const current = sections.length - sectionIndex - 1; 107 | if (current !== currentActive) { 108 | removeAllActive(); 109 | currentActive = current; 110 | makeActive(current); 111 | if (init) { 112 | window.dispatchEvent(sectionChanged); 113 | } 114 | init = true; 115 | } 116 | } 117 | }; 118 | 119 | const inHiddenRegion = (top, bottom, hiddenRegions) => { 120 | for (const region of hiddenRegions) { 121 | if (top <= region.bottom && bottom >= region.top) { 122 | return true; 123 | } 124 | } 125 | return false; 126 | }; 127 | 128 | const categorySelector = "header.quarto-title-block .quarto-category"; 129 | const activateCategories = (href) => { 130 | // Find any categories 131 | // Surround them with a link pointing back to: 132 | // #category=Authoring 133 | try { 134 | const categoryEls = window.document.querySelectorAll(categorySelector); 135 | for (const categoryEl of categoryEls) { 136 | const categoryText = categoryEl.textContent; 137 | if (categoryText) { 138 | const link = `${href}#category=${encodeURIComponent(categoryText)}`; 139 | const linkEl = window.document.createElement("a"); 140 | linkEl.setAttribute("href", link); 141 | for (const child of categoryEl.childNodes) { 142 | linkEl.append(child); 143 | } 144 | categoryEl.appendChild(linkEl); 145 | } 146 | } 147 | } catch { 148 | // Ignore errors 149 | } 150 | }; 151 | function hasTitleCategories() { 152 | return window.document.querySelector(categorySelector) !== null; 153 | } 154 | 155 | function offsetRelativeUrl(url) { 156 | const offset = getMeta("quarto:offset"); 157 | return offset ? offset + url : url; 158 | } 159 | 160 | function offsetAbsoluteUrl(url) { 161 | const offset = getMeta("quarto:offset"); 162 | const baseUrl = new URL(offset, window.location); 163 | 164 | const projRelativeUrl = url.replace(baseUrl, ""); 165 | if (projRelativeUrl.startsWith("/")) { 166 | return projRelativeUrl; 167 | } else { 168 | return "/" + projRelativeUrl; 169 | } 170 | } 171 | 172 | // read a meta tag value 173 | function getMeta(metaName) { 174 | const metas = window.document.getElementsByTagName("meta"); 175 | for (let i = 0; i < metas.length; i++) { 176 | if (metas[i].getAttribute("name") === metaName) { 177 | return metas[i].getAttribute("content"); 178 | } 179 | } 180 | return ""; 181 | } 182 | 183 | async function findAndActivateCategories() { 184 | const currentPagePath = offsetAbsoluteUrl(window.location.href); 185 | const response = await fetch(offsetRelativeUrl("listings.json")); 186 | if (response.status == 200) { 187 | return response.json().then(function (listingPaths) { 188 | const listingHrefs = []; 189 | for (const listingPath of listingPaths) { 190 | const pathWithoutLeadingSlash = listingPath.listing.substring(1); 191 | for (const item of listingPath.items) { 192 | if ( 193 | item === currentPagePath || 194 | item === currentPagePath + "index.html" 195 | ) { 196 | // Resolve this path against the offset to be sure 197 | // we already are using the correct path to the listing 198 | // (this adjusts the listing urls to be rooted against 199 | // whatever root the page is actually running against) 200 | const relative = offsetRelativeUrl(pathWithoutLeadingSlash); 201 | const baseUrl = window.location; 202 | const resolvedPath = new URL(relative, baseUrl); 203 | listingHrefs.push(resolvedPath.pathname); 204 | break; 205 | } 206 | } 207 | } 208 | 209 | // Look up the tree for a nearby linting and use that if we find one 210 | const nearestListing = findNearestParentListing( 211 | offsetAbsoluteUrl(window.location.pathname), 212 | listingHrefs 213 | ); 214 | if (nearestListing) { 215 | activateCategories(nearestListing); 216 | } else { 217 | // See if the referrer is a listing page for this item 218 | const referredRelativePath = offsetAbsoluteUrl(document.referrer); 219 | const referrerListing = listingHrefs.find((listingHref) => { 220 | const isListingReferrer = 221 | listingHref === referredRelativePath || 222 | listingHref === referredRelativePath + "index.html"; 223 | return isListingReferrer; 224 | }); 225 | 226 | if (referrerListing) { 227 | // Try to use the referrer if possible 228 | activateCategories(referrerListing); 229 | } else if (listingHrefs.length > 0) { 230 | // Otherwise, just fall back to the first listing 231 | activateCategories(listingHrefs[0]); 232 | } 233 | } 234 | }); 235 | } 236 | } 237 | if (hasTitleCategories()) { 238 | findAndActivateCategories(); 239 | } 240 | 241 | const findNearestParentListing = (href, listingHrefs) => { 242 | if (!href || !listingHrefs) { 243 | return undefined; 244 | } 245 | // Look up the tree for a nearby linting and use that if we find one 246 | const relativeParts = href.substring(1).split("/"); 247 | while (relativeParts.length > 0) { 248 | const path = relativeParts.join("/"); 249 | for (const listingHref of listingHrefs) { 250 | if (listingHref.startsWith(path)) { 251 | return listingHref; 252 | } 253 | } 254 | relativeParts.pop(); 255 | } 256 | 257 | return undefined; 258 | }; 259 | 260 | const manageSidebarVisiblity = (el, placeholderDescriptor) => { 261 | let isVisible = true; 262 | 263 | return (hiddenRegions) => { 264 | if (el === null) { 265 | return; 266 | } 267 | 268 | // Find the last element of the TOC 269 | const lastChildEl = el.lastElementChild; 270 | 271 | if (lastChildEl) { 272 | // Find the top and bottom o the element that is being managed 273 | const elTop = el.offsetTop; 274 | const elBottom = 275 | elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; 276 | 277 | // Converts the sidebar to a menu 278 | const convertToMenu = () => { 279 | for (const child of el.children) { 280 | child.style.opacity = 0; 281 | child.style.overflow = "hidden"; 282 | } 283 | 284 | const toggleContainer = window.document.createElement("div"); 285 | toggleContainer.style.width = "100%"; 286 | toggleContainer.classList.add("zindex-over-content"); 287 | toggleContainer.classList.add("quarto-sidebar-toggle"); 288 | toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom 289 | toggleContainer.id = placeholderDescriptor.id; 290 | toggleContainer.style.position = "fixed"; 291 | 292 | const toggleIcon = window.document.createElement("i"); 293 | toggleIcon.classList.add("quarto-sidebar-toggle-icon"); 294 | toggleIcon.classList.add("bi"); 295 | toggleIcon.classList.add("bi-caret-down-fill"); 296 | 297 | const toggleTitle = window.document.createElement("div"); 298 | const titleEl = window.document.body.querySelector( 299 | placeholderDescriptor.titleSelector 300 | ); 301 | if (titleEl) { 302 | toggleTitle.append(titleEl.innerText, toggleIcon); 303 | } 304 | toggleTitle.classList.add("zindex-over-content"); 305 | toggleTitle.classList.add("quarto-sidebar-toggle-title"); 306 | toggleContainer.append(toggleTitle); 307 | 308 | const toggleContents = window.document.createElement("div"); 309 | toggleContents.classList = el.classList; 310 | toggleContents.classList.add("zindex-over-content"); 311 | toggleContents.classList.add("quarto-sidebar-toggle-contents"); 312 | for (const child of el.children) { 313 | if (child.id === "toc-title") { 314 | continue; 315 | } 316 | 317 | const clone = child.cloneNode(true); 318 | clone.style.opacity = 1; 319 | clone.style.display = null; 320 | toggleContents.append(clone); 321 | } 322 | toggleContents.style.height = "0px"; 323 | toggleContainer.append(toggleContents); 324 | el.parentElement.prepend(toggleContainer); 325 | 326 | // Process clicks 327 | let tocShowing = false; 328 | // Allow the caller to control whether this is dismissed 329 | // when it is clicked (e.g. sidebar navigation supports 330 | // opening and closing the nav tree, so don't dismiss on click) 331 | const clickEl = placeholderDescriptor.dismissOnClick 332 | ? toggleContainer 333 | : toggleTitle; 334 | 335 | const closeToggle = () => { 336 | if (tocShowing) { 337 | toggleContainer.classList.remove("expanded"); 338 | toggleContents.style.height = "0px"; 339 | tocShowing = false; 340 | } 341 | }; 342 | 343 | const positionToggle = () => { 344 | // position the element (top left of parent, same width as parent) 345 | const elRect = el.getBoundingClientRect(); 346 | toggleContainer.style.left = `${elRect.left}px`; 347 | toggleContainer.style.top = `${elRect.top}px`; 348 | toggleContainer.style.width = `${elRect.width}px`; 349 | }; 350 | 351 | // Get rid of any expanded toggle if the user scrolls 352 | window.document.addEventListener( 353 | "scroll", 354 | throttle(() => { 355 | closeToggle(); 356 | }, 50) 357 | ); 358 | 359 | // Handle positioning of the toggle 360 | window.addEventListener( 361 | "resize", 362 | throttle(() => { 363 | positionToggle(); 364 | }, 50) 365 | ); 366 | positionToggle(); 367 | 368 | // Process the click 369 | clickEl.onclick = () => { 370 | if (!tocShowing) { 371 | toggleContainer.classList.add("expanded"); 372 | toggleContents.style.height = null; 373 | tocShowing = true; 374 | } else { 375 | closeToggle(); 376 | } 377 | }; 378 | }; 379 | 380 | // Converts a sidebar from a menu back to a sidebar 381 | const convertToSidebar = () => { 382 | for (const child of el.children) { 383 | child.style.opacity = 1; 384 | child.style.overflow = null; 385 | } 386 | 387 | const placeholderEl = window.document.getElementById( 388 | placeholderDescriptor.id 389 | ); 390 | if (placeholderEl) { 391 | placeholderEl.remove(); 392 | } 393 | 394 | el.classList.remove("rollup"); 395 | }; 396 | 397 | if (isReaderMode()) { 398 | convertToMenu(); 399 | isVisible = false; 400 | } else { 401 | if (!isVisible) { 402 | // If the element is current not visible reveal if there are 403 | // no conflicts with overlay regions 404 | if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { 405 | convertToSidebar(); 406 | isVisible = true; 407 | } 408 | } else { 409 | // If the element is visible, hide it if it conflicts with overlay regions 410 | // and insert a placeholder toggle (or if we're in reader mode) 411 | if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { 412 | convertToMenu(); 413 | isVisible = false; 414 | } 415 | } 416 | } 417 | } 418 | }; 419 | }; 420 | 421 | // Find any conflicting margin elements and add margins to the 422 | // top to prevent overlap 423 | const marginChildren = window.document.querySelectorAll( 424 | ".column-margin.column-container > * " 425 | ); 426 | 427 | nexttick(() => { 428 | let lastBottom = 0; 429 | for (const marginChild of marginChildren) { 430 | const top = marginChild.getBoundingClientRect().top + window.scrollY; 431 | if (top < lastBottom) { 432 | const margin = lastBottom - top; 433 | marginChild.style.marginTop = `${margin}px`; 434 | } 435 | const styles = window.getComputedStyle(marginChild); 436 | const marginTop = parseFloat(styles["marginTop"]); 437 | 438 | lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; 439 | } 440 | }); 441 | 442 | // Manage the visibility of the toc and the sidebar 443 | const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { 444 | id: "quarto-toc-toggle", 445 | titleSelector: "#toc-title", 446 | dismissOnClick: true, 447 | }); 448 | const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { 449 | id: "quarto-sidebarnav-toggle", 450 | titleSelector: ".title", 451 | dismissOnClick: false, 452 | }); 453 | let tocLeftScrollVisibility; 454 | if (leftTocEl) { 455 | tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { 456 | id: "quarto-lefttoc-toggle", 457 | titleSelector: "#toc-title", 458 | dismissOnClick: true, 459 | }); 460 | } 461 | 462 | // Find the first element that uses formatting in special columns 463 | const conflictingEls = window.document.body.querySelectorAll( 464 | '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' 465 | ); 466 | 467 | // Filter all the possibly conflicting elements into ones 468 | // the do conflict on the left or ride side 469 | const arrConflictingEls = Array.from(conflictingEls); 470 | const leftSideConflictEls = arrConflictingEls.filter((el) => { 471 | if (el.tagName === "ASIDE") { 472 | return false; 473 | } 474 | return Array.from(el.classList).find((className) => { 475 | return ( 476 | className !== "column-body" && 477 | className.startsWith("column-") && 478 | !className.endsWith("right") && 479 | !className.endsWith("container") && 480 | className !== "column-margin" 481 | ); 482 | }); 483 | }); 484 | const rightSideConflictEls = arrConflictingEls.filter((el) => { 485 | if (el.tagName === "ASIDE") { 486 | return true; 487 | } 488 | 489 | const hasMarginCaption = Array.from(el.classList).find((className) => { 490 | return className == "margin-caption"; 491 | }); 492 | if (hasMarginCaption) { 493 | return true; 494 | } 495 | 496 | return Array.from(el.classList).find((className) => { 497 | return ( 498 | className !== "column-body" && 499 | !className.endsWith("container") && 500 | className.startsWith("column-") && 501 | !className.endsWith("left") 502 | ); 503 | }); 504 | }); 505 | 506 | const kOverlapPaddingSize = 10; 507 | function toRegions(els) { 508 | return els.map((el) => { 509 | const top = 510 | el.getBoundingClientRect().top + 511 | document.documentElement.scrollTop - 512 | kOverlapPaddingSize; 513 | return { 514 | top, 515 | bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, 516 | }; 517 | }); 518 | } 519 | 520 | const hideOverlappedSidebars = () => { 521 | marginScrollVisibility(toRegions(rightSideConflictEls)); 522 | sidebarScrollVisiblity(toRegions(leftSideConflictEls)); 523 | if (tocLeftScrollVisibility) { 524 | tocLeftScrollVisibility(toRegions(leftSideConflictEls)); 525 | } 526 | }; 527 | 528 | window.quartoToggleReader = () => { 529 | // Applies a slow class (or removes it) 530 | // to update the transition speed 531 | const slowTransition = (slow) => { 532 | const manageTransition = (id, slow) => { 533 | const el = document.getElementById(id); 534 | if (el) { 535 | if (slow) { 536 | el.classList.add("slow"); 537 | } else { 538 | el.classList.remove("slow"); 539 | } 540 | } 541 | }; 542 | 543 | manageTransition("TOC", slow); 544 | manageTransition("quarto-sidebar", slow); 545 | }; 546 | 547 | const readerMode = !isReaderMode(); 548 | setReaderModeValue(readerMode); 549 | 550 | // If we're entering reader mode, slow the transition 551 | if (readerMode) { 552 | slowTransition(readerMode); 553 | } 554 | highlightReaderToggle(readerMode); 555 | hideOverlappedSidebars(); 556 | 557 | // If we're exiting reader mode, restore the non-slow transition 558 | if (!readerMode) { 559 | slowTransition(!readerMode); 560 | } 561 | }; 562 | 563 | const highlightReaderToggle = (readerMode) => { 564 | const els = document.querySelectorAll(".quarto-reader-toggle"); 565 | if (els) { 566 | els.forEach((el) => { 567 | if (readerMode) { 568 | el.classList.add("reader"); 569 | } else { 570 | el.classList.remove("reader"); 571 | } 572 | }); 573 | } 574 | }; 575 | 576 | const setReaderModeValue = (val) => { 577 | if (window.location.protocol !== "file:") { 578 | window.localStorage.setItem("quarto-reader-mode", val); 579 | } else { 580 | localReaderMode = val; 581 | } 582 | }; 583 | 584 | const isReaderMode = () => { 585 | if (window.location.protocol !== "file:") { 586 | return window.localStorage.getItem("quarto-reader-mode") === "true"; 587 | } else { 588 | return localReaderMode; 589 | } 590 | }; 591 | let localReaderMode = null; 592 | 593 | // Walk the TOC and collapse/expand nodes 594 | // Nodes are expanded if: 595 | // - they are top level 596 | // - they have children that are 'active' links 597 | // - they are directly below an link that is 'active' 598 | const walk = (el, depth) => { 599 | // Tick depth when we enter a UL 600 | if (el.tagName === "UL") { 601 | depth = depth + 1; 602 | } 603 | 604 | // It this is active link 605 | let isActiveNode = false; 606 | if (el.tagName === "A" && el.classList.contains("active")) { 607 | isActiveNode = true; 608 | } 609 | 610 | // See if there is an active child to this element 611 | let hasActiveChild = false; 612 | for (child of el.children) { 613 | hasActiveChild = walk(child, depth) || hasActiveChild; 614 | } 615 | 616 | // Process the collapse state if this is an UL 617 | if (el.tagName === "UL") { 618 | if (depth === 1 || hasActiveChild || prevSiblingIsActiveLink(el)) { 619 | el.classList.remove("collapse"); 620 | } else { 621 | el.classList.add("collapse"); 622 | } 623 | 624 | // untick depth when we leave a UL 625 | depth = depth - 1; 626 | } 627 | return hasActiveChild || isActiveNode; 628 | }; 629 | 630 | // walk the TOC and expand / collapse any items that should be shown 631 | 632 | if (tocEl) { 633 | walk(tocEl, 0); 634 | updateActiveLink(); 635 | } 636 | 637 | // Throttle the scroll event and walk peridiocally 638 | window.document.addEventListener( 639 | "scroll", 640 | throttle(() => { 641 | if (tocEl) { 642 | updateActiveLink(); 643 | walk(tocEl, 0); 644 | } 645 | if (!isReaderMode()) { 646 | hideOverlappedSidebars(); 647 | } 648 | }, 5) 649 | ); 650 | window.addEventListener( 651 | "resize", 652 | throttle(() => { 653 | if (!isReaderMode()) { 654 | hideOverlappedSidebars(); 655 | } 656 | }, 10) 657 | ); 658 | hideOverlappedSidebars(); 659 | highlightReaderToggle(isReaderMode()); 660 | }); 661 | 662 | // grouped tabsets 663 | window.addEventListener("pageshow", (_event) => { 664 | function getTabSettings() { 665 | const data = localStorage.getItem("quarto-persistent-tabsets-data"); 666 | if (!data) { 667 | localStorage.setItem("quarto-persistent-tabsets-data", "{}"); 668 | return {}; 669 | } 670 | if (data) { 671 | return JSON.parse(data); 672 | } 673 | } 674 | 675 | function setTabSettings(data) { 676 | localStorage.setItem( 677 | "quarto-persistent-tabsets-data", 678 | JSON.stringify(data) 679 | ); 680 | } 681 | 682 | function setTabState(groupName, groupValue) { 683 | const data = getTabSettings(); 684 | data[groupName] = groupValue; 685 | setTabSettings(data); 686 | } 687 | 688 | function toggleTab(tab, active) { 689 | const tabPanelId = tab.getAttribute("aria-controls"); 690 | const tabPanel = document.getElementById(tabPanelId); 691 | if (active) { 692 | tab.classList.add("active"); 693 | tabPanel.classList.add("active"); 694 | } else { 695 | tab.classList.remove("active"); 696 | tabPanel.classList.remove("active"); 697 | } 698 | } 699 | 700 | function toggleAll(selectedGroup, selectorsToSync) { 701 | for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { 702 | const active = selectedGroup === thisGroup; 703 | for (const tab of tabs) { 704 | toggleTab(tab, active); 705 | } 706 | } 707 | } 708 | 709 | function findSelectorsToSyncByLanguage() { 710 | const result = {}; 711 | const tabs = Array.from( 712 | document.querySelectorAll(`div[data-group] a[id^='tabset-']`) 713 | ); 714 | for (const item of tabs) { 715 | const div = item.parentElement.parentElement.parentElement; 716 | const group = div.getAttribute("data-group"); 717 | if (!result[group]) { 718 | result[group] = {}; 719 | } 720 | const selectorsToSync = result[group]; 721 | const value = item.innerHTML; 722 | if (!selectorsToSync[value]) { 723 | selectorsToSync[value] = []; 724 | } 725 | selectorsToSync[value].push(item); 726 | } 727 | return result; 728 | } 729 | 730 | function setupSelectorSync() { 731 | const selectorsToSync = findSelectorsToSyncByLanguage(); 732 | Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { 733 | Object.entries(tabSetsByValue).forEach(([value, items]) => { 734 | items.forEach((item) => { 735 | item.addEventListener("click", (_event) => { 736 | setTabState(group, value); 737 | toggleAll(value, selectorsToSync[group]); 738 | }); 739 | }); 740 | }); 741 | }); 742 | return selectorsToSync; 743 | } 744 | 745 | const selectorsToSync = setupSelectorSync(); 746 | for (const [group, selectedName] of Object.entries(getTabSettings())) { 747 | const selectors = selectorsToSync[group]; 748 | // it's possible that stale state gives us empty selections, so we explicitly check here. 749 | if (selectors) { 750 | toggleAll(selectedName, selectors); 751 | } 752 | } 753 | }); 754 | 755 | function throttle(func, wait) { 756 | let waiting = false; 757 | return function () { 758 | if (!waiting) { 759 | func.apply(this, arguments); 760 | waiting = true; 761 | setTimeout(function () { 762 | waiting = false; 763 | }, wait); 764 | } 765 | }; 766 | } 767 | 768 | function nexttick(func) { 769 | return setTimeout(func, 0); 770 | } 771 | -------------------------------------------------------------------------------- /doc/example_files/libs/quarto-html/tippy.css: -------------------------------------------------------------------------------- 1 | .tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} -------------------------------------------------------------------------------- /doc/example_files/libs/quarto-html/tippy.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); 2 | 3 | -------------------------------------------------------------------------------- /example.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Custom-numbered-blocks Example" 3 | number-sections: true 4 | filters: 5 | - custom-numbered-blocks 6 | format: 7 | html: default 8 | pdf: default 9 | 10 | custom-numbered-blocks: 11 | groups: 12 | thmlike: 13 | colors: [948bde, 584eab] 14 | boxstyle: foldbox.simple 15 | collapse: false 16 | listin: [mathstuff] 17 | todos: default 18 | classes: 19 | Theorem: 20 | group: thmlike 21 | Corollary: 22 | group: thmlike 23 | Conjecture: 24 | group: thmlike 25 | collapse: true 26 | Definition: 27 | group: thmlike 28 | colors: [d999d3, a01793] 29 | Feature: default 30 | TODO: 31 | label: "To do" 32 | colors: [e7b1b4, 8c3236] 33 | group: todos 34 | listin: [stilltodo] 35 | DONE: 36 | label: "Done" 37 | colors: [cce7b1, 86b754] 38 | group: todos 39 | --- 40 | 41 | # Custom blocks and crossreferencing 42 | 43 | With this filter, you can define custom div classes (environments) that come with numbering, such as theorems, examples, exercises. The filter supports output formats pdf and html. 44 | 45 | ::: Feature 46 | ### Numbering 47 | Numbering is (currently) within section for single documents, or within chapter for books. Grouped classes share the same counter, and the same default style. 48 | 49 | Numbered custom blocks can be cross-referenced with `\ref`. 50 | 51 | Default numbering can be switched off for the whole class by setting the `numbered` to false, or for an individual block by adding the class `unnumbered`. 52 | 53 | Crossreferences my need a re-run to update. 54 | 55 | ::: Feature 56 | ### Boxes can be nested 57 | However, inner boxes are not numbered -- it would be hard to put them in a sequence with outer boxes anyway. 58 | ::: 59 | ::: 60 | 61 | 62 | ::: {.Feature title="Block box style"} 63 | The default style for custom divs is `foldbox`: a collapsible similar to quarto's callouts, with a collapse button at the bottom that makes it easier collapse long boxes, and box open to the right. It comes with the variant `foldbox.simple`, with closed box and no additional close button. (needs a fix for the moment) 64 | ::: 65 | 66 | 67 | ::: TODO 68 | ### Custom styles 69 | - [ ] create an API for user defined block styles 70 | - [ ] provide an example 71 | - [ ] and documentation 72 | ::: 73 | 74 | ::: {.DONE label="Done, may change"} 75 | ### Custom list of blocks 76 | Generate `.qmd` files that contains a list of selected block classes, intermitted with headers from the document for easy customization and commenting. 77 | This way, one can make a list of all definitions, examples, or {theorems, propositions and lemmas} etc., edit it later and attach to the main document. 78 | If you edit, make sure to rename the autogenerated list first, otherwise it will get overwritten in the next run and all work is lost ... 79 | 80 | Currently, you need to give a key `listin` for any class or group of classes that should appear in a list of things. The value of this key is an array of names, also if only one list is to be generated. These names are turned into files `list-of-`name`.qmd`. I am considering replacing the yaml interface by a sub-key to `custom-numbered-classes`. This would allow to define arbitrary classes that can be attached to any custom div block, such as `.important`. 81 | ::: 82 | 83 | # Pseudomath examples 84 | 85 | ::: {.Definition #fancy} 86 | ### F$\alpha$ncybox 87 | A box is called *f$\alpha$ncybox* if it looks quite fancy. 88 | 89 | In this context, by *fancy* we mean that the title of the box appears as a clickable button when rendered as html, where *clickable* implies that it throws a small shadow that becomes bigger when hovering over it. 90 | ::: 91 | 92 | 93 | ::: Corollary 94 | By Definition \ref{fancy}, `foldboxes` are fancyboxes. 95 | ::: 96 | 97 | ::: {.Conjecture #TeachersHope} 98 | Students are lured into clicking on the title and unfolding the fancybox. 99 | ::: 100 | 101 | ::: {.Theorem #TeacherHopes collapse="true"} 102 | This extension has been written by a teacher who hopes that students read the course notes... 103 | ::: 104 | 105 | Theorem \ref{TeacherHopes} is suggested by Conjecture \ref{TeachersHope}, but it cannot be proven theoretically. It does give rise to more conjectures, though. 106 | 107 | ::: {.Conjecture .unnumbered} 108 | The teacher mentioned in Theorem \ref{TeacherHopes} is a statistician who got addicted to quarto due to James J Balamuta's web-r extension, and desparately wanted to have a common counter for theorem and alike. She got also convinced that everything is possible in quarto by the many nice extensions from Shafayet Khan Shafee. 109 | ::: 110 | 111 | 117 | --------------------------------------------------------------------------------