diff --git a/admin/site/content/about.md b/admin/site/content/about.md --- a/admin/site/content/about.md +++ b/admin/site/content/about.md @@ -1,54 +1,54 @@ --- title: "About" menu: main: name: "About" - weight: 6 + weight: 7 --- The Archive of Formal Proofs is a collection of proof libraries, examples, and larger scientific developments, mechanically checked in the theorem prover Isabelle. It is organized in the way of a scientific journal. [Submissions](/submission) are refereed. The archive repository is hosted on [Heptapod](https://foss.heptapod.net/isa-afp/) to provide easy free access to archive entries. The entries are tested and maintained continuously against the current stable release of Isabelle. Older versions of archive entries will remain available. ## Editors The editors of the Archive of Formal Proofs are: * [Manuel Eberl](https://www.in.tum.de/~eberlm/), [Technische Universität München](https://www.tum.de/) * [Gerwin Klein](https://www.cse.unsw.edu.au/~kleing/), [Data61](https://www.data61.csiro.au) * [Tobias Nipkow](https://www.in.tum.de/~nipkow/), [Technische Universität München](https://www.tum.de/) * [Larry Paulson](https://www.cl.cam.ac.uk/users/lcp/), [University of Cambridge](https://www.cam.ac.uk/) * [René Thiemann](http://cl-informatik.uibk.ac.at/users/thiemann/), [University of Innsbruck](https://www.uibk.ac.at/) ## Why We aim to strengthen the community and to foster the development of formal proofs. We hope that the Archive will provide: * a resource of knowledge, examples, and libraries for users, * a large and relevant test bed of theories for Isabelle developers, and * a central, citable place for authors to publish their theories We encourage authors of publications that contain Isabelle developments to make their theories available in the Archive of Formal Proofs and to refer to the archive entry in their publication. It makes it easier for referees to check the validity of theorems (all entries in the Archive are mechanically checked), it makes it easier for readers of the publication to understand details of your development, and it makes it easier to use and build on your work. ## License All entries in the Archive of Formal Proofs are licensed under a [BSD-style License](LICENSE) or the [GNU LGPL](https://www.gnu.org/copyleft/lesser.html). This means they are free to download, free to use, free to change, and free to redistribute with minimal restrictions. The authors retain their full copyright on their original work, including their right to make the development available under another, additional license in the future. ## Website This website is the result of a project from the School of Informatics at the [University of Edinburgh](https://www.ed.ac.uk) by: * Carlin MacKenzie, [AIML](https://aiml.inf.ed.ac.uk) * James Vaughan, [AIAI](https://web.inf.ed.ac.uk/aiai/) * Jacques Fleuriot, [AIAI](https://web.inf.ed.ac.uk/aiai/) Integration and maintenance by: * [Fabian Huch](https://www21.in.tum.de/team/huch), [Technische Universität München](https://www.tum.de/) diff --git a/admin/site/content/statistics.md b/admin/site/content/statistics.md --- a/admin/site/content/statistics.md +++ b/admin/site/content/statistics.md @@ -1,9 +1,9 @@ ---- -title: Statistics -menu: - main: - name: "Statistics" - weight: 5 ---- - +--- +title: Statistics +menu: + main: + name: "Statistics" + weight: 6 +--- + {{< statistics >}} \ No newline at end of file diff --git a/admin/site/content/submission.md b/admin/site/content/submission.md --- a/admin/site/content/submission.md +++ b/admin/site/content/submission.md @@ -1,63 +1,64 @@ ---- -title: Entry Submission -menu: - bottom: - name: "Submission" ---- - -## Submission Guidelines - -**The submission must follow the following Isabelle style rules.** For additional guidelines on Isabelle proofs, also see the this [guide](https://proofcraft.org/blog/isabelle-style.html) (feel free to follow all of these; only the below are mandatory). **Technical details about the submission process and the format of the submission are explained on the submission site.** - -* No use of the commands `sorry` or `back`. -* Instantiations must not use Isabelle-generated names such as `xa` — use Isar, the `subgoal` command or `rename_tac` to avoid such names. -* No use of the command `smt_oracle`. -* If your theories contain calls to `nitpick`, `quickcheck`, or `nunchaku` those calls must include the `expect` parameter. Alternatively the `expect` parameter must be set globally via, e.g. `nitpick_params`. -* `apply` scripts should be indented by subgoal as in the Isabelle distribution. If an `apply` command is applied to a state with `n+1` subgoals, it must be indented by `n` spaces relative to the first `apply` in the sequence. -* Only named lemmas should carry attributes such as `[simp]`. -* We prefer structured Isar proofs over apply style, but do not mandate them. -* If there are proof steps that take significant time, i.e. longer than roughly 1 min, please add a short comment to that step, so maintainers will know what to expect. -* The entry must contain a ROOT file with one session that has the name of the entry. We strongly encourage precisely one session per entry, but exceptions can be made. All sessions must be in group (AFP), and all theory files of the submission must be contained in at least one session. See also the example [ROOT](https://foss.heptapod.net/isa-afp/afp-2020/-/blob/branch/default/thys/Example-Submission/ROOT) file in the [Example submission](/entries/Example-Submission.html). -* The entry should cite all sources that the theories are based on, for example textbooks or research articles containing informal versions of the proofs. - -Your submission must contain an abstract to be displayed on the web site – usually this will be the same as the abstract of your proof document in the root.tex file. You can use LaTeX formulae in this web site abstract, either inline formulae in the form $a+b$ or \\(a+b\\) or display formulae in the form $$a + b$$ or \\\[a + b\\\]. Other occurrences of these characters must be escaped (e.g. \\$ or \\\\(). Note that LaTeX in the title of an entry is _not_ allowed. Most basic LaTeX functionality should be supported. For details on what parts of LaTeX are supported, see the [MathJax documentation.](https://docs.mathjax.org/en/v2.7-latest/tex.html) - -It is possible and encouraged to build on other archive entries in your submission. There is a standardised way to [refer to other AFP entries](/help) in your theories. - -## Submission Form - -Please send your submission [via this web page](https://ci.isabelle.systems/afp-submission/). - -Your submission will be refereed and you will receive notification as soon as possible. If accepted, you must agree to maintain your archive entry or nominate someone else to maintain it. The Isabelle development team will assist with maintenance, but it does not have the resources to fully maintain the complete archive. - -If you have questions regarding your submission, please email [afp-submit@in.tum.de](mailto:afp-submit@in.tum.de). If you need help with Isabelle, please use the [isabelle-users@cl.cam.ac.uk](mailto:isabelle-users@cl.cam.ac.uk) mailing list. It is always a good idea to [subscribe](https://lists.cam.ac.uk/mailman/listinfo/cl-isabelle-users). - -# Updating Entries - -## Change - -The Archive of Formal Proofs is an online resource and therefore more dynamic than a normal scientific journal. Existing entries can and do evolve and can also be updated significantly by their authors. - -This conflicts with the purpose of archiving and preserving entries as they have been submitted and with the purpose of providing a clear and simple interface to readers. - -The AFP deals with this by synchronizing such updates with Isabelle releases: - -* The entries released and visible on the main site are always working with the most recent stable Isabelle version and do not change. -* In the background, the archive maintainers evolve all entries to be up to date with the current Isabelle development version. Authors can contribute changes to this version which is available as a [Heptapod mercurial repository](https://foss.heptapod.net/isa-afp/afp-devel/) or as tar.gz package on the [download page](/download). -* When a new Isabelle version is released, the above mentioned development version of AFP is frozen and turns into the main version displayed on the front page. Older versions (including the original submission) of all entries are archived and remain accessible. - -Significant changes of an entry should be recorded in the metadata of the entry using the keyword "extra-history". The resulting web page should look [something like this](https://www.isa-afp.org/entries/JinjaThreads.html). - -## Monotonicity - -Updating an entry should be mostly monotone: you add new material, but you do not modify existing material in a major way. Ideally, entries (by other people) that build on yours should not be affected. Otherwise you have to liaise with them first. If you intend to carry out major non-monotone changes, you will need to submit a completely new entry (with a description of how it relates to the old one). This should be required only very rarely: AFP entries should be mature enough not to require major changes to their interface (i.e. the main functions and theorems provided). - -Major monotone changes, e.g. adding a new concept rather than more results on existing concepts, may also call for a new entry, but one that builds on the existing one. This depends on how you would like to organize your entries. - -## If you are an author - -The above means that if you are an author and would like to provide a new, better version of your AFP entry, you can do so. - -To achieve this, you should base your changes on the [mercurial development version](https:/foss.heptapod.net/isa-afp/afp-devel/) of your AFP entry and test it against the current [Isabelle development version](https://isabelle.in.tum.de/devel/). - -If you would like to get write access to your entry in the mercurial repository or if you need assistance, please contact the [editors](/about#editors). +--- +title: Entry Submission +menu: + main: + name: "Submission" + weight: 5 +--- + +## Submission Guidelines + +**The submission must follow the following Isabelle style rules.** For additional guidelines on Isabelle proofs, also see the this [guide](https://proofcraft.org/blog/isabelle-style.html) (feel free to follow all of these; only the below are mandatory). **Technical details about the submission process and the format of the submission are explained on the submission site.** + +* No use of the commands `sorry` or `back`. +* Instantiations must not use Isabelle-generated names such as `xa` — use Isar, the `subgoal` command or `rename_tac` to avoid such names. +* No use of the command `smt_oracle`. +* If your theories contain calls to `nitpick`, `quickcheck`, or `nunchaku` those calls must include the `expect` parameter. Alternatively the `expect` parameter must be set globally via, e.g. `nitpick_params`. +* `apply` scripts should be indented by subgoal as in the Isabelle distribution. If an `apply` command is applied to a state with `n+1` subgoals, it must be indented by `n` spaces relative to the first `apply` in the sequence. +* Only named lemmas should carry attributes such as `[simp]`. +* We prefer structured Isar proofs over apply style, but do not mandate them. +* If there are proof steps that take significant time, i.e. longer than roughly 1 min, please add a short comment to that step, so maintainers will know what to expect. +* The entry must contain a ROOT file with one session that has the name of the entry. We strongly encourage precisely one session per entry, but exceptions can be made. All sessions must be in group (AFP), and all theory files of the submission must be contained in at least one session. See also the example [ROOT](https://foss.heptapod.net/isa-afp/afp-2020/-/blob/branch/default/thys/Example-Submission/ROOT) file in the [Example submission](/entries/Example-Submission.html). +* The entry should cite all sources that the theories are based on, for example textbooks or research articles containing informal versions of the proofs. + +Your submission must contain an abstract to be displayed on the web site – usually this will be the same as the abstract of your proof document in the root.tex file. You can use LaTeX formulae in this web site abstract, either inline formulae in the form $a+b$ or \\(a+b\\) or display formulae in the form $$a + b$$ or \\\[a + b\\\]. Other occurrences of these characters must be escaped (e.g. \\$ or \\\\(). Note that LaTeX in the title of an entry is _not_ allowed. Most basic LaTeX functionality should be supported. For details on what parts of LaTeX are supported, see the [MathJax documentation.](https://docs.mathjax.org/en/v2.7-latest/tex.html) + +It is possible and encouraged to build on other archive entries in your submission. There is a standardised way to [refer to other AFP entries](/help) in your theories. + +## Submission Form + +Please send your submission [via this web page](https://ci.isabelle.systems/afp-submission/). + +Your submission will be refereed and you will receive notification as soon as possible. If accepted, you must agree to maintain your archive entry or nominate someone else to maintain it. The Isabelle development team will assist with maintenance, but it does not have the resources to fully maintain the complete archive. + +If you have questions regarding your submission, please email [afp-submit@in.tum.de](mailto:afp-submit@in.tum.de). If you need help with Isabelle, please use the [isabelle-users@cl.cam.ac.uk](mailto:isabelle-users@cl.cam.ac.uk) mailing list. It is always a good idea to [subscribe](https://lists.cam.ac.uk/mailman/listinfo/cl-isabelle-users). + +# Updating Entries + +## Change + +The Archive of Formal Proofs is an online resource and therefore more dynamic than a normal scientific journal. Existing entries can and do evolve and can also be updated significantly by their authors. + +This conflicts with the purpose of archiving and preserving entries as they have been submitted and with the purpose of providing a clear and simple interface to readers. + +The AFP deals with this by synchronizing such updates with Isabelle releases: + +* The entries released and visible on the main site are always working with the most recent stable Isabelle version and do not change. +* In the background, the archive maintainers evolve all entries to be up to date with the current Isabelle development version. Authors can contribute changes to this version which is available as a [Heptapod mercurial repository](https://foss.heptapod.net/isa-afp/afp-devel/) or as tar.gz package on the [download page](/download). +* When a new Isabelle version is released, the above mentioned development version of AFP is frozen and turns into the main version displayed on the front page. Older versions (including the original submission) of all entries are archived and remain accessible. + +Significant changes of an entry should be recorded in the metadata of the entry using the keyword "extra-history". The resulting web page should look [something like this](https://www.isa-afp.org/entries/JinjaThreads.html). + +## Monotonicity + +Updating an entry should be mostly monotone: you add new material, but you do not modify existing material in a major way. Ideally, entries (by other people) that build on yours should not be affected. Otherwise you have to liaise with them first. If you intend to carry out major non-monotone changes, you will need to submit a completely new entry (with a description of how it relates to the old one). This should be required only very rarely: AFP entries should be mature enough not to require major changes to their interface (i.e. the main functions and theorems provided). + +Major monotone changes, e.g. adding a new concept rather than more results on existing concepts, may also call for a new entry, but one that builds on the existing one. This depends on how you would like to organize your entries. + +## If you are an author + +The above means that if you are an author and would like to provide a new, better version of your AFP entry, you can do so. + +To achieve this, you should base your changes on the [mercurial development version](https:/foss.heptapod.net/isa-afp/afp-devel/) of your AFP entry and test it against the current [Isabelle development version](https://isabelle.in.tum.de/devel/). + +If you would like to get write access to your entry in the mercurial repository or if you need assistance, please contact the [editors](/about#editors). diff --git a/admin/site/themes/afp/assets/sass/main.scss b/admin/site/themes/afp/assets/sass/main.scss --- a/admin/site/themes/afp/assets/sass/main.scss +++ b/admin/site/themes/afp/assets/sass/main.scss @@ -1,1133 +1,1149 @@ //colors $theme: hsl(242, 27%, 24%); $theme-hover: hsl(241, 26%, 36%); $theme-lightest: hsl(261, 36%, 86%); $theme-focus: #acabcf; $link: #0645ad; $link-hover: #0000ee; $link-visited: #42047c; $white: #fff; $lightest-grey: #f2f2f2; $lighter-grey: #ddd; $light-grey: #bbb; $dark-grey: #777; $darker-grey: #555; $darkest-grey: #303030; $black: #000; $black_15: rgba(0, 0, 0, 0.15); //fonts $sans-serif: "Open Sans", sans-serif; $monospace: "Consolas", monospace; $OpenSans: "Open Sans"; $light: 300; $normal: 400; $bold: 700; $border-bottom: 2px $darker-grey solid; //sizes $size-xs: 650px; // for example mobile $size-s: 875px; // for example half desktop window $size-xl: 1800px; // for example 4k desktop %code { font-family: $monospace; background-color: $lightest-grey; color: $darkest-grey; } %button { border: 1px solid $dark-grey; font-size: 1rem; vertical-align: middle; background-color: $white; color: $black; line-height: 0; font-family: $sans-serif; } %button-image { position: relative; top: 0.12em; left: 5px; padding-right: 5px; } %sticky { position: sticky; top: 0; background-color: $white; margin: 0; padding-bottom: 0.5rem; } @font-face { font-family: $OpenSans; font-display: swap; font-style: normal; font-weight: 300; src: local(""), url("/font/open-sans-v18-latin-300.woff2") format("woff2"), url("/font/open-sans-v18-latin-300.woff") format("woff"); } @font-face { font-family: $OpenSans; font-display: swap; font-style: normal; font-weight: 400; src: local(""), url("/font/open-sans-v18-latin-regular.woff2") format("woff2"), url("/font/open-sans-v18-latin-regular.woff") format("woff"); } @font-face { font-family: $OpenSans; font-display: swap; font-style: italic; font-weight: 400; src: local(""), url("/font/open-sans-v18-latin-italic.woff2") format("woff2"), url("/font/open-sans-v18-latin-italic.woff") format("woff"); } @font-face { font-family: $OpenSans; font-display: swap; font-style: normal; font-weight: 700; src: local(""), url("/font/open-sans-v18-latin-700.woff2") format("woff2"), url("/font/open-sans-v18-latin-700.woff") format("woff"); } @font-face { font-family: $OpenSans; font-display: swap; font-style: italic; font-weight: 700; src: local(""), url("/font/open-sans-v18-latin-700italic.woff2") format("woff2"), url("/font/open-sans-v18-latin-700italic.woff") format("woff"); } *::selection { background-color: $theme; color: $white; } body { color: $black; background: $white; font-family: $sans-serif; display: grid; grid-template-columns: #{"max(20%, 200px)"} 1fr; grid-template-areas: "sidebar content"; justify-items: center; margin: 0; } aside { grid-area: sidebar; background-color: $theme; width: 20vw; margin: 0; height: 100%; position: fixed; top: 0; left: 0; min-width: 200px; word-break: break-all; overflow-y: auto; color: $white; nav { display: flex; flex-direction: column; justify-content: space-between; height: 100vh; } ul { list-style: none; padding: 0; } li { padding: 0.5em 2rem; &:hover { background-color: $theme-hover; } } .active { background-color: $theme-hover; } div > a { display: block; text-align: center; } a:not(.entity_ref) { &:link, &:visited { color: $white; } &:focus { outline: 1px $theme-focus solid; outline-offset: 3.5px; } } hr { margin: 2rem; border: none; border-top: 2px solid $dark-grey; } } .logo { width: 40%; height: auto; padding: 10%; max-width: 160px; } li { margin: 0.25em 0; } header { border-bottom: $border-bottom; margin-bottom: 20px; padding: 0.5rem 0; > div { display: flex; justify-content: space-between; padding: 0.5rem 0; } form { padding: 0.5rem 0; float: right; } h1 { padding: 0.5rem 0; margin: 0; clear: both; line-height: 2.75rem; } #search-input { height: 2rem; padding: 7px; width: 175px; } #search-button { height: 2rem; padding: 7px; width: 2rem; &:hover { background-color: $lighter-grey; } &:active { background-color: $light-grey; } } div p { margin: 0; } } .content { grid-area: content; margin: 0 auto 2rem; width: #{"min(80%, 1100px)"}; min-width: 550px; div { line-height: 1.35em; width: 100%; h1 { border-bottom: $border-bottom; } } > div { form { margin: 2rem auto 0; width: 50%; min-width: 290px; padding: 1rem 0; } #search-button { height: 2.5rem; width: 25%; min-width: 70px; background: $theme; color: $white; border: 1px $theme solid; } #search-input { height: 2.5rem; width: 75%; } } .popup { background: $white; box-shadow: 0 0 8px 0 $black_15, 0 4px 10px 0 $black_15; padding: 2.5rem; display: block; width: auto; min-width: 300px; max-width: 90vw; max-height: 75vh; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); overflow-y: auto; } .chart { min-width: 400px; width: 100%; min-height: 400px; max-height: 50vh; background: $white; margin: 20px auto 40px; border: 1px solid $black; position: relative; } .statsnumber { text-align: right; padding-right: 1rem; } ul { list-style-type: none; } li { word-wrap: break-word; word-break: break-word; } li::before { content: "–"; position: absolute; margin-left: -1.3em; } } .entries { > div { display: grid; grid-template-columns: 3fr 1fr; } main { padding-right: 2%; max-width: 90ch; div { margin: 1rem 0; } } nav { width: 130px; margin-left: auto; h4 { color: $darker-grey; font-weight: $normal; } } } .theories { grid-template-columns: 20% 80%; justify-content: normal; .collapsible.collapsed { display: none; } .invertible.collapsed { transform: scale(1, -1); } .collapse-container.collapsed { .collapsible { display: none; } .invertible { transform: scale(1, -1); } } .content { min-width: clamp(60ch, 50vw, 50vw); width: 100%; max-width: 75vw; margin: 0 0 2rem 4vw; } pre { width: fit-content; } h2 { @extend %sticky; padding-top: 0.5rem; border-bottom: 1px $light-grey solid; svg { height: 2rem; width: 20px; position: sticky; right: 1rem; float: right; stroke: $dark-grey; transition: transform 100ms; } } main { > div { margin-bottom: 1rem; } } aside { word-break: normal; z-index: 1; overflow-x: hidden; .logo { padding-top: 2rem; padding-bottom: 2rem; width: 100px; @media screen and (min-width: $size-xl) { width: 150px; } } #return { position: sticky; top: 0rem; background: #2E2D4E; min-width: 200px; } #pdfs { position: fixed; bottom: 0; background: #2E2D4E; margin: 0; padding: 1rem 1.75rem; width: 20vw; box-sizing: border-box; cursor: pointer; } > div { margin-right: 1rem; } &:hover { min-width: fit-content; div { min-width: fit-content; } } ul { margin-left: 1.75rem; } li { padding: 0; &:hover { background-color: $theme; } > a:hover { background-color: $theme-hover; } a { padding: 0.5em 0; display: inline-block; width: 100%; } } } @media screen and (max-width: $size-s) { padding: 0; grid-template-columns: 1fr; aside { overflow-x: visible; } #menu { min-width: fit-content; padding-right: 1rem; } .content { min-width: 350px; max-width: 100vw; margin: 80px 0 0 20px; } h2 { top: 80px; padding-left: 0.5rem; } } } .links { button { display: inline-block; margin: 0.5rem 0; } a { display: inline-block; margin: 0.5rem 0; } } .popup-button:not(.entity_ref) { &:visited { color: $white; } &:link { border: none; padding: 0.75rem 0; background-color: $theme; color: $white; cursor: pointer; text-align: center; transition: background-color 200ms ease, transform 150ms ease; font-weight: $bold; font-size: 0.9rem; width: 130px; } &:active { transform: scale(0.98); } } a:not(.entity_ref) { &:link { color: $link; text-decoration: none; font-weight: $bold; } &:visited { color: $link-visited; } &:hover { color: $link-hover; } } h1 { margin: 1.5em 0 0 0; font-size: 2em; padding-bottom: 20px; span.first { font-size: 2.3rem; } } h2 { margin: 1.5em 0 0 0; padding: 0; line-height: normal; } h3 { margin: 1.5em 0 0 0; font-stretch: normal; font-size: larger; padding: 0; font-weight: $light; &:first-child { margin: 0.5rem 0 0 0; } } h4 { margin: 1.5em 0 0 0; margin-bottom: 0.5ex; font-weight: $bold; } .popup { h2 { margin-top: 0; } .close { position: absolute; width: 20px; height: 20px; top: 2rem; right: 2rem; transition: all 200ms; font-size: 24px; font-weight: $bold; color: $darker-grey; text-align: center; &:hover { color: $darkest-grey; } } } #download-popup { a[download] { display: block; width: 154px; margin: 2.5rem auto 0; } } .year { @extend %sticky; padding-top: 2rem; border-bottom: $border-bottom; } img { max-width: 90%; } pre { @extend %code; padding: 1rem; overflow: auto; &.bibtex { white-space: pre; border-width: thin; border-style: solid; } &.code { white-space: pre; padding: 10px 2px; } } code { padding: 1px 2px 2px; @extend %code; } blockquote { border: 2px solid $lighter-grey; padding: 1rem 2rem; margin: auto 0; } tr:nth-child(even) { background-color: $lightest-grey; } .nobr { white-space: nowrap; } .large-top-margin { margin-top: 48px; } .search-page { > div { display: grid; grid-template-columns: 3fr #{"max(25%, 175px)"}; grid-template-rows: 150px 1fr; grid-template-areas: "search search" "entries sidebar"; } form { grid-area: search; } #search-main { grid-area: entries; padding-right: 2rem; box-sizing: border-box; } #search-results { > div { position: relative; margin: 1.5rem 0; border-bottom: 2px $lighter-grey solid; padding-bottom: 1.5rem; } div + div { margin-top: 1rem; } .title, .subtitle { display: flex; justify-content: space-between; align-items: center; } .subtitle { color: $dark-grey; font-size: 0.875em; > p { margin: 0; } } a[download] { padding: 5px 7.5px; flex-shrink: 0; margin-left: 1em; } } #search-sidebar { grid-area: sidebar; } } a[download] { display: inline-block; color: $black; height: 22px; padding: 4px 7px; @extend %button; &:focus { outline: none; box-shadow: 0 0 0 2px #c5c4ddbb; } &:hover, &:active { background-color: $light-grey; color: $black; } &:after { content: url("/images/download.svg"); @extend %button-image; } } .entry { display: flex; align-items: center; margin-top: 1.35rem; a[download] { position: absolute; top: 0; right: 0; } .date { text-align: right; width: 15%; min-width: 6ch; font-family: $monospace; } h5 { font-size: initial; display: initial; } } .form-container { width: 290px; text-align: left; } #search-input { @extend %button; border-right: none; font-family: $sans-serif; padding: 0.5em; } #search-button:not(.entity_ref) { @extend %button; cursor: pointer; &:focus { outline: none; box-shadow: 0 0 0 2px #c5c4ddbb; } img { max-width: 1rem; } } .entity_def.active { background-color: $theme-focus; } button { @extend %button; height: 2rem; padding: 7px; font-weight: $bold; cursor: pointer; &:hover { background-color: $lighter-grey; } &:active { background-color: $light-grey; } &:focus { outline: none; box-shadow: 0 0 0 2px #c5c4ddbb; } } button[copy] { &:after { content: url("/images/copy.svg"); @extend %button-image; } } #search-input-autocomplete-list { width: 214px; position: absolute; margin-bottom: 24px; padding: 8px 0; background-color: $white; border: 1px solid $lighter-grey; box-shadow: 0 2px 4px $black_15; z-index: 1; div { width: auto; padding: 8px 12px; line-height: 16px; cursor: pointer; &:hover { background: $lighter-grey; } } } .autocomplete-active { background: $lighter-grey; } .item-text { width: 85%; } .date { color: $darker-grey; font-size: 14px; } .overlay { position: fixed; top: 0; bottom: 0; left: 0; right: 0; background: $black_15; transition: opacity 200ms; visibility: hidden; opacity: 0; .cancel { position: absolute; width: 100%; height: 100%; cursor: pointer; } &:target { visibility: visible; opacity: 1; } } .status-ok { background-color: #00FF99; padding: 8px; } .status-skipped { background-color: #FFFF99; padding: 8px; } .status-failed { background-color: #DD2222; padding: 8px; } iframe { width: 100%; height: 90vh; } .loader { margin: 24px 24px 0 0; height: 6px; position: relative; display: block; background-color: $theme; border-radius: 4px; overflow: hidden; .animation { background-color: $light-grey; &:before { content: ''; position: absolute; background-color: inherit; top: 0; left: 0; bottom: 0; will-change: left, right; animation: loader 2s linear infinite; } } } @keyframes loader { 0% { left: -20%; right: 100%; } 100% { left: 100%; right: -20%; } } #menu-toggle { input, label { display: none; } > .logo-link, img[alt="Search"] { display: none; } form { display: none; } } @media screen and (max-width: $size-xs) { .content { width: 90%; min-width: 350px; } .search-page { > div { display: grid; grid-template-columns: 1fr; grid-template-rows: 150px auto auto; grid-template-areas: 'search' 'sidebar' 'entries'; } } } +@media screen and (min-width: $size-s) { + ul.horizontal-list { + display: flex; + flex-flow: wrap; + padding-left: 0; + + li { + margin-right: 16px; + } + + li::before { + content: none; + } + } +} + @media screen and (max-width: $size-s) { body { grid-template-columns: 1fr; grid-template-areas: 'content'; } aside { height: 80px; width: 100vw; overflow-y: visible; z-index: 1; } header { form { display: none; } } .content { margin-top: 80px; } .entries { > div { display: grid; grid-template-rows: auto auto; grid-template-columns: 1fr; } nav { margin-left: 0; } } .large-top-margin { margin-top: 0; } #banner { left: 0; width: 100vw; height: 75px; p { line-height: normal; margin: 1rem; } } #menu-toggle { background: $theme; display: block; position: relative; top: 0; left: 0; z-index: 1; height: 80px; user-select: none; width: 100vw; a { text-decoration: none; transition: color 0.3s ease; } .logo-link { height: 3rem; max-width: 50vw; top: 1rem; left: 0; right: 0; margin-left: auto; margin-right: auto; img { padding: 0; height: 3rem; object-fit: contain; } } .logo-link, input, label, a[href="/search"] { display: block; position: absolute; z-index: 2; } input, label, a[href="/search"] { width: 24px; height: 24px; top: 28px; } input, label { left: 28px; cursor: pointer; } a[href="/search"] { right: 28px; filter: invert(1); } a[href="/search"] img, label img { width: 100%; } input { opacity: 0; &:checked { ~ nav { left: 0; } } } label span { display: none; } } #menu { box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15); height: calc(100vh - 80px); position: absolute; top: 0; margin-top: 80px; box-sizing: border-box; min-width: 200px; width: 50vw; max-width: 350px; min-height: 355px; background: $theme; left: -100%; transition: left 0.3s ease; overflow-y: scroll; .logo-link { display: none; } } } diff --git a/admin/site/themes/afp/layouts/_default/list.html b/admin/site/themes/afp/layouts/_default/list.html --- a/admin/site/themes/afp/layouts/_default/list.html +++ b/admin/site/themes/afp/layouts/_default/list.html @@ -1,19 +1,19 @@ {{ define "main" }} {{- $site := . -}} {{- range (.Data.Pages).GroupByDate "2006" -}} {{- if ne .Key "0001" -}}

{{- .Key -}}

{{- range .Pages -}}
- -

{{- htmlUnescape .Title -}}
- {{- with .Params.authors -}}by {{ partial "authors.html" (dict "site" $site "authors" .) -}}{{- end -}}

-
+
+
{{- htmlUnescape .Title -}}

+ {{- with .Params.authors -}}by {{ partial "authors.html" (dict "site" $site "authors" .) -}}{{- end -}} +
{{ .Date.Format "Jan 02" }}
{{ end }} {{ end }} {{ end }} {{ end }} \ No newline at end of file diff --git a/admin/site/themes/afp/layouts/authors/list.html b/admin/site/themes/afp/layouts/authors/list.html --- a/admin/site/themes/afp/layouts/authors/list.html +++ b/admin/site/themes/afp/layouts/authors/list.html @@ -1,38 +1,38 @@ {{ define "main" }} {{- $site := . -}} {{ with (index (index .Site.Data.authors .Title) "homepages") }}

Homepages 🌐

{{ end }} {{ with (index (index .Site.Data.authors .Title) "emails") }}

E-Mails 📧

{{ end }}

Entries

{{- range (.Data.Pages).GroupByDate "2006" -}} {{- if ne .Key "0001" -}}

{{- .Key -}}

{{- range .Pages -}}
- -

{{- htmlUnescape .Title -}}
- {{- $entry := . -}} - {{- with .Params.authors -}}by {{ partial "authors.html" (dict "site" $site "authors" . "affiliations" $entry.Params.affiliations) -}}{{- end -}}

-
+
+
{{- htmlUnescape .Title -}}

+ {{- $entry := . -}} + {{- with .Params.authors -}}by {{ partial "authors.html" (dict "site" $site "authors" . "affiliations" $entry.Params.affiliations) -}}{{- end -}} +
{{ .Date.Format "Jan 02" }}
{{ end }} {{ end }} {{ end }} {{ end }} \ No newline at end of file diff --git a/admin/site/themes/afp/layouts/entries/single.html b/admin/site/themes/afp/layouts/entries/single.html --- a/admin/site/themes/afp/layouts/entries/single.html +++ b/admin/site/themes/afp/layouts/entries/single.html @@ -1,154 +1,163 @@ {{- define "main" -}} {{- $site := . -}} {{- $path := .Site.Params.afpUrls.html -}} {{- if isset .Site.Data "status" -}} {{- $path = .Site.Params.afpUrls.htmlDevel -}} {{- end -}} {{- $entry_name := .File.BaseFileName -}}
{{- if isset .Site.Data "status" -}} This is a development version of this entry. It might change over time and is not stable. Please refer to release versions for citations. {{- end -}}

Abstract

{{- trim .Params.abstract "\n" | safeHTML -}}
{{- if (eq .Params.licence "BSD") -}} BSD License {{- else if (eq .Params.license "LGPL") -}} GNU Lesser General Public License (LGPL) {{- else -}} {{- printf "%s License" .Params.license -}} {{- end -}} {{- with .Params.extra -}} {{- range $key, $value := . -}}

{{- humanize $key -}}

{{- $value | safeHTML -}}

{{- end -}} {{- end -}} {{- $Scratch := newScratch -}} - {{- with .Params.dependencies -}} -

Depends On

- {{- end -}} - - {{- with (index .Site.Taxonomies.dependencies (urlize $entry_name)) -}} -

Used by

- - {{- end -}} - {{- with .Params.topics -}}

Topics

{{- end -}} {{- range .Params.sessions -}} {{ $session := .session }}

Theories of {{ $session }}

{{- end -}} +
+ {{- with .Params.dependencies -}} +
+

Depends On

+
    + {{- range . -}} +
  • {{- printf "[%s](/entries/%s.html)" . . | $.Page.RenderString -}}
  • + {{- end -}} +
+
+ {{- end -}} + + {{- with (index .Site.Taxonomies.dependencies (urlize $entry_name)) -}} +
+

Used by

+
    + {{- range . -}} +
  • {{- printf "[%s](%s)" .File.BaseFileName .RelPermalink | $.Page.RenderString -}}
  • + {{- end -}} +
+
+ {{- end -}} + {{ $related := .Site.RegularPages.Related . | first 3 }} {{ with $related }} -

Related Entries

- +
+

Related Entries

+
    + {{ range . }} +
  • {{- printf "[%s](%s)" .Title .RelPermalink | $.Page.RenderString -}}
  • + {{ end }} +
+
{{ end }} +
{{- end -}} diff --git a/admin/site/themes/afp/layouts/partials/navigation.html b/admin/site/themes/afp/layouts/partials/navigation.html --- a/admin/site/themes/afp/layouts/partials/navigation.html +++ b/admin/site/themes/afp/layouts/partials/navigation.html @@ -1,37 +1,30 @@ \ No newline at end of file diff --git a/admin/site/themes/afp/static/js/scroll-spy.js b/admin/site/themes/afp/static/js/scroll-spy.js --- a/admin/site/themes/afp/static/js/scroll-spy.js +++ b/admin/site/themes/afp/static/js/scroll-spy.js @@ -1,138 +1,147 @@ /** * Scrollspy, inspired from bootstrap. Original license: * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * * -------------------------------------------------------------------------- */ const EVENT_SPY_ACTIVATE = 'activate.spy' const CLASS_ACTIVE = 'active' const CLASS_SPY_LINK = 'spy-link' /** * Class definition */ class ScrollSpy { constructor(element, target, target_suffix = "", offset = 0.5) { ScrollSpy.instance = this this._element = element this._offset = offset this._offsets = [] this._link_ids = [] this._active_id = null this._scrollHeight = 0 this._target = target + this._eps = 0.01 this._target_suffix = target_suffix window.onscroll = () => this._process() this.refresh() this._process() } + scroll_to(id) { + const elem = document.getElementById(id + this._target_suffix) + if (elem) { + const offset = -window.innerHeight * (this._offset - this._eps) + window.scroll(0, elem.offsetTop + offset) + } + } + refresh() { this._clear() this._offsets = [] this._link_ids = [] this._scrollHeight = this._get_scroll_height() const targets = [] for (const link of document.getElementById(this._target).getElementsByClassName(CLASS_SPY_LINK)) { // visible and has id if (link.id && !is_collapsed(link)) { const target = document.getElementById(this._get_target_id(link)) if (target) { const targetBCR = target.getBoundingClientRect() if (targetBCR.width || targetBCR.height) { targets.push([targetBCR.top + window.pageYOffset, link.id]) } } } } for (const item of targets.sort((a, b) => a[0] - b[0])) { this._offsets.push(item[0]) this._link_ids.push(item[1]) } } // Private _get_target_id(link) { return link.getAttribute('href').slice(1) + this._target_suffix } _get_scroll_height() { return window.scrollHeight || Math.max( document.body.scrollHeight, document.documentElement.scrollHeight ) } _process() { const scroll_top = window.pageYOffset + this._offset * window.innerHeight const scroll_height = this._get_scroll_height() const max_scroll = this._offset * window.innerHeight + scroll_height - window.innerHeight if (this._scrollHeight !== scroll_height) { this.refresh() } if (scroll_top >= max_scroll) { const target_id = this._link_ids[this._link_ids.length - 1] if (this._active_id !== target_id) { this._activate(target_id) } return } if (this._active_id && scroll_top < this._offsets[0] && this._offsets[0] > 0) { this._clear() return } for (let i = this._offsets.length; i--;) { const isActiveTarget = this._active_id !== this._link_ids[i] && scroll_top >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scroll_top < this._offsets[i + 1]) if (isActiveTarget) { this._activate(this._link_ids[i]) } } } _activate(link_id) { this._clear() this._active_id = link_id const link = document.getElementById(link_id) if (link) { const elem = document.getElementById(this._get_target_id(link)) link.classList.add(CLASS_ACTIVE) elem.classList.add(CLASS_ACTIVE) const event = new Event(EVENT_SPY_ACTIVATE) event.relatedTarget = link window.dispatchEvent(event) } } _clear() { if (this._active_id) { const link = document.getElementById(this._active_id) if (link) { const elem = document.getElementById(this._get_target_id(link)) if (link.classList.contains(CLASS_ACTIVE)) link.classList.remove(CLASS_ACTIVE) if (elem.classList.contains(CLASS_ACTIVE)) elem.classList.remove(CLASS_ACTIVE) } } } static instance } \ No newline at end of file diff --git a/admin/site/themes/afp/static/js/search.js b/admin/site/themes/afp/static/js/search.js --- a/admin/site/themes/afp/static/js/search.js +++ b/admin/site/themes/afp/static/js/search.js @@ -1,302 +1,302 @@ /* constants */ const URL_FINDFACTS = 'https://search.isabelle.in.tum.de' const NUM_MAX_SIDE_RESULTS = 4 const NUM_MAX_MAIN_RESULTS = 15 const ID_SEARCH_INPUT = 'search-input' const ID_SEARCH_BUTTON = 'search-button' const ID_RESULTS_ENTRIES = 'search-results' const ID_RESULTS_AUTHORS = 'author-results' const ID_RESULTS_TOPICS = 'topic-results' const ID_RESULTS_FINDFACTS = 'find-facts-results' /* index */ function build_indexes(entries, authors, topics, keywords) { const entry_index = new FlexSearch.Document({ encoder: 'advanced', tokenize: 'forward', document: { id: 'id', - index: ['title', 'abstract', 'date', 'topics[]', 'authors[]'], + index: ['shortname', 'title', 'abstract', 'date', 'topics[]', 'authors[]'], store: true, }, }) entries.forEach(entry => entry_index.add(entry)) const author_index = new FlexSearch.Document({ encoder: 'advanced', tokenize: 'forward', document: { id: 'id', index: 'name', store: true }, }) authors.forEach(author => author_index.add(author)) const topic_index = new FlexSearch.Document({ encoder: 'icase', tokenize: 'forward', document: { id: 'id', index: 'name', store: true }, }) topics.forEach(topic => topic_index.add(topic)) const suggest_index = get_suggest_index(keywords) return { entry: entry_index, author: author_index, topic: topic_index, suggest: suggest_index, } } async function get_findfacts_index() { const indexes = await fetch(URL_FINDFACTS + '/v1/indexes').then(async http_res => { if (http_res.status !== 200) { console.log(`Error ${http_res.status} when fetching indexes: ${http_res.statusText}`) return Promise.resolve([]) } return await http_res.json() }).catch((err) => { console.log(`Could not fetch indexes: ${err}`) return Promise.resolve([]) }) return indexes.find(index => index.startsWith('default_')) } /* search */ function local_search(indices, query) { const entry_results = [...new Set(indices['entry'].search(query, NUM_MAX_MAIN_RESULTS + 1, {enrich: true}).flatMap(e => e.result).map(d => d.doc))] const topic_results = indices['topic'].search(query, NUM_MAX_SIDE_RESULTS + 1, {enrich: true, pluck: 'name'}).map(d => d.doc) const author_results = indices['author'].search(query, NUM_MAX_SIDE_RESULTS + 1, {enrich: true, pluck: 'name'}).map(d => d.doc) return {entries: entry_results, topics: topic_results, authors: author_results} } async function findfacts_search(index, query) { if (!index) return {} const facet_query = { filters: [ { field: 'SourceCode', filter: { Term: { inner: query } } } ], fields: ['Kind'], maxFacets: 5 } const facet = await fetch(`${URL_FINDFACTS}/v1/${index}/facet`, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(facet_query), } ).then(async (response) => { if (response.status !== 200) { console.log(`Error ${http_res.status} on findfacts query: ${http_res.statusText}`) return {} } else { const json = await response.json() return json['Kind'] } }).catch((err) => { console.log(`Error in findfacts query: ${err}`) return {} }) const search_query = `{"term"%3A"${encodeURIComponent(query)}"}` const url = `${URL_FINDFACTS}/#search/${index}?q=${search_query}` return {facet: facet, url: url} } /* result handling */ function render_entry(entry) { const authors = entry.authors.join(', ') const topics = entry.topics.map((topic, key) => `${topic}`).join(', ') const year = entry.date.substring(0, 4) return `

${entry.title}

${authors} ${year}
${entry.abstract}
in ${topics}
` } function render_entries_results(data, query) { if (!data) return parse_elem('

Please enter a search term above.

') if (data.length === 0) return parse_elem('

No results.

') else { const end = data.length > NUM_MAX_MAIN_RESULTS ? '...' : '' const res = parse_elem(` ${data.slice(0, NUM_MAX_MAIN_RESULTS).map(entry => render_entry(entry)).join('')}${end}`) new Mark(res).mark(query) return res } } function render_results_shortlist(data, query) { if (!data) return '' if (data.length === 0) return parse_elem('

No results

') else { const end = data.length > NUM_MAX_SIDE_RESULTS ? '
  • ...
  • ' : '' const res = parse_elem(` `) new Mark(res).mark(query) return res } } function render_findfacts_results(res) { if (res.facet && Object.keys(res.facet).length > 0) { return parse_elem(` `) } else return parse_elem('

    No results

    ') } /* input handling */ function debounce(callback, wait) { let timeout return (...args) => { clearTimeout(timeout) timeout = setTimeout(function () { callback.apply(this, args) }, wait) } } function handleSubmit(value) { if (typeof history.pushState !== 'undefined') { history.pushState({}, 'Search the Archive - ' + value, '?s=' + value) } } /* setup */ const init_search = async () => { const input = document.getElementById(ID_SEARCH_INPUT) const button = document.getElementById(ID_SEARCH_BUTTON) const entries_res = document.getElementById(ID_RESULTS_ENTRIES) const authors_res = document.getElementById(ID_RESULTS_AUTHORS) const topics_res = document.getElementById(ID_RESULTS_TOPICS) const search_query = get_query('s') if (search_query) input.value = search_query.replace('+', ' ') const index_data = await Promise.all([ fetch('/index.json'), fetch('/authors/index.json'), fetch('/topics/index.json'), fetch('/data/keywords.json') ]) const index_json = await Promise.all(index_data.map(r => r.json())) const indexes = build_indexes(...index_json) input.addEventListener('keydown', (event) => { switch (event.key) { case 'Enter': event.preventDefault() handleSubmit(event.target.value) } }) const run_local_search = (query) => { let res = {} if (query && query.length > 1) { add_suggestions(indexes['suggest'], query) res = local_search(indexes, query) } authors_res.replaceChildren(render_results_shortlist(res.authors, query)) topics_res.replaceChildren(render_results_shortlist(res.topics, query)) entries_res.replaceChildren(render_entries_results(res.entries, query)) MathJax.typeset() } input.addEventListener('keyup', (event) => { switch (event.key) { case 'Enter': case 'Up': case 'ArrowUp': case 'Down': case 'ArrowDown': case 'Left': case 'ArrowLeft': case 'Right': case 'ArrowRight': case 'Escape': break default: run_local_search(event.target.value) } }) button.addEventListener('click', () => handleSubmit(input.value)) if (input.value) run_local_search(input.value) input.focus() // findfacts const findfacts_res = document.getElementById(ID_RESULTS_FINDFACTS) const findfacts_index = await get_findfacts_index() const memoized_findfacts_search = memoize(findfacts_search) const run_findfacts_search = async (query) => { let res = {} if (query && query.length > 2) { findfacts_res.replaceChildren(parse_elem('

    ...

    ')) res = await memoized_findfacts_search([findfacts_index, query]) } findfacts_res.replaceChildren(render_findfacts_results(res)) } input.addEventListener('keyup', debounce(async (event) => run_findfacts_search(event.target.value), 300)) if (input.value) await run_findfacts_search(input.value) } document.addEventListener('DOMContentLoaded', init_search) diff --git a/admin/site/themes/afp/static/js/theory.js b/admin/site/themes/afp/static/js/theory.js --- a/admin/site/themes/afp/static/js/theory.js +++ b/admin/site/themes/afp/static/js/theory.js @@ -1,287 +1,279 @@ /* constants */ const ID_THEORY_LIST = 'theories' const CLASS_LOADER = 'loader' const CLASS_ANIMATION = 'animation' const ATTRIBUTE_THEORY_SRC = 'theory-src' const CLASS_NAVBAR_TYPE = 'theory-navbar-type' const CLASS_THY_NAV = 'thy-nav' const PARAM_NAVBAR_TYPE = 'theory-navbar-type' const ID_NAVBAR_TYPE_SELECTOR = 'navbar-type-selector' const ID_NAVBAR = 'theory-navbar' const NAVBAR_TYPES = ['fact', 'type', 'const'] /* routing */ function target(base_href, rel_href) { const href_parts = rel_href.split('/') if (href_parts.length === 1) return `#${href_parts[0]}` else if (href_parts.length === 3 && href_parts[0] === '..' && href_parts[1] !== '..') { return `../${href_parts[1].toLowerCase()}/#${href_parts[2]}` } else return `${base_href}/../${rel_href}` } -function to_ref(thy_name, ref) { +function to_id(thy_name, ref) { if (ref) return `${thy_name}.html#${ref}` else return `${thy_name}.html` } -const to_id = (id) => `${id}#` +const to_fresh_id = (id) => `${id}#` const to_svg_id = (id) => `${id}#svg` const to_container_id = (id) => `${id}#container` const to_collapsible_id = (id) => `${id}#collapsible` const to_spinner_id = (id) => `${id}#spinner` const to_nav_id = (id) => `${id}#nav` const to_ul_id = (id) => `${id}#ul` const of_ul_id = (id) => id.split('#').slice(0, -1).join('#') const to_a_id = (id) => `${id}#a` /* document translation */ function translate(base_href, thy_name, thy_body) { const thy_refs = [...thy_body.getElementsByTagName('span')].map((span) => { let ref = span.getAttribute('id') if (ref) { - span.setAttribute('id', to_id(to_ref(thy_name, ref))) + span.setAttribute('id', to_fresh_id(to_id(thy_name, ref))) } return ref }).filter(e => e) for (const link of thy_body.getElementsByTagName('a')) { const rel_href = link.getAttribute('href') link.setAttribute('href', target(base_href, rel_href)) } return thy_refs } /* theory lazy-loading */ async function fetch_theory_body(href) { const html_str = await fetch(href).then((http_res) => { if (http_res.status !== 200) return Promise.resolve(`${http_res.statusText}`) else return http_res.text() }).catch((_) => { console.log(`Could not load theory at '${href}'. Redirecting...`) window.location.replace(href) }) const parser = new DOMParser() const html = parser.parseFromString(html_str, 'text/html') return html.getElementsByTagName('body')[0] } async function load_theory(thy_name, href) { const thy_body = await fetch_theory_body(href) const refs = translate(href, thy_name, thy_body) const collapse = document.getElementById(to_collapsible_id(thy_name)) collapse.append(...Array(...thy_body.children).slice(1)) return refs } async function open_theory(thy_name) { const container = document.getElementById(to_container_id(thy_name)) if (container) { if (document.getElementById(to_collapsible_id(thy_name))) open(container) else { const collapsible = parse_elem(`
    `) container.appendChild(collapsible) open(container) let refs = await load_theory(thy_name, container.getAttribute(ATTRIBUTE_THEORY_SRC)) await load_theory_nav(thy_name, refs) const spinner = document.getElementById(to_spinner_id(thy_name)) spinner.parentNode.removeChild(spinner) } } } function nav_tree_rec(thy_name, path, key, ref_parts, type) { const rec_ref = ref_parts.filter(e => e.length > 0) const ref = `${path.join('.')}.${key}|${type}` - const id = to_ref(thy_name, ref) + const id = to_id(thy_name, ref) let res if (rec_ref.length < ref_parts.length) { res = `${escape_html(key)}` } else { - const head_id = to_ref(thy_name, `${[...path, key, ...ref_parts[0]].join('.')}|${type}`) + const head_id = to_id(thy_name, `${[...path, key, ...ref_parts[0]].join('.')}|${type}`) res = `${escape_html(key)}` } if (rec_ref.length > 1) { const by_key = group_by(rec_ref) const children = Object.keys(by_key).map((key1) => `
  • ${nav_tree_rec(thy_name, [...path, key], key1, by_key[key1], type)}
  • `) return ` ${res} ` } else return res } function nav_tree(thy_name, refs, type) { let trees = Object.entries(group_by(refs || [])).map(([key, parts]) => `
  • ${nav_tree_rec(thy_name, [thy_name], key, parts, type)}
  • `) return parse_elem(` `) } const cached_refs = Object.fromEntries(NAVBAR_TYPES.map(t => [t, {}])) const load_theory_nav = (thy_name, refs) => { let selected = get_query(PARAM_NAVBAR_TYPE) || NAVBAR_TYPES[0] let by_type = group_by(refs.filter(ref => ref.includes('|')).map((id) => id.split('|').reverse())) let type_selector = document.getElementById(ID_NAVBAR_TYPE_SELECTOR) let options = [...type_selector.options].map(e => e.value) for (let [type, elems] of Object.entries(by_type)) { if (NAVBAR_TYPES.includes(type) && !options.includes(type)) { type_selector.appendChild(parse_elem(``)) } let parts_by_thy = group_by(elems.map((s) => s[0].split('.'))) if (NAVBAR_TYPES.includes(type)) cached_refs[type][thy_name] = parts_by_thy[thy_name] } let tree = nav_tree(thy_name, cached_refs[selected][thy_name], selected) document.getElementById(to_nav_id(thy_name)).appendChild(tree) ScrollSpy.instance.refresh() } /* state */ let navbar_last_opened = [] /* controls */ -const scroll_to = (ref) => { - const elem = document.getElementById(to_id(decodeURI(ref))) - if (elem) { - const offset = -window.innerHeight * 0.49 - window.scroll(0, elem.offsetTop + offset) - } -} - const follow_theory_hash = async () => { let hash = window.location.hash if (hash.length > 1) { const id = hash.slice(1) const thy_name = strip_suffix(id.split('#')[0], '.html') await open_theory(thy_name) - scroll_to(id) + ScrollSpy.instance.scroll_to(id) } } const toggle_theory = async (thy_name) => { - const hash = `#${to_ref(thy_name)}` + const hash = `#${to_id(thy_name)}` const collapsible = document.getElementById(to_container_id(thy_name)) if (collapsible) { if (!collapse(collapsible)) { if (window.location.hash === hash) open(collapsible) else window.location.hash = hash } } else window.location.hash = hash } const change_selector = (type) => { let old_type = get_query(PARAM_NAVBAR_TYPE) if (!old_type || old_type !== type) { set_query(PARAM_NAVBAR_TYPE, type) for (const elem of document.getElementsByClassName(CLASS_NAVBAR_TYPE)) { let thy_name = of_ul_id(elem.id) elem.replaceWith(nav_tree(thy_name, cached_refs[type][thy_name], type)) } ScrollSpy.instance.refresh() } } const open_tree = (elem) => { if (elem.classList.contains(CLASS_COLLAPSIBLE)) { if (open(elem)) navbar_last_opened.push(elem) } if (elem.parentElement) open_tree(elem.parentElement) } const sync_navbar = (link) => { for (const elem of navbar_last_opened){ collapse(elem) } open_tree(link.parentElement) link.scrollIntoView({block: "center"}) } /* setup */ const init = async () => { const theory_list = document.getElementById(ID_THEORY_LIST) const navbar = document.getElementById(ID_NAVBAR) if (theory_list && navbar) { const thy_names = [] for (const theory of theory_list.children) { thy_names.push(theory.id) const href = theory.getAttribute('href') const thy_name = theory.id const thy_collapsible = parse_elem(`
    -

    +

    ${thy_name}

    `) theory.replaceWith(thy_collapsible) } const type = get_query(PARAM_NAVBAR_TYPE) ? get_query(PARAM_NAVBAR_TYPE) : NAVBAR_TYPES[0] navbar.appendChild(parse_elem(`
  • `)) navbar.append(...thy_names.map((thy_name) => parse_elem(`
  • - + ${thy_name}
  • `))) navbar.insertAdjacentElement('afterend', document.createElement('hr')); window.onhashchange = follow_theory_hash window.addEventListener(EVENT_SPY_ACTIVATE, (e) => sync_navbar(e.relatedTarget)) new ScrollSpy(document.body, ID_NAVBAR, "#") await follow_theory_hash() } } document.addEventListener('DOMContentLoaded', init)