1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-01-31 11:29:09 +00:00

Merge pull request #62 from bouncepaw/1.2

1.2
This commit is contained in:
Timur Ismagilov 2021-06-05 23:02:41 +05:00 committed by GitHub
commit f493c89ebb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 2320 additions and 3301 deletions

11
.gitignore vendored
View File

@ -1,6 +1,11 @@
mycorrhiza mycorrhiza
hyphae/*.gog metarrhiza
example-wiki
# go editors and IDEA folders # VScode and IDEA folders
.idea/
.vscode/ .vscode/
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

9
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,9 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="USE_TAB_CHARACTER" value="true" />
</value>
</option>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CssUnknownTarget" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="NonAsciiCharacters" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="CHECK_FOR_NOT_ASCII_IDENTIFIER_NAME" value="false" />
<option name="CHECK_FOR_DIFFERENT_LANGUAGES_IN_IDENTIFIER_NAME" value="false" />
</inspection_tool>
</profile>
</component>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mycorrhiza.iml" filepath="$PROJECT_DIR$/.idea/mycorrhiza.iml" />
</modules>
</component>
</project>

9
.idea/mycorrhiza.iml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/metarrhiza" vcs="Git" />
</component>
</project>

View File

@ -1,11 +1,13 @@
WIKI=~/src/example-wiki
run: build run: build
./mycorrhiza metarrhiza ./mycorrhiza ${WIKI}
config_run: build config_run: build
./mycorrhiza -config-path "assets/config.ini" metarrhiza ./mycorrhiza -config-path "assets/config.ini" ${WIKI}
devconfig_run: build devconfig_run: build
./mycorrhiza -config-path "assets/devconfig.ini" metarrhiza ./mycorrhiza -config-path "assets/devconfig.ini" ${WIKI}
build: build:
go generate go generate

View File

@ -1,18 +1,10 @@
# 🍄 MycorrhizaWiki 1.1 # 🍄 MycorrhizaWiki 1.2
A wiki engine. A wiki engine.
[Main wiki](https://mycorrhiza.lesarbr.es) [Main wiki](https://mycorrhiza.lesarbr.es)
## Building ## Building
Also see [detailed instructions](https://mycorrhiza.lesarbr.es/hypha/guide/deployment) on wiki. See [the guide](https://mycorrhiza.lesarbr.es/hypha/guide/deployment) on the wiki.
```sh
git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza
cd mycorrhiza
make
# That make will:
# * run the default wiki. You can edit it right away.
# * create an executable called `mycorrhiza`. Run it with path to your wiki.
```
## Installing ## Installing

View File

@ -113,16 +113,18 @@ func StreamDefaultCSS(qw422016 *qt422016.Writer) {
qw422016.N().S(` qw422016.N().S(`
`) `)
//line assets/assets.qtpl:10 //line assets/assets.qtpl:10
qw422016.N().S(` qw422016.N().S(`.non-existent-hypha { }
.non-existent-hypha__ways { display: flex; flex-direction: column; width: 100%; margin: 0 0 1rem 0;}
.non-existent-hypha__way { border: 1px #999 solid; border-radius: .25rem; padding: .25rem; }
.non-existent-hypha__title { margin-bottom: 1rem; }
.non-existent-hypha__subtitle { margin: 0; }
.amnt-grid { display: grid; grid-template-columns: 1fr 1fr; } .amnt-grid { display: grid; grid-template-columns: 1fr 1fr; }
.upload-binary__input { display: block; margin: .25rem 0; } #upload-binary__input { display: block; margin: .25rem 0 .25rem 0; }
.modal__title { font-size: 2rem; } .modal__title { font-size: 2rem; }
.modal__title_small { font-size: 1.5rem; } .modal__title_small { font-size: 1.5rem; }
.modal__confirmation-msg { margin: 0 0 .5rem 0; } .modal__confirmation-msg { margin: 0 0 .5rem 0; }
.modal__action { display: inline-block; font-size: 1rem; padding: .25rem; border-radius: .25rem; }
.modal__submit { border: 1px #999 solid; }
.modal__cancel { border: 1px #999 dashed; text-decoration: none; }
.hypha-list { padding-left: 0; } .hypha-list { padding-left: 0; }
.hypha-list__entry { list-style-type: none; } .hypha-list__entry { list-style-type: none; }
@ -138,7 +140,7 @@ header { width: 100%; margin-bottom: 1rem; }
.header-links__entry, .hypha-tabs__tab { list-style-type: none; } .header-links__entry, .hypha-tabs__tab { list-style-type: none; }
.header-links__entry { margin-right: .5rem; } .header-links__entry { margin-right: .5rem; }
.header-links__entry_user { font-style:italic; } .header-links__entry_user, .header-links__entry_register { font-style:italic; }
.header-links__link { display: inline-block; padding: .25rem; text-decoration: none; } .header-links__link { display: inline-block; padding: .25rem; text-decoration: none; }
.hypha-tabs { padding: 0; margin: 0; } .hypha-tabs { padding: 0; margin: 0; }
@ -157,6 +159,12 @@ header { width: 100%; margin-bottom: 1rem; }
main { padding: 1rem; margin: 0; } main { padding: 1rem; margin: 0; }
} }
@media screen and (min-width: 500px) {
.non-existent-hypha__way { flex: 1; margin-right: .5rem; }
.non-existent-hypha__ways { flex-direction: row; }
.non-existent-hypha__way:last-child { margin-right: 0; }
}
/* No longer a phone but still small screen: draw normal tabs, center main */ /* No longer a phone but still small screen: draw normal tabs, center main */
@media screen and (min-width: 801px) { @media screen and (min-width: 801px) {
.main-width { padding: 1rem 2rem; width: 800px; margin: 0 auto; } .main-width { padding: 1rem 2rem; width: 800px; margin: 0 auto; }
@ -225,15 +233,15 @@ textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;
.icon {margin-right: .25rem; vertical-align: bottom; } .icon {margin-right: .25rem; vertical-align: bottom; }
main h1:not(.navi-title) {font-size:1.7rem;} main h1:not(.navi-title) {font-size:1.7rem;}
blockquote { margin-left: 0; padding-left: 1rem; } blockquote { margin: 0; padding-left: .75rem; }
.wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; } .wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; }
/* .wikilink_external { padding-left: 16px; } */ /* .wikilink_external { padding-left: 16px; } */
.wikilink_gopher::before { content: url("/static/icon/gopher"); } .wikilink_gopher::before { content: url("/assets/icon/gopher"); }
.wikilink_http::before { content: url("/static/icon/http"); } .wikilink_http::before { content: url("/assets/icon/http"); }
.wikilink_https::before { content: url("/static/icon/http"); } .wikilink_https::before { content: url("/assets/icon/http"); }
/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */ /* .wikilink_https { background: transparent url("/assets/icon/http") center left no-repeat; } */
.wikilink_gemini::before { content: url("/static/icon/gemini"); } .wikilink_gemini::before { content: url("/assets/icon/gemini"); }
.wikilink_mailto::before { content: url("/static/icon/mailto"); } .wikilink_mailto::before { content: url("/assets/icon/mailto"); }
article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; } article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; }
main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; } main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; }
@ -242,7 +250,7 @@ main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; }
.heading__link:hover::after, .heading__link:active::after { color: #999; } .heading__link:hover::after, .heading__link:active::after { color: #999; }
article p { margin: .5rem 0; } article p { margin: .5rem 0; }
article ul, ol { padding-left: 1.5rem; margin: .5rem 0; } article ul, ol { padding-left: 1.5rem; margin: .5rem 0; }
article code { padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article code { padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; font-family: 'Menlo', 'PT Mono', monospace; }
article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25rem;} article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
.codeblock code {padding:0; font-size:15px;} .codeblock code {padding:0; font-size:15px;}
.transclusion { border-radius: .25rem; } .transclusion { border-radius: .25rem; }
@ -253,9 +261,11 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25
/* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */ /* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */
.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } .launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); }
.binary-container { width: 100%; }
.binary-container > a { display: flex; justify-content: center; }
.binary-container_with-img img, .binary-container_with-img img,
.binary-container_with-video video, .binary-container_with-video video,
.binary-container_with-audio audio {width: 100%} .binary-container_with-audio audio {max-height: 30em; width: auto; }
.subhyphae__title { padding-bottom: .5rem; clear: both; } .subhyphae__title { padding-bottom: .5rem; clear: both; }
.navi-title { padding-bottom: .5rem; margin: .25rem 0; } .navi-title { padding-bottom: .5rem; margin: .25rem 0; }
@ -309,9 +319,16 @@ caption { caption-side: top; font-size: small; }
.relative-hyphae__entry_this { padding: .25rem .5rem; font-weight: bold; } .relative-hyphae__entry_this { padding: .25rem .5rem; font-weight: bold; }
.relative-hyphae__link { text-decoration: none; display: block; padding: .25rem .5rem; } .relative-hyphae__link { text-decoration: none; display: block; padding: .25rem .5rem; }
::-webkit-file-upload-button,
.btn { line-height: normal; display: inline-block; border: 1px #999 solid; border-radius: .25rem; text-decoration: none; padding: .25rem; font-size: 1rem; margin: 0; }
.btn_weak { border: 1px #999 dashed; }
/* Color stuff */ /* Color stuff */
/* Lighter stuff #eee */ /* Lighter stuff #eee */
::-webkit-file-upload-button, .btn { background-color: #eee; color: black; }
.btn:visited { color: black; }
.btn_weak { background-color: transparent; }
article code, article code,
article .codeblock, article .codeblock,
.transclusion, .transclusion,
@ -336,18 +353,17 @@ table { background-color: #eee; }
.layout-card { border-radius: .25rem; background-color: white; } .layout-card { border-radius: .25rem; background-color: white; }
.layout-card__title { font-size: 1rem; margin: 0; padding: .25rem .5rem; border-radius: .25rem .25rem 0 0; } .layout-card__title { font-size: 1rem; margin: 0; padding: .25rem .5rem; border-radius: .25rem .25rem 0 0; }
.layout-card__title { background-color: #eee; } .layout-card__title { border-bottom: 1px solid #eee; }
/* Other stuff */ /* Other stuff */
html { background-color: #ddd; html { background-color: #eee;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='199' viewBox='0 0 100 199'%3E%3Cg fill='%23bbbbbb' %3E%3Cpath d='M0 199V0h1v1.99L100 199h-1.12L1 4.22V199H0zM100 2h-.12l-1-2H100v2z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"); }
} /* heropatterns.com */ header { background-color: #eee; }
header { background-color: #bbb; }
.header-links__link { color: black; } .header-links__link { color: black; }
.header-links__link:hover { background-color: #eee; } .header-links__link:hover { background-color: #ddd; }
main { background-color: white; } main { background-color: white; }
blockquote { border-left: 4px black solid; } blockquote { border-left: 2px #999 solid; }
.wikilink_new {color:#a55858;} .wikilink_new {color:#a55858;}
.transclusion code, .transclusion .codeblock {background-color:#ddd;} .transclusion code, .transclusion .codeblock {background-color:#ddd;}
.transclusion__link { color: black; } .transclusion__link { color: black; }
@ -375,9 +391,12 @@ a:visited, .wikilink_external:visited { color: #ffb86c; }
.hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; } .hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; }
.layout-card__title, .hypha-tabs__tab_active { background-color: #343434; } .layout-card__title, .hypha-tabs__tab_active { background-color: #343434; }
blockquote { border-left: 4px #ddd solid; }
.transclusion .transclusion__link { color: #ddd; } .transclusion .transclusion__link { color: #ddd; }
input[type="text"], input[type="password"],
::-webkit-file-upload-button,
.btn,
article code, article code,
article .codeblock, article .codeblock,
.transclusion, .transclusion,
@ -388,6 +407,11 @@ article .codeblock,
.upload-amnt, .upload-amnt,
textarea, textarea,
table { border: 0; background-color: #444444; color: #ddd; } table { border: 0; background-color: #444444; color: #ddd; }
.btn:visited { color: #ddd;}
.btn { border: #444 solid 1px; border-radius: .25rem; }
.btn_weak { background-color: transparent; }
.transclusion code, .transclusion code,
.transclusion .codeblock { background-color: #454545; } .transclusion .codeblock { background-color: #454545; }
mark { background: rgba(130, 80, 30, 5); color: inherit; } mark { background: rgba(130, 80, 30, 5); color: inherit; }
@ -484,7 +508,7 @@ const wrapBold = selectionWrapper(2, '**'),
//line assets/assets.qtpl:14 //line assets/assets.qtpl:14
qw422016.N().S(`'), qw422016.N().S(`'),
wrapHighlighted = selectionWrapper(2, '!!'), wrapHighlighted = selectionWrapper(2, '!!'),
wrapLifted = selectionWrapper(1, '^'), wrapLifted = selectionWrapper(2, '^^'),
wrapLowered = selectionWrapper(2, ',,'), wrapLowered = selectionWrapper(2, ',,'),
wrapStrikethrough = selectionWrapper(2, '~~'), wrapStrikethrough = selectionWrapper(2, '~~'),
wrapLink = selectionWrapper(2, '[[', ']]') wrapLink = selectionWrapper(2, '[[', ']]')
@ -529,6 +553,11 @@ function insertDate() {
textInserter(date)() textInserter(date)()
} }
function insertTimeUTC() {
let time = new Date().toISOString().substring(11, 19) + " UTC"
textInserter(time)()
}
function insertUserlink() { function insertUserlink() {
const userlink = document.querySelector('.header-links__entry_user a') const userlink = document.querySelector('.header-links__entry_user a')
const userHypha = userlink.getAttribute('href').substring(7) // no /hypha/ const userHypha = userlink.getAttribute('href').substring(7) // no /hypha/

View File

@ -1,13 +1,15 @@
.non-existent-hypha { }
.non-existent-hypha__ways { display: flex; flex-direction: column; width: 100%; margin: 0 0 1rem 0;}
.non-existent-hypha__way { border: 1px #999 solid; border-radius: .25rem; padding: .25rem; }
.non-existent-hypha__title { margin-bottom: 1rem; }
.non-existent-hypha__subtitle { margin: 0; }
.amnt-grid { display: grid; grid-template-columns: 1fr 1fr; } .amnt-grid { display: grid; grid-template-columns: 1fr 1fr; }
.upload-binary__input { display: block; margin: .25rem 0; } #upload-binary__input { display: block; margin: .25rem 0 .25rem 0; }
.modal__title { font-size: 2rem; } .modal__title { font-size: 2rem; }
.modal__title_small { font-size: 1.5rem; } .modal__title_small { font-size: 1.5rem; }
.modal__confirmation-msg { margin: 0 0 .5rem 0; } .modal__confirmation-msg { margin: 0 0 .5rem 0; }
.modal__action { display: inline-block; font-size: 1rem; padding: .25rem; border-radius: .25rem; }
.modal__submit { border: 1px #999 solid; }
.modal__cancel { border: 1px #999 dashed; text-decoration: none; }
.hypha-list { padding-left: 0; } .hypha-list { padding-left: 0; }
.hypha-list__entry { list-style-type: none; } .hypha-list__entry { list-style-type: none; }
@ -23,7 +25,7 @@ header { width: 100%; margin-bottom: 1rem; }
.header-links__entry, .hypha-tabs__tab { list-style-type: none; } .header-links__entry, .hypha-tabs__tab { list-style-type: none; }
.header-links__entry { margin-right: .5rem; } .header-links__entry { margin-right: .5rem; }
.header-links__entry_user { font-style:italic; } .header-links__entry_user, .header-links__entry_register { font-style:italic; }
.header-links__link { display: inline-block; padding: .25rem; text-decoration: none; } .header-links__link { display: inline-block; padding: .25rem; text-decoration: none; }
.hypha-tabs { padding: 0; margin: 0; } .hypha-tabs { padding: 0; margin: 0; }
@ -42,6 +44,12 @@ header { width: 100%; margin-bottom: 1rem; }
main { padding: 1rem; margin: 0; } main { padding: 1rem; margin: 0; }
} }
@media screen and (min-width: 500px) {
.non-existent-hypha__way { flex: 1; margin-right: .5rem; }
.non-existent-hypha__ways { flex-direction: row; }
.non-existent-hypha__way:last-child { margin-right: 0; }
}
/* No longer a phone but still small screen: draw normal tabs, center main */ /* No longer a phone but still small screen: draw normal tabs, center main */
@media screen and (min-width: 801px) { @media screen and (min-width: 801px) {
.main-width { padding: 1rem 2rem; width: 800px; margin: 0 auto; } .main-width { padding: 1rem 2rem; width: 800px; margin: 0 auto; }
@ -110,15 +118,15 @@ textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;
.icon {margin-right: .25rem; vertical-align: bottom; } .icon {margin-right: .25rem; vertical-align: bottom; }
main h1:not(.navi-title) {font-size:1.7rem;} main h1:not(.navi-title) {font-size:1.7rem;}
blockquote { margin-left: 0; padding-left: 1rem; } blockquote { margin: 0; padding-left: .75rem; }
.wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; } .wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; }
/* .wikilink_external { padding-left: 16px; } */ /* .wikilink_external { padding-left: 16px; } */
.wikilink_gopher::before { content: url("/static/icon/gopher"); } .wikilink_gopher::before { content: url("/assets/icon/gopher"); }
.wikilink_http::before { content: url("/static/icon/http"); } .wikilink_http::before { content: url("/assets/icon/http"); }
.wikilink_https::before { content: url("/static/icon/http"); } .wikilink_https::before { content: url("/assets/icon/http"); }
/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */ /* .wikilink_https { background: transparent url("/assets/icon/http") center left no-repeat; } */
.wikilink_gemini::before { content: url("/static/icon/gemini"); } .wikilink_gemini::before { content: url("/assets/icon/gemini"); }
.wikilink_mailto::before { content: url("/static/icon/mailto"); } .wikilink_mailto::before { content: url("/assets/icon/mailto"); }
article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; } article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; }
main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; } main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; }
@ -127,7 +135,7 @@ main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; }
.heading__link:hover::after, .heading__link:active::after { color: #999; } .heading__link:hover::after, .heading__link:active::after { color: #999; }
article p { margin: .5rem 0; } article p { margin: .5rem 0; }
article ul, ol { padding-left: 1.5rem; margin: .5rem 0; } article ul, ol { padding-left: 1.5rem; margin: .5rem 0; }
article code { padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article code { padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; font-family: 'Menlo', 'PT Mono', monospace; }
article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25rem;} article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
.codeblock code {padding:0; font-size:15px;} .codeblock code {padding:0; font-size:15px;}
.transclusion { border-radius: .25rem; } .transclusion { border-radius: .25rem; }
@ -138,9 +146,11 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25
/* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */ /* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */
.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } .launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); }
.binary-container { width: 100%; }
.binary-container > a { display: flex; justify-content: center; }
.binary-container_with-img img, .binary-container_with-img img,
.binary-container_with-video video, .binary-container_with-video video,
.binary-container_with-audio audio {width: 100%} .binary-container_with-audio audio {max-height: 30em; width: auto; }
.subhyphae__title { padding-bottom: .5rem; clear: both; } .subhyphae__title { padding-bottom: .5rem; clear: both; }
.navi-title { padding-bottom: .5rem; margin: .25rem 0; } .navi-title { padding-bottom: .5rem; margin: .25rem 0; }
@ -166,7 +176,7 @@ figcaption { padding-bottom: .5rem; }
.rc-entry__links, .rc-entry__msg { grid-column: 1 / span 2; } .rc-entry__links, .rc-entry__msg { grid-column: 1 / span 2; }
.rc-entry__author { font-style: italic; } .rc-entry__author { font-style: italic; }
.prevnext__el { display: inline-block; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; } .prevnext__el { display: inline-block; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; max-width: 49%; }
.prevnext__prev { float: left; } .prevnext__prev { float: left; }
.prevnext__next { float: right; text-align: right; } .prevnext__next { float: right; text-align: right; }
@ -194,9 +204,16 @@ caption { caption-side: top; font-size: small; }
.relative-hyphae__entry_this { padding: .25rem .5rem; font-weight: bold; } .relative-hyphae__entry_this { padding: .25rem .5rem; font-weight: bold; }
.relative-hyphae__link { text-decoration: none; display: block; padding: .25rem .5rem; } .relative-hyphae__link { text-decoration: none; display: block; padding: .25rem .5rem; }
::-webkit-file-upload-button,
.btn { line-height: normal; display: inline-block; border: 1px #999 solid; border-radius: .25rem; text-decoration: none; padding: .25rem; font-size: 1rem; margin: 0; }
.btn_weak { border: 1px #999 dashed; }
/* Color stuff */ /* Color stuff */
/* Lighter stuff #eee */ /* Lighter stuff #eee */
::-webkit-file-upload-button, .btn { background-color: #eee; color: black; }
.btn:visited { color: black; }
.btn_weak { background-color: transparent; }
article code, article code,
article .codeblock, article .codeblock,
.transclusion, .transclusion,
@ -221,18 +238,17 @@ table { background-color: #eee; }
.layout-card { border-radius: .25rem; background-color: white; } .layout-card { border-radius: .25rem; background-color: white; }
.layout-card__title { font-size: 1rem; margin: 0; padding: .25rem .5rem; border-radius: .25rem .25rem 0 0; } .layout-card__title { font-size: 1rem; margin: 0; padding: .25rem .5rem; border-radius: .25rem .25rem 0 0; }
.layout-card__title { background-color: #eee; } .layout-card__title { border-bottom: 1px solid #eee; }
/* Other stuff */ /* Other stuff */
html { background-color: #ddd; html { background-color: #eee;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='199' viewBox='0 0 100 199'%3E%3Cg fill='%23bbbbbb' %3E%3Cpath d='M0 199V0h1v1.99L100 199h-1.12L1 4.22V199H0zM100 2h-.12l-1-2H100v2z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E"); }
} /* heropatterns.com */ header { background-color: #eee; }
header { background-color: #bbb; }
.header-links__link { color: black; } .header-links__link { color: black; }
.header-links__link:hover { background-color: #eee; } .header-links__link:hover { background-color: #ddd; }
main { background-color: white; } main { background-color: white; }
blockquote { border-left: 4px black solid; } blockquote { border-left: 2px #999 solid; }
.wikilink_new {color:#a55858;} .wikilink_new {color:#a55858;}
.transclusion code, .transclusion .codeblock {background-color:#ddd;} .transclusion code, .transclusion .codeblock {background-color:#ddd;}
.transclusion__link { color: black; } .transclusion__link { color: black; }
@ -260,9 +276,12 @@ a:visited, .wikilink_external:visited { color: #ffb86c; }
.hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; } .hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; }
.layout-card__title, .hypha-tabs__tab_active { background-color: #343434; } .layout-card__title, .hypha-tabs__tab_active { background-color: #343434; }
blockquote { border-left: 4px #ddd solid; }
.transclusion .transclusion__link { color: #ddd; } .transclusion .transclusion__link { color: #ddd; }
input[type="text"], input[type="password"],
::-webkit-file-upload-button,
.btn,
article code, article code,
article .codeblock, article .codeblock,
.transclusion, .transclusion,
@ -273,6 +292,11 @@ article .codeblock,
.upload-amnt, .upload-amnt,
textarea, textarea,
table { border: 0; background-color: #444444; color: #ddd; } table { border: 0; background-color: #444444; color: #ddd; }
.btn:visited { color: #ddd;}
.btn { border: #444 solid 1px; border-radius: .25rem; }
.btn_weak { background-color: transparent; }
.transclusion code, .transclusion code,
.transclusion .codeblock { background-color: #454545; } .transclusion .codeblock { background-color: #454545; }
mark { background: rgba(130, 80, 30, 5); color: inherit; } mark { background: rgba(130, 80, 30, 5); color: inherit; }

View File

@ -2,7 +2,7 @@ WikiName = Mycorrhiza (dev)
NaviTitleIcon = 🧑‍💻 NaviTitleIcon = 🧑‍💻
[Hyphae] [Hyphae]
HomeHypha = home HomeHypha = mycorrhiza_wiki
UserHypha = u UserHypha = u
HeaderLinksHypha = header-links HeaderLinksHypha = header-links
@ -17,3 +17,8 @@ FixedAuthCredentialsPath = mycocredentials.json
UseRegistration = true UseRegistration = true
RegistrationCredentialsPath = mycoregistration.json RegistrationCredentialsPath = mycoregistration.json
LimitRegistration = 3 LimitRegistration = 3
[CustomScripts]
OmnipresentScripts = https://lesarbr.es/do-the-roll.js
ViewScripts = https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/components/prism-core.min.js,https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/plugins/autoloader/prism-autoloader.min.js
EditScripts = https://example.org

View File

@ -42,7 +42,7 @@ const wrapBold = selectionWrapper(2, '**'),
wrapItalic = selectionWrapper(2, '//'), wrapItalic = selectionWrapper(2, '//'),
wrapMonospace = selectionWrapper(1, '`'), wrapMonospace = selectionWrapper(1, '`'),
wrapHighlighted = selectionWrapper(2, '!!'), wrapHighlighted = selectionWrapper(2, '!!'),
wrapLifted = selectionWrapper(1, '^'), wrapLifted = selectionWrapper(2, '^^'),
wrapLowered = selectionWrapper(2, ',,'), wrapLowered = selectionWrapper(2, ',,'),
wrapStrikethrough = selectionWrapper(2, '~~'), wrapStrikethrough = selectionWrapper(2, '~~'),
wrapLink = selectionWrapper(2, '[[', ']]') wrapLink = selectionWrapper(2, '[[', ']]')
@ -63,6 +63,11 @@ function insertDate() {
textInserter(date)() textInserter(date)()
} }
function insertTimeUTC() {
let time = new Date().toISOString().substring(11, 19) + " UTC"
textInserter(time)()
}
function insertUserlink() { function insertUserlink() {
const userlink = document.querySelector('.header-links__entry_user a') const userlink = document.querySelector('.header-links__entry_user a')
const userHypha = userlink.getAttribute('href').substring(7) // no /hypha/ const userHypha = userlink.getAttribute('href').substring(7) // no /hypha/

157
cfg/config.go Normal file
View File

@ -0,0 +1,157 @@
// Package cfg contains global variables that represent the current wiki configuration, including CLI options, configuration file values and header links.
package cfg
import (
"log"
"path/filepath"
"strconv"
"github.com/go-ini/ini"
)
// These variables represent the configuration. You are not meant to modify them after they were set.
//
// See https://mycorrhiza.lesarbr.es/hypha/configuration/fields for their docs.
var (
WikiName string
NaviTitleIcon string
HomeHypha string
UserHypha string
HeaderLinksHypha string
HTTPPort string
URL string
GeminiCertificatePath string
UseFixedAuth bool
FixedAuthCredentialsPath string
UseRegistration bool
RegistrationCredentialsPath string
LimitRegistration int
OmnipresentScripts []string
ViewScripts []string
EditScripts []string
)
// These variables are set before reading the config file, they are set in main.parseCliArgs.
var (
// WikiDir is a full path to the wiki storage directory, which also must be a git repo.
WikiDir string
// ConfigFilePath is a path to the config file. Its value is used when calling ReadConfigFile.
ConfigFilePath string
)
// Config represents a Mycorrhiza wiki configuration file. This type is used only when reading configs.
type Config struct {
WikiName string
NaviTitleIcon string
Hyphae
Network
Authorization
CustomScripts
}
// Hyphae is a section of Config which has fields related to special hyphae.
type Hyphae struct {
HomeHypha string
UserHypha string
HeaderLinksHypha string
}
// Network is a section of Config that has fields related to network stuff: HTTP and Gemini.
type Network struct {
HTTPPort uint64
URL string
GeminiCertificatePath string
}
// CustomScripts is a section with paths to JavaScript files that are loaded on specified pages.
type CustomScripts struct {
// OmnipresentScripts: everywhere...
OmnipresentScripts []string `delim:","`
// ViewScripts: /hypha, /rev
ViewScripts []string `delim:","`
// Edit: /edit
EditScripts []string `delim:","`
}
// Authorization is a section of Config that has fields related to authorization and authentication.
type Authorization struct {
UseFixedAuth bool
FixedAuthCredentialsPath string
UseRegistration bool
RegistrationCredentialsPath string
LimitRegistration uint64
}
// ReadConfigFile reads a config on the given path and stores the configuration. Call it sometime during the initialization.
//
// Note that it may log.Fatal.
func ReadConfigFile() {
cfg := &Config{
WikiName: "MycorrhizaWiki",
NaviTitleIcon: "🍄",
Hyphae: Hyphae{
HomeHypha: "home",
UserHypha: "u",
HeaderLinksHypha: "",
},
Network: Network{
HTTPPort: 1737,
URL: "",
GeminiCertificatePath: "",
},
Authorization: Authorization{
UseFixedAuth: false,
FixedAuthCredentialsPath: "",
UseRegistration: false,
RegistrationCredentialsPath: "",
LimitRegistration: 0,
},
CustomScripts: CustomScripts{
OmnipresentScripts: []string{},
ViewScripts: []string{},
EditScripts: []string{},
},
}
if ConfigFilePath != "" {
path, err := filepath.Abs(ConfigFilePath)
if err != nil {
log.Fatalf("cannot expand config file path: %s", err)
}
log.Println("Loading config at", path)
err = ini.MapTo(cfg, path)
if err != nil {
log.Fatal(err)
}
}
// Map the struct to the global variables
WikiName = cfg.WikiName
NaviTitleIcon = cfg.NaviTitleIcon
HomeHypha = cfg.HomeHypha
UserHypha = cfg.UserHypha
HeaderLinksHypha = cfg.HeaderLinksHypha
HTTPPort = strconv.FormatUint(cfg.HTTPPort, 10)
URL = cfg.URL
GeminiCertificatePath = cfg.GeminiCertificatePath
UseFixedAuth = cfg.UseFixedAuth
FixedAuthCredentialsPath = cfg.FixedAuthCredentialsPath
UseRegistration = cfg.UseRegistration
RegistrationCredentialsPath = cfg.RegistrationCredentialsPath
LimitRegistration = int(cfg.LimitRegistration)
OmnipresentScripts = cfg.OmnipresentScripts
ViewScripts = cfg.ViewScripts
EditScripts = cfg.EditScripts
// This URL makes much more sense.
if URL == "" {
URL = "http://0.0.0.0:" + HTTPPort
}
}

50
cfg/header_links.go Normal file
View File

@ -0,0 +1,50 @@
package cfg
// See https://mycorrhiza.lesarbr.es/hypha/configuration/header
import (
"github.com/bouncepaw/mycomarkup/blocks"
"strings"
)
// HeaderLinks is a list off current header links. Feel free to iterate it directly but do not modify it by yourself. Call ParseHeaderLinks if you need to set new header links.
var HeaderLinks []HeaderLink
// SetDefaultHeaderLinks sets the header links to the default list of: home hypha, recent changes, hyphae list, random hypha.
func SetDefaultHeaderLinks() {
HeaderLinks = []HeaderLink{
{"/", WikiName},
{"/recent-changes", "Recent changes"},
{"/list", "All hyphae"},
{"/random", "Random"},
}
}
// ParseHeaderLinks extracts all rocketlinks from the given text and saves them as header links.
func ParseHeaderLinks(text string) {
HeaderLinks = []HeaderLink{}
for _, line := range strings.Split(text, "\n") {
// There is a false positive when parsing markup like that:
//
// ```
// => this is not a link, it is part of the preformatted block
// ```
//
// I do not really care.
if strings.HasPrefix(line, "=>") {
rl := blocks.MakeRocketLink(line, HeaderLinksHypha)
href, display := rl.Href(), rl.Display()
HeaderLinks = append(HeaderLinks, HeaderLink{
Href: href,
Display: display,
})
}
}
}
// HeaderLink represents a header link. Header links are the links shown in the top gray bar.
type HeaderLink struct {
// Href is the URL of the link. It goes <a href="here">...</a>.
Href string
// Display is what is shown when the link is rendered. It goes <a href="...">here</a>.
Display string
}

31
files/Structure.md Normal file
View File

@ -0,0 +1,31 @@
# The Structure
Here I am, doing new stuff before finishing the old stuff.
See https://github.com/bouncepaw/mycorrhiza/issues/57 for the discussion.
## The idea
Instead of letting users figure everything out by themselves, we think of the best (in our opinion) file layout and force it onto the users and remove the possibility of configuring it.
## What is inside the Structure
### Root
The whole wiki is inside one directory or inside one of its subdirectories. Only the Mycorrhiza binary itself and Git (and possible future runtime dependencies) might be outside that directory. That directory (called _root directory_) can have any name.
### Subdirectories
* `wiki.git` is a valid Git repository. If it is not present or is not a valid Git repository, the engine shall fail to work. When the Wizard is implemented, the engine will offer to make the Git repository.
* `cache` contains temporary files such as user token caches. Wiki administrators can safely delete this directory and expect the wiki to continue working. In the future, stuff like pre-rendered HTML can be stored here.
* All other subdirectories are ignored.
### User configuration
* `registered-users.json` contains a JSON array of all registered users. The engine will edit this file, and the administrators should not edit by themselves, unless they really want to.
* `fixed-users.json` contains a JSON array of all fixed users. Wiki administrators will edit this file by themselves.
### Wiki configuration
* `config.ini` is the main configuration file.
### Customisation
* `favicon.ico` is the Favicon as you know it.
* `common.css` redefines the built-in CSS, the Common style.
* `custom.css` is sent to the user after the Common style.
### Meta
* `README.txt` contains a short description of the files that can be inside the Structure. A small reminder for the administrators.

View File

@ -1,13 +1,14 @@
// Package files is used to get paths to different files Mycorrhiza uses. Also see cfg.
package files package files
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
) )
@ -17,9 +18,20 @@ var paths struct {
fixedCredentialsJSON string fixedCredentialsJSON string
} }
func TokensJSON() string { return paths.tokensJSON } // TokensJSON returns a path to the JSON file where users' tokens are stored.
//
// Default path: $XDG_DATA_HOME/mycorrhiza/tokens.json
func TokensJSON() string { return paths.tokensJSON }
// RegistrationCredentialsJSON returns a path to the JSON file where registration credentials are stored.
//
// Default path: $XDG_DATA_HOME/mycorrhiza/registration.json
func RegistrationCredentialsJSON() string { return paths.registrationCredentialsJSON } func RegistrationCredentialsJSON() string { return paths.registrationCredentialsJSON }
func FixedCredentialsJSON() string { return paths.fixedCredentialsJSON }
// FixedCredentialsJSON returns a path to the JSON file where fixed credentials are stored.
//
// There is no default path.
func FixedCredentialsJSON() string { return paths.fixedCredentialsJSON }
// CalculatePaths looks for all external paths and stores them. Tries its best to find any errors. It is safe it to call it multiple times in order to save new paths. // CalculatePaths looks for all external paths and stores them. Tries its best to find any errors. It is safe it to call it multiple times in order to save new paths.
func CalculatePaths() error { func CalculatePaths() error {
@ -49,7 +61,7 @@ func tokenStoragePath() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if strings.HasPrefix(dir, util.WikiDir) { if strings.HasPrefix(dir, cfg.WikiDir) {
return "", errors.New("wiki storage directory includes private config files") return "", errors.New("wiki storage directory includes private config files")
} }
return dir, nil return dir, nil
@ -57,7 +69,7 @@ func tokenStoragePath() (string, error) {
func registrationCredentialsPath() (string, error) { func registrationCredentialsPath() (string, error) {
var err error var err error
path := util.RegistrationCredentialsPath path := cfg.RegistrationCredentialsPath
if len(path) == 0 { if len(path) == 0 {
path, err = xdg.DataFile("mycorrhiza/registration.json") path, err = xdg.DataFile("mycorrhiza/registration.json")
@ -81,7 +93,7 @@ func registrationCredentialsPath() (string, error) {
func fixedCredentialsPath() (string, error) { func fixedCredentialsPath() (string, error) {
var err error var err error
path := util.FixedCredentialsPath path := cfg.FixedAuthCredentialsPath
if len(path) > 0 { if len(path) > 0 {
path, err = homedir.Expand(path) path, err = homedir.Expand(path)

43
flag.go
View File

@ -3,30 +3,38 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"github.com/bouncepaw/mycorrhiza/assets" "github.com/bouncepaw/mycorrhiza/assets"
"github.com/bouncepaw/mycorrhiza/util"
) )
// CLI options are read and parsed here.
var printExampleConfig bool var printExampleConfig bool
func init() { func init() {
flag.StringVar(&util.ConfigFilePath, "config-path", "", "Path to a configuration file. Leave empty if you don't want to use it.") flag.StringVar(&cfg.ConfigFilePath, "config-path", "", "Path to a configuration file. Leave empty if you don't want to use it.")
flag.BoolVar(&printExampleConfig, "print-example-config", false, "If true, print an example configuration file contents and exit. You can save the output to a file and base your own configuration on it.") flag.BoolVar(&printExampleConfig, "print-example-config", false, "If true, print an example configuration file contents and exit. You can save the output to a file and base your own configuration on it.")
flag.Usage = func() { flag.Usage = printHelp
fmt.Fprintf(
flag.CommandLine.Output(),
assets.HelpMessage(),
os.Args[0],
)
flag.PrintDefaults()
}
} }
// Do the things related to cli args and die maybe // printHelp prints the help message. The help message is stored in assets.
func printHelp() {
_, err := fmt.Fprintf(
flag.CommandLine.Output(),
assets.HelpMessage(),
os.Args[0],
)
if err != nil {
log.Fatal(err)
}
flag.PrintDefaults()
}
// parseCliArgs parses CLI options and sets several important global variables. Call it early.
func parseCliArgs() { func parseCliArgs() {
flag.Parse() flag.Parse()
@ -40,18 +48,9 @@ func parseCliArgs() {
log.Fatal("Error: pass a wiki directory") log.Fatal("Error: pass a wiki directory")
} }
var err error wikiDir, err := filepath.Abs(args[0])
WikiDir, err = filepath.Abs(args[0]) cfg.WikiDir = wikiDir
util.WikiDir = WikiDir
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if util.URL == "" {
util.URL = "http://0.0.0.0:" + util.ServerPort
}
util.HomePage = util.CanonicalName(util.HomePage)
util.UserHypha = util.CanonicalName(util.UserHypha)
util.HeaderLinksHypha = util.CanonicalName(util.HeaderLinksHypha)
} }

View File

@ -1,29 +1,36 @@
package main package main
// Gemini-related stuff. This is currently a proof-of-concept implementation, no one really uses it.
// Maybe we should deprecate it until we find power to do it properly?
//
// When this stuff gets more serious, a separate module will be needed.
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509/pkix" "crypto/x509/pkix"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"git.sr.ht/~adnano/go-gemini" "git.sr.ht/~adnano/go-gemini"
"git.sr.ht/~adnano/go-gemini/certificate" "git.sr.ht/~adnano/go-gemini/certificate"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
func geminiHomeHypha(w *gemini.ResponseWriter, rq *gemini.Request) { func geminiHomeHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
log.Println(rq.URL) log.Println(rq.URL)
w.Write([]byte(`# MycorrhizaWiki _, _ = io.WriteString(w, `# MycorrhizaWiki
You have successfully served the wiki through Gemini. Currently, support is really work-in-progress; you should resort to using Mycorrhiza through the web protocols. You have successfully served the wiki through Gemini. Currently, support is really work-in-progress; you should resort to using Mycorrhiza through the web protocols.
Visit home hypha: Visit home hypha:
=> /hypha/` + util.HomePage)) => /hypha/`+cfg.HomeHypha)
} }
func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) { func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
@ -37,21 +44,20 @@ func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
if h.Exists { if h.Exists {
fileContentsT, errT := ioutil.ReadFile(h.TextPath) fileContentsT, errT := ioutil.ReadFile(h.TextPath)
if errT == nil { if errT == nil {
md := markup.Doc(hyphaName, string(fileContentsT)) contents = string(fileContentsT)
contents = md.AsGemtext()
} }
} }
if hasAmnt { if hasAmnt {
w.Write([]byte("This hypha has an attachment\n")) _, _ = io.WriteString(w, "This hypha has an attachment\n")
} }
w.Write([]byte(contents)) _, _ = io.WriteString(w, contents)
} }
func handleGemini() { func handleGemini() {
if util.GeminiCertPath == "" { if cfg.GeminiCertificatePath == "" {
return return
} }
certPath, err := filepath.Abs(util.GeminiCertPath) certPath, err := filepath.Abs(cfg.GeminiCertificatePath)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -82,3 +88,15 @@ func handleGemini() {
log.Fatal(err) log.Fatal(err)
} }
} }
// geminiHyphaNameFromRq extracts hypha name from gemini request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
func geminiHyphaNameFromRq(rq *gemini.Request, actions ...string) string {
p := rq.URL.Path
for _, action := range actions {
if strings.HasPrefix(p, "/"+action+"/") {
return util.CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
}
}
log.Fatal("HyphaNameFromRq: no matching action passed")
return ""
}

3
go.mod
View File

@ -5,12 +5,13 @@ go 1.14
require ( require (
git.sr.ht/~adnano/go-gemini v0.1.13 git.sr.ht/~adnano/go-gemini v0.1.13
github.com/adrg/xdg v0.2.2 github.com/adrg/xdg v0.2.2
github.com/bouncepaw/mycomarkup v0.4.5
github.com/go-ini/ini v1.62.0 github.com/go-ini/ini v1.62.0
github.com/gorilla/feeds v1.1.1 github.com/gorilla/feeds v1.1.1
github.com/kr/pretty v0.2.1 // indirect github.com/kr/pretty v0.2.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/smartystreets/goconvey v1.6.4 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/valyala/quicktemplate v1.6.3 github.com/valyala/quicktemplate v1.6.3
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
) )

7
go.sum
View File

@ -3,6 +3,8 @@ git.sr.ht/~adnano/go-gemini v0.1.13/go.mod h1:If1VxEWcZDrRt5FeAFnGTcM2Ud1E3BXs3V
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/bouncepaw/mycomarkup v0.4.5 h1:QBLSGmqGsb3smjAmywosio2nVL7A8sR2KF5AB38GrXc=
github.com/bouncepaw/mycomarkup v0.4.5/go.mod h1:0n6thlGGgrx2Y/2NaaUH4qHW4v1xJ+EpW7yMFUxNRIg=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ini/ini v1.62.0 h1:7VJT/ZXjzqSrvtraFp4ONq80hTcRQth1c9ZnQ3uNQvU= github.com/go-ini/ini v1.62.0 h1:7VJT/ZXjzqSrvtraFp4ONq80hTcRQth1c9ZnQ3uNQvU=
@ -37,11 +39,14 @@ github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl
github.com/valyala/quicktemplate v1.6.3 h1:O7EuMwuH7Q94U2CXD6sOX8AYHqQqWtmIk690IhmpkKA= github.com/valyala/quicktemplate v1.6.3 h1:O7EuMwuH7Q94U2CXD6sOX8AYHqQqWtmIk690IhmpkKA=
github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY= github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -1,8 +1,10 @@
// Package history provides a git wrapper.
package history package history
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"html"
"log" "log"
"os/exec" "os/exec"
"regexp" "regexp"
@ -10,6 +12,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -19,20 +22,18 @@ var gitpath string
var renameMsgPattern = regexp.MustCompile(`^Rename (.*) to .*`) var renameMsgPattern = regexp.MustCompile(`^Rename (.*) to .*`)
// Start finds git and initializes git credentials. // Start finds git and initializes git credentials.
func Start(wikiDir string) { func Start() {
path, err := exec.LookPath("git") path, err := exec.LookPath("git")
if err != nil { if err != nil {
log.Fatal("Cound not find the git executable. Check your $PATH.") log.Fatal("Could not find the git executable. Check your $PATH.")
} else {
log.Println("Git path is", path)
} }
gitpath = path gitpath = path
_, err = gitsh("config", "user.name", "wikimind") _, err = silentGitsh("config", "user.name", "wikimind")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
_, err = gitsh("config", "user.email", "wikimind@mycorrhiza") _, err = silentGitsh("config", "user.email", "wikimind@mycorrhiza")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -44,9 +45,26 @@ type Revision struct {
Username string Username string
Time time.Time Time time.Time
Message string Message string
filesAffectedBuf []string
hyphaeAffectedBuf []string hyphaeAffectedBuf []string
} }
// filesAffected tells what files have been affected by the revision.
func (rev *Revision) filesAffected() (filenames []string) {
if nil != rev.filesAffectedBuf {
return rev.filesAffectedBuf
}
// List of files affected by this revision, one per line.
out, err := silentGitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
// There's an error? Well, whatever, let's just assign an empty slice, who cares.
if err != nil {
rev.filesAffectedBuf = []string{}
} else {
rev.filesAffectedBuf = strings.Split(out.String(), "\n")
}
return rev.filesAffectedBuf
}
// determine what hyphae were affected by this revision // determine what hyphae were affected by this revision
func (rev *Revision) hyphaeAffected() (hyphae []string) { func (rev *Revision) hyphaeAffected() (hyphae []string) {
if nil != rev.hyphaeAffectedBuf { if nil != rev.hyphaeAffectedBuf {
@ -54,8 +72,6 @@ func (rev *Revision) hyphaeAffected() (hyphae []string) {
} }
hyphae = make([]string, 0) hyphae = make([]string, 0)
var ( var (
// List of files affected by this revision, one per line.
out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
// set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most currently). // set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most currently).
set = make(map[string]bool) set = make(map[string]bool)
isNewName = func(hyphaName string) bool { isNewName = func(hyphaName string) bool {
@ -65,11 +81,9 @@ func (rev *Revision) hyphaeAffected() (hyphae []string) {
set[hyphaName] = true set[hyphaName] = true
return true return true
} }
filesAffected = rev.filesAffected()
) )
if err != nil { for _, filename := range filesAffected {
return hyphae
}
for _, filename := range strings.Split(out.String(), "\n") {
if strings.IndexRune(filename, '.') >= 0 { if strings.IndexRune(filename, '.') >= 0 {
dotPos := strings.LastIndexByte(filename, '.') dotPos := strings.LastIndexByte(filename, '.')
hyphaName := string([]byte(filename)[0:dotPos]) // is it safe? hyphaName := string([]byte(filename)[0:dotPos]) // is it safe?
@ -94,15 +108,44 @@ func (rev Revision) HyphaeLinksHTML() (html string) {
if i > 0 { if i > 0 {
html += `<span aria-hidden="true">, </span>` html += `<span aria-hidden="true">, </span>`
} }
html += fmt.Sprintf(`<a href="/page/%[1]s">%[1]s</a>`, hyphaName) html += fmt.Sprintf(`<a href="/hypha/%[1]s">%[1]s</a>`, hyphaName)
} }
return html return html
} }
func (rev *Revision) descriptionForFeed() (html string) { // descriptionForFeed generates a good enough HTML contents for a web feed.
func (rev *Revision) descriptionForFeed() (htmlDesc string) {
return fmt.Sprintf( return fmt.Sprintf(
`<p>%s</p> `<p>%s</p>
<p><b>Hyphae affected:</b> %s</p>`, rev.Message, rev.HyphaeLinksHTML()) <p><b>Hyphae affected:</b> %s</p>
<pre><code>%s</code></pre>`, rev.Message, rev.HyphaeLinksHTML(), html.EscapeString(rev.textDiff()))
}
// textDiff generates a good enough diff to display in a web feed. It is not html-escaped.
func (rev *Revision) textDiff() (diff string) {
filenames, ok := rev.mycoFiles()
if !ok {
return "No text changes"
}
for _, filename := range filenames {
text, err := PrimitiveDiffAtRevision(filename, rev.Hash)
if err != nil {
diff += "\nAn error has occured with " + filename + "\n"
}
diff += text + "\n"
}
return diff
}
// mycoFiles returns filenames of .myco file. It is not ok if there are no myco files.
func (rev *Revision) mycoFiles() (filenames []string, ok bool) {
filenames = []string{}
for _, filename := range rev.filesAffected() {
if strings.HasSuffix(filename, ".myco") {
filenames = append(filenames, filename)
}
}
return filenames, len(filenames) > 0
} }
// Try and guess what link is the most important by looking at the message. // Try and guess what link is the most important by looking at the message.
@ -113,11 +156,11 @@ func (rev *Revision) bestLink() string {
) )
switch { switch {
case renameRes != nil: case renameRes != nil:
return "/page/" + renameRes[1] return "/hypha/" + renameRes[1]
case len(revs) == 0: case len(revs) == 0:
return "" return ""
default: default:
return "/page/" + revs[0] return "/hypha/" + revs[0]
} }
} }
@ -126,7 +169,7 @@ func (rev *Revision) bestLink() string {
func gitsh(args ...string) (out bytes.Buffer, err error) { func gitsh(args ...string) (out bytes.Buffer, err error) {
fmt.Printf("$ %v\n", args) fmt.Printf("$ %v\n", args)
cmd := exec.Command(gitpath, args...) cmd := exec.Command(gitpath, args...)
cmd.Dir = util.WikiDir cmd.Dir = cfg.WikiDir
b, err := cmd.CombinedOutput() b, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@ -135,6 +178,15 @@ func gitsh(args ...string) (out bytes.Buffer, err error) {
return *bytes.NewBuffer(b), err return *bytes.NewBuffer(b), err
} }
// silentGitsh is like gitsh, except it writes less to the stdout.
func silentGitsh(args ...string) (out bytes.Buffer, err error) {
cmd := exec.Command(gitpath, args...)
cmd.Dir = cfg.WikiDir
b, err := cmd.CombinedOutput()
return *bytes.NewBuffer(b), err
}
// Convert a UNIX timestamp as string into a time. If nil is returned, it means that the timestamp could not be converted. // Convert a UNIX timestamp as string into a time. If nil is returned, it means that the timestamp could not be converted.
func unixTimestampAsTime(ts string) *time.Time { func unixTimestampAsTime(ts string) *time.Time {
i, err := strconv.ParseInt(ts, 10, 64) i, err := strconv.ParseInt(ts, 10, 64)

View File

@ -1,28 +1,29 @@
// information.go
// Things related to gathering existing information.
package history package history
// information.go
// Things related to gathering existing information.
import ( import (
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"log"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/gorilla/feeds" "github.com/gorilla/feeds"
) )
func recentChangesFeed() *feeds.Feed { func recentChangesFeed() *feeds.Feed {
feed := &feeds.Feed{ feed := &feeds.Feed{
Title: "Recent changes", Title: "Recent changes",
Link: &feeds.Link{Href: util.URL}, Link: &feeds.Link{Href: cfg.URL},
Description: "List of 30 recent changes on the wiki", Description: "List of 30 recent changes on the wiki",
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"}, Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
Updated: time.Now(), Updated: time.Now(),
} }
var ( var (
out, err = gitsh( out, err = silentGitsh(
"log", "--oneline", "--no-merges", "log", "--oneline", "--no-merges",
"--pretty=format:\"%h\t%ae\t%at\t%s\"", "--pretty=format:\"%h\t%ae\t%at\t%s\"",
"--max-count=30", "--max-count=30",
@ -34,6 +35,7 @@ func recentChangesFeed() *feeds.Feed {
revs = append(revs, parseRevisionLine(line)) revs = append(revs, parseRevisionLine(line))
} }
} }
log.Printf("Found %d recent changes", len(revs))
for _, rev := range revs { for _, rev := range revs {
feed.Add(&feeds.Item{ feed.Add(&feeds.Item{
Title: rev.Message, Title: rev.Message,
@ -42,7 +44,7 @@ func recentChangesFeed() *feeds.Feed {
Description: rev.descriptionForFeed(), Description: rev.descriptionForFeed(),
Created: rev.Time, Created: rev.Time,
Updated: rev.Time, Updated: rev.Time,
Link: &feeds.Link{Href: util.URL + rev.bestLink()}, Link: &feeds.Link{Href: cfg.URL + rev.bestLink()},
}) })
} }
return feed return feed
@ -62,7 +64,7 @@ func RecentChangesJSON() (string, error) {
func RecentChanges(n int) []Revision { func RecentChanges(n int) []Revision {
var ( var (
out, err = gitsh( out, err = silentGitsh(
"log", "--oneline", "--no-merges", "log", "--oneline", "--no-merges",
"--pretty=format:\"%h\t%ae\t%at\t%s\"", "--pretty=format:\"%h\t%ae\t%at\t%s\"",
"--max-count="+strconv.Itoa(n), "--max-count="+strconv.Itoa(n),
@ -74,6 +76,7 @@ func RecentChanges(n int) []Revision {
revs = append(revs, parseRevisionLine(line)) revs = append(revs, parseRevisionLine(line))
} }
} }
log.Printf("Found %d recent changes", len(revs))
return revs return revs
} }
@ -86,7 +89,7 @@ func FileChanged(path string) bool {
// Revisions returns slice of revisions for the given hypha name. // Revisions returns slice of revisions for the given hypha name.
func Revisions(hyphaName string) ([]Revision, error) { func Revisions(hyphaName string) ([]Revision, error) {
var ( var (
out, err = gitsh( out, err = silentGitsh(
"log", "--oneline", "--no-merges", "log", "--oneline", "--no-merges",
// Hash, author email, author time, commit msg separated by tab // Hash, author email, author time, commit msg separated by tab
"--pretty=format:\"%h\t%ae\t%at\t%s\"", "--pretty=format:\"%h\t%ae\t%at\t%s\"",
@ -101,6 +104,7 @@ func Revisions(hyphaName string) ([]Revision, error) {
} }
} }
} }
log.Printf("Found %d revisions for %s\n", len(revs), hyphaName)
return revs, err return revs, err
} }
@ -137,7 +141,7 @@ func (rev *Revision) asHistoryEntry(hyphaName string) (html string) {
author := "" author := ""
if rev.Username != "anon" { if rev.Username != "anon" {
author = fmt.Sprintf(` author = fmt.Sprintf(`
<span class="history-entry__author">by <a href="/page/%[1]s/%[2]s" rel="author">%[2]s</span>`, util.UserHypha, rev.Username) <span class="history-entry__author">by <a href="/page/%[1]s/%[2]s" rel="author">%[2]s</span>`, cfg.UserHypha, rev.Username)
} }
return fmt.Sprintf(` return fmt.Sprintf(`
<li class="history__entry"> <li class="history__entry">
@ -170,13 +174,20 @@ func parseRevisionLine(line string) Revision {
} }
} }
// See how the file with `filepath` looked at commit with `hash`. // FileAtRevision shows how the file with the given file path looked at the commit with the hash. It may return an error if git fails.
func FileAtRevision(filepath, hash string) (string, error) { func FileAtRevision(filepath, hash string) (string, error) {
out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, util.WikiDir+"/")) out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, cfg.WikiDir+"/"))
if err != nil {
return "", err
}
return out.String(), err return out.String(), err
} }
// PrimitiveDiffAtRevision generates a plain-text diff for the given filepath at the commit with the given hash. It may return an error if git fails.
func PrimitiveDiffAtRevision(filepath, hash string) (string, error) { func PrimitiveDiffAtRevision(filepath, hash string) (string, error) {
out, err := gitsh("diff", "--unified=1", "--no-color", hash+"~", hash, "--", filepath) out, err := silentGitsh("diff", "--unified=1", "--no-color", hash+"~", hash, "--", filepath)
if err != nil {
return "", err
}
return out.String(), err return out.String(), err
} }

View File

@ -1,7 +1,7 @@
// history/operations.go
// Things related to writing history.
package history package history
// history/operations.go
// Things related to writing history.
import ( import (
"fmt" "fmt"
"os" "os"
@ -84,7 +84,10 @@ func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp {
func (hop *HistoryOp) WithFilesRenamed(pairs map[string]string) *HistoryOp { func (hop *HistoryOp) WithFilesRenamed(pairs map[string]string) *HistoryOp {
for from, to := range pairs { for from, to := range pairs {
if from != "" { if from != "" {
os.MkdirAll(filepath.Dir(to), 0777) if err := os.MkdirAll(filepath.Dir(to), 0777); err != nil {
hop.Errs = append(hop.Errs, err)
continue
}
if err := Rename(from, to); err != nil { if err := Rename(from, to); err != nil {
hop.Errs = append(hop.Errs, err) hop.Errs = append(hop.Errs, err)
} }

View File

@ -27,6 +27,8 @@ func Index(path string) {
h.Insert() h.Insert()
} }
} }
log.Println("Indexed", Count(), "hyphae")
} }
// indexHelper finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller. // indexHelper finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller.

View File

@ -1,4 +1,4 @@
// The `hyphae` package is for the Hypha type, hypha storage and stuff like that. It shall not depend on mycorrhiza modules other than util. // Package hyphae is for the Hypha type, hypha storage and stuff like that. It shall not depend on mycorrhiza modules other than util.
package hyphae package hyphae
import ( import (
@ -8,7 +8,7 @@ import (
) )
// HyphaPattern is a pattern which all hyphae must match. // HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`)
type Hypha struct { type Hypha struct {
sync.RWMutex sync.RWMutex

View File

@ -1,138 +0,0 @@
package link
import (
"fmt"
"path"
"strings"
"github.com/bouncepaw/mycorrhiza/util"
)
// LinkType tells what type the given link is.
type LinkType int
const (
LinkInvalid LinkType = iota
// LinkLocalRoot is a link like "/list", "/user-list", etc.
LinkLocalRoot
// LinkLocalHypha is a link like "test", "../test", etc.
LinkLocalHypha
// LinkExternal is an external link with specified protocol.
LinkExternal
// LinkInterwiki is currently unused.
LinkInterwiki
)
// Link is an abstraction for universal representation of links, be they links in mycomarkup links or whatever.
type Link struct {
// Address is what the link points to.
Address string
// Display is what gets nested into the <a> tag.
Display string
Kind LinkType
DestinationUnknown bool
// #...
Anchor string
Protocol string
// How the link address looked originally in source text.
SrcAddress string
// How the link display text looked originally in source text. May be empty.
SrcDisplay string
// RelativeTo is hypha name to which the link is relative to.
RelativeTo string
}
// DoubtExistence sets DestinationUnknown to true if the link is local hypha link.
func (l *Link) DoubtExistence() {
if l.Kind == LinkLocalHypha {
l.DestinationUnknown = true
}
}
// Classes returns CSS class string for given link.
func (l *Link) Classes() string {
if l.Kind == LinkExternal {
return fmt.Sprintf("wikilink wikilink_external wikilink_%s", l.Protocol)
}
classes := "wikilink wikilink_internal"
if l.DestinationUnknown {
classes += " wikilink_new"
}
return classes
}
// Href returns content for the href attrubite for hyperlink. You should always use it.
func (l *Link) Href() string {
switch l.Kind {
case LinkExternal, LinkLocalRoot:
return l.Address + l.Anchor
default:
return "/hypha/" + l.Address + l.Anchor
}
}
// ImgSrc returns content for src attribute of img tag. Used with `img{}`.
func (l *Link) ImgSrc() string {
switch l.Kind {
case LinkExternal, LinkLocalRoot:
return l.Address
default:
return "/binary/" + l.Address
}
}
// From returns a Link object given these `address` and `display` on relative to given `hyphaName`.
func From(address, display, hyphaName string) *Link {
address = strings.TrimSpace(address)
link := Link{
SrcAddress: address,
SrcDisplay: display,
RelativeTo: hyphaName,
}
if display == "" {
link.Display = address
} else {
link.Display = strings.TrimSpace(display)
}
if pos := strings.IndexRune(address, '#'); pos != -1 && pos != 0 {
link.Anchor = address[pos:]
address = address[:pos]
}
switch {
case strings.ContainsRune(address, ':'):
pos := strings.IndexRune(address, ':')
link.Protocol = address[:pos]
link.Kind = LinkExternal
if display == "" {
link.Display = address[pos+1:]
if strings.HasPrefix(link.Display, "//") && len(link.Display) > 2 {
link.Display = link.Display[2:]
}
link.Display += link.Anchor
}
link.Address = address
case strings.HasPrefix(address, "#"):
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(hyphaName)
link.Anchor = address
case strings.HasPrefix(address, "/"):
link.Address = address
link.Kind = LinkLocalRoot
case strings.HasPrefix(address, "./"):
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(path.Join(hyphaName, address[2:]))
case strings.HasPrefix(address, "../"):
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(path.Join(path.Dir(hyphaName), address[3:]))
default:
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(address)
}
return &link
}

215
main.go
View File

@ -2,229 +2,46 @@
//go:generate qtc -dir=assets //go:generate qtc -dir=assets
//go:generate qtc -dir=views //go:generate qtc -dir=views
//go:generate qtc -dir=tree //go:generate qtc -dir=tree
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
package main package main
import ( import (
"fmt" "github.com/bouncepaw/mycorrhiza/cfg"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"strings"
"github.com/bouncepaw/mycorrhiza/assets"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/shroom" "github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/web"
"github.com/bouncepaw/mycorrhiza/views" "log"
"net/http"
"os"
) )
// WikiDir is a rooted path to the wiki storage directory.
var WikiDir string
// HttpErr is used by many handlers to signal errors in a compact way.
func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
log.Println(errMsg, "for", name)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(status)
fmt.Fprint(
w,
base(
title,
fmt.Sprintf(
`<main class="main-width"><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
errMsg,
name,
),
user.EmptyUser(),
),
)
}
// Show all hyphae
func handlerList(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
util.HTTP200Page(w, base("List of pages", views.HyphaListHTML(), user.FromRequest(rq)))
}
// This part is present in all html documents.
var base = views.BaseHTML
// Reindex all hyphae by checking the wiki storage directory anew.
func handlerReindex(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
if ok := user.CanProceed(rq, "reindex"); !ok {
HttpErr(w, http.StatusForbidden, util.HomePage, "Not enough rights", "You must be an admin to reindex hyphae.")
log.Println("Rejected", rq.URL)
return
}
hyphae.ResetCount()
log.Println("Wiki storage directory is", WikiDir)
log.Println("Start indexing hyphae...")
hyphae.Index(WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae")
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
// Stop the wiki
// Update header links by reading the configured hypha, if there is any, or resorting to default values.
func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
if ok := user.CanProceed(rq, "update-header-links"); !ok {
HttpErr(w, http.StatusForbidden, util.HomePage, "Not enough rights", "You must be a moderator to update header links.")
log.Println("Rejected", rq.URL)
return
}
shroom.SetHeaderLinks()
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
// Redirect to a random hypha.
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
randomHyphaName string
amountOfHyphae int = hyphae.Count()
)
if amountOfHyphae == 0 {
HttpErr(w, http.StatusNotFound, util.HomePage, "There are no hyphae",
"It is not possible to display a random hypha because the wiki does not contain any hyphae")
return
}
i := rand.Intn(amountOfHyphae)
for h := range hyphae.YieldExistingHyphae() {
if i == 0 {
randomHyphaName = h.Name
}
i--
}
http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther)
}
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
if _, err := os.Stat(util.WikiDir + "/static/common.css"); err == nil {
http.ServeFile(w, rq, util.WikiDir+"/static/common.css")
} else {
w.Header().Set("Content-Type", "text/css;charset=utf-8")
w.Write([]byte(assets.DefaultCSS()))
}
if bytes, err := ioutil.ReadFile(util.WikiDir + "/static/custom.css"); err == nil {
w.Write(bytes)
}
}
func handlerToolbar(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
w.Header().Set("Content-Type", "text/javascript;charset=utf-8")
w.Write([]byte(assets.ToolbarJS()))
}
func handlerIcon(w http.ResponseWriter, rq *http.Request) {
iconName := strings.TrimPrefix(rq.URL.Path, "/static/icon/")
if iconName == "https" {
iconName = "http"
}
files, err := ioutil.ReadDir(WikiDir + "/static/icon")
if err == nil {
for _, f := range files {
if strings.HasPrefix(f.Name(), iconName+"-protocol-icon") {
http.ServeFile(w, rq, WikiDir+"/static/icon/"+f.Name())
return
}
}
}
w.Header().Set("Content-Type", "image/svg+xml")
switch iconName {
case "gemini":
w.Write([]byte(assets.IconGemini()))
case "mailto":
w.Write([]byte(assets.IconMailto()))
case "gopher":
w.Write([]byte(assets.IconGopher()))
case "feed":
w.Write([]byte(assets.IconFeed()))
default:
w.Write([]byte(assets.IconHTTP()))
}
}
func handlerAbout(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(base("About "+util.SiteName, views.AboutHTML(), user.FromRequest(rq))))
}
func handlerUserList(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(base("User list", views.UserListHTML(), user.FromRequest(rq))))
}
func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte(
`User-agent: *
Allow: /page/
Allow: /recent-changes
Disallow: /
Crawl-delay: 5`))
}
func main() { func main() {
parseCliArgs() parseCliArgs()
// It is ok if the path is "" // It is ok if the path is ""
util.ReadConfigFile(util.ConfigFilePath) cfg.ReadConfigFile()
if err := files.CalculatePaths(); err != nil { if err := files.CalculatePaths(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Println("Running MycorrhizaWiki") log.Println("Running MycorrhizaWiki 1.2.0 indev")
if err := os.Chdir(WikiDir); err != nil { if err := os.Chdir(cfg.WikiDir); err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Println("Wiki storage directory is", WikiDir) log.Println("Wiki storage directory is", cfg.WikiDir)
hyphae.Index(WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae")
// Initialize user database // Init the subsystems:
hyphae.Index(cfg.WikiDir)
user.InitUserDatabase() user.InitUserDatabase()
history.Start()
history.Start(WikiDir)
shroom.SetHeaderLinks() shroom.SetHeaderLinks()
// Network:
go handleGemini() go handleGemini()
web.Init()
// See http_admin.go for /admin, /admin/* log.Fatal(http.ListenAndServe("0.0.0.0:"+cfg.HTTPPort, nil))
initAdmin()
// See http_readers.go for /page/, /hypha/, /text/, /binary/, /attachment/
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
// See http_history.go for /history/, /recent-changes
http.HandleFunc("/list", handlerList)
http.HandleFunc("/reindex", handlerReindex)
http.HandleFunc("/update-header-links", handlerUpdateHeaderLinks)
http.HandleFunc("/random", handlerRandom)
http.HandleFunc("/about", handlerAbout)
http.HandleFunc("/user-list", handlerUserList)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static"))))
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
})
http.HandleFunc("/static/common.css", handlerStyle)
http.HandleFunc("/static/toolbar.js", handlerToolbar)
http.HandleFunc("/static/icon/", handlerIcon)
http.HandleFunc("/robots.txt", handlerRobotsTxt)
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
http.Redirect(w, rq, "/hypha/"+util.HomePage, http.StatusSeeOther)
})
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
} }

View File

@ -1,34 +0,0 @@
package markup
import (
"unicode"
)
// MatchesHorizontalLine checks if the string can be interpreted as suitable for rendering as <hr/>.
//
// The rule is: if there are more than 4 characters "-" in the string, then make it a horizontal line.
// Otherwise it is a paragraph (<p>).
func MatchesHorizontalLine(line string) bool {
counter := 0
// Check initially that the symbol is "-". If it is not a "-", it is most likely a space or another character.
// With unicode.IsLetter() we can separate spaces and characters.
for _, ch := range line {
if ch == '-' {
counter++
continue
}
// If we bump into any other character (letter) in the line, it is immediately an incorrect horizontal line.
// There is no point in counting further, we end the loop.
if unicode.IsLetter(ch) {
counter = 0
break
}
}
if counter >= 4 {
return true
}
return false
}

View File

@ -1,203 +0,0 @@
package markup
import (
"fmt"
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/link"
)
var imgRe = regexp.MustCompile(`^img\s+{`)
func MatchesImg(line string) bool {
return imgRe.MatchString(line)
}
type imgState int
const (
inRoot imgState = iota
inName
inDimensionsW
inDimensionsH
inDescription
)
type Img struct {
entries []imgEntry
currEntry imgEntry
hyphaName string
state imgState
}
func (img *Img) pushEntry() {
if strings.TrimSpace(img.currEntry.path.String()) != "" {
img.currEntry.srclink = link.From(img.currEntry.path.String(), "", img.hyphaName)
img.currEntry.srclink.DoubtExistence()
img.entries = append(img.entries, img.currEntry)
img.currEntry = imgEntry{}
img.currEntry.path.Reset()
}
}
func (img *Img) Process(line string) (shouldGoBackToNormal bool) {
stateToProcessor := map[imgState]func(rune) bool{
inRoot: img.processInRoot,
inName: img.processInName,
inDimensionsW: img.processInDimensionsW,
inDimensionsH: img.processInDimensionsH,
inDescription: img.processInDescription,
}
for _, r := range line {
if shouldReturnTrue := stateToProcessor[img.state](r); shouldReturnTrue {
return true
}
}
return false
}
func (img *Img) processInDescription(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.state = inName
default:
img.currEntry.desc.WriteRune(r)
}
return false
}
func (img *Img) processInRoot(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '\n', '\r':
img.pushEntry()
case ' ', '\t':
default:
img.state = inName
img.currEntry = imgEntry{}
img.currEntry.path.Reset()
img.currEntry.path.WriteRune(r)
}
return false
}
func (img *Img) processInName(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '|':
img.state = inDimensionsW
case '{':
img.state = inDescription
case '\n', '\r':
img.pushEntry()
img.state = inRoot
default:
img.currEntry.path.WriteRune(r)
}
return false
}
func (img *Img) processInDimensionsW(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '*':
img.state = inDimensionsH
case ' ', '\t', '\n':
case '{':
img.state = inDescription
default:
img.currEntry.sizeW.WriteRune(r)
}
return false
}
func (img *Img) processInDimensionsH(r rune) (shouldGoBackToNormal bool) {
switch r {
case '}':
img.pushEntry()
return true
case ' ', '\t', '\n':
case '{':
img.state = inDescription
default:
img.currEntry.sizeH.WriteRune(r)
}
return false
}
func ImgFromFirstLine(line, hyphaName string) (img *Img, shouldGoBackToNormal bool) {
img = &Img{
hyphaName: hyphaName,
entries: make([]imgEntry, 0),
}
line = line[strings.IndexRune(line, '{')+1:]
return img, img.Process(line)
}
func (img *Img) pagePathFor(path string) string {
path = strings.TrimSpace(path)
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
return path
} else {
return "/page/" + xclCanonicalName(img.hyphaName, path)
}
}
func parseDimensions(dimensions string) (sizeW, sizeH string) {
xIndex := strings.IndexRune(dimensions, '*')
if xIndex == -1 { // If no x in dimensions
sizeW = strings.TrimSpace(dimensions)
} else {
sizeW = strings.TrimSpace(dimensions[:xIndex])
sizeH = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1]))
}
return
}
func (img *Img) markExistenceOfSrcLinks() {
HyphaIterate(func(hn string) {
for _, entry := range img.entries {
if hn == entry.srclink.Address {
entry.srclink.DestinationUnknown = false
}
}
})
}
func (img *Img) ToHtml() (html string) {
img.markExistenceOfSrcLinks()
isOneImageOnly := len(img.entries) == 1 && img.entries[0].desc.Len() == 0
if isOneImageOnly {
html += `<section class="img-gallery img-gallery_one-image">`
} else {
html += `<section class="img-gallery img-gallery_many-images">`
}
for _, entry := range img.entries {
html += `<figure>`
if entry.srclink.DestinationUnknown {
html += fmt.Sprintf(
`<a class="%s" href="%s">Hypha <i>%s</i> does not exist</a>`,
entry.srclink.Classes(),
entry.srclink.Href(),
entry.srclink.Address)
} else {
html += fmt.Sprintf(
`<a href="%s"><img src="%s" %s %s></a>`,
entry.srclink.Href(),
entry.srclink.ImgSrc(),
entry.sizeWAsAttr(),
entry.sizeHAsAttr())
}
html += entry.descriptionAsHtml(img.hyphaName)
html += `</figure>`
}
return html + `</section>`
}

View File

@ -1,45 +0,0 @@
package markup
import (
"strings"
"github.com/bouncepaw/mycorrhiza/link"
)
type imgEntry struct {
srclink *link.Link
path strings.Builder
sizeW strings.Builder
sizeH strings.Builder
desc strings.Builder
}
func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) {
if entry.desc.Len() == 0 {
return ""
}
lines := strings.Split(entry.desc.String(), "\n")
for _, line := range lines {
if line = strings.TrimSpace(line); line != "" {
if html != "" {
html += `<br>`
}
html += ParagraphToHtml(hyphaName, line)
}
}
return `<figcaption>` + html + `</figcaption>`
}
func (entry *imgEntry) sizeWAsAttr() string {
if entry.sizeW.Len() == 0 {
return ""
}
return ` width="` + entry.sizeW.String() + `"`
}
func (entry *imgEntry) sizeHAsAttr() string {
if entry.sizeH.Len() == 0 {
return ""
}
return ` height="` + entry.sizeH.String() + `"`
}

View File

@ -1,229 +0,0 @@
package markup
import (
"fmt"
"html"
"strings"
"github.com/bouncepaw/mycorrhiza/util"
)
// HyphaExists holds function that checks that a hypha is present.
var HyphaExists func(string) bool
//
var HyphaImageForOG func(string) string
// HyphaAccess holds function that accesses a hypha by its name.
var HyphaAccess func(string) (rawText, binaryHtml string, err error)
// HyphaIterate is a function that iterates all hypha names existing.
var HyphaIterate func(func(string))
// GemLexerState is used by markup parser to remember what is going on.
type GemLexerState struct {
// Name of hypha being parsed
name string
where string // "", "list", "pre"
// Line id
id int
buf string
// Temporaries
img *Img
table *Table
list *List
}
type Line struct {
id int
// interface{} may be bad. TODO: a proper type
contents interface{}
}
func (md *MycoDoc) lex() (ast []Line) {
var state = GemLexerState{name: md.hyphaName}
for _, line := range append(strings.Split(md.contents, "\n"), "") {
lineToAST(line, &state, &ast)
}
return ast
}
// Lex `line` in markup and save it to `ast` using `state`.
func lineToAST(line string, state *GemLexerState, ast *[]Line) {
addLine := func(text interface{}) {
*ast = append(*ast, Line{id: state.id, contents: text})
}
addParagraphIfNeeded := func() {
if state.where == "p" {
state.where = ""
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, strings.ReplaceAll(ParagraphToHtml(state.name, state.buf), "\n", "<br>")))
state.buf = ""
}
}
// Process empty lines depending on the current state
if "" == strings.TrimSpace(line) {
switch state.where {
case "pre":
state.buf += "\n"
case "launchpad":
state.where = ""
addLine(state.buf + "</ul>")
case "p":
addParagraphIfNeeded()
}
return
}
startsWith := func(token string) bool {
return strings.HasPrefix(line, token)
}
addHeading := func(i int) {
id := util.LettersNumbersOnly(line[i+1:])
addLine(fmt.Sprintf(`<h%d id='%d'>%s<a href="#%s" id="%s" class="heading__link"></a></h%d>`, i, state.id, ParagraphToHtml(state.name, line[i+1:]), id, id, i))
}
// Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code.
switch state.where {
case "img":
goto imgState
case "table":
goto tableState
case "list":
goto listState
case "pre":
goto preformattedState
case "launchpad":
goto launchpadState
default: // "p" or ""
goto normalState
}
imgState:
if shouldGoBackToNormal := state.img.Process(line); shouldGoBackToNormal {
state.where = ""
addLine(*state.img)
}
return
tableState:
if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal {
state.where = ""
addLine(*state.table)
}
return
listState:
if done := state.list.Parse(line); done {
state.list.Finalize()
state.where = ""
goto normalState
}
return
preformattedState:
switch {
case startsWith("```"):
state.where = ""
state.buf = strings.TrimSuffix(state.buf, "\n")
addLine(state.buf + "</code></pre>")
state.buf = ""
default:
state.buf += html.EscapeString(line) + "\n"
}
return
launchpadState:
switch {
case startsWith("=>"):
href, text, class := Rocketlink(line, state.name)
state.buf += fmt.Sprintf(` <li class="launchpad__entry"><a href="%s" class="rocketlink %s">%s</a></li>`, href, class, text)
case startsWith("```"):
state.where = "pre"
addLine(state.buf + "</ul>")
state.id++
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
default:
state.where = ""
addLine(state.buf + "</ul>")
goto normalState
}
return
normalState:
state.id++
switch {
case startsWith("```"):
addParagraphIfNeeded()
state.where = "pre"
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
case startsWith("###### "):
addParagraphIfNeeded()
addHeading(6)
case startsWith("##### "):
addParagraphIfNeeded()
addHeading(5)
case startsWith("#### "):
addParagraphIfNeeded()
addHeading(4)
case startsWith("### "):
addParagraphIfNeeded()
addHeading(3)
case startsWith("## "):
addParagraphIfNeeded()
addHeading(2)
case startsWith("# "):
addParagraphIfNeeded()
addHeading(1)
case startsWith(">"):
addParagraphIfNeeded()
addLine(
fmt.Sprintf(
"<blockquote id='%d'>%s</blockquote>",
state.id,
ParagraphToHtml(state.name, remover(">")(line)),
),
)
case startsWith("=>"):
addParagraphIfNeeded()
state.where = "launchpad"
state.buf = fmt.Sprintf("<ul class='launchpad' id='%d'>\n", state.id)
goto launchpadState
case startsWith("<="):
addParagraphIfNeeded()
addLine(parseTransclusion(line, state.name))
case MatchesHorizontalLine(line):
addParagraphIfNeeded()
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
case MatchesList(line):
addParagraphIfNeeded()
list, _ := NewList(line, state.name)
state.where = "list"
state.list = list
addLine(state.list)
case MatchesImg(line):
addParagraphIfNeeded()
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
if shouldGoBackToNormal {
addLine(*img)
} else {
state.where = "img"
state.img = img
}
case MatchesTable(line):
addParagraphIfNeeded()
state.where = "table"
state.table = TableFromFirstLine(line, state.name)
case state.where == "p":
state.buf += "\n" + line
default:
state.where = "p"
state.buf = line
}
}

View File

@ -1,33 +0,0 @@
package markup
import (
"strings"
"github.com/bouncepaw/mycorrhiza/link"
)
// LinkParts determines what href, text and class should resulting <a> have based on mycomarkup's addr, display and hypha name.
//
// => addr display
// [[addr|display]]
// TODO: deprecate
func LinkParts(addr, display, hyphaName string) (href, text, class string) {
l := link.From(addr, display, hyphaName)
if l.Kind == link.LinkLocalHypha && !HyphaExists(l.Address) {
l.DestinationUnknown = true
}
return l.Href(), l.Display, l.Classes()
}
// Parse markup line starting with "=>" according to wikilink rules.
// See http://localhost:1737/page/wikilink
func Rocketlink(src, hyphaName string) (href, text, class string) {
src = strings.TrimSpace(src[2:]) // Drop =>
if src == "" {
return
}
// Href is text after => till first whitespace
addr := strings.Fields(src)[0]
display := strings.TrimPrefix(src, addr)
return LinkParts(addr, display, hyphaName)
}

View File

@ -1,171 +0,0 @@
package markup
import (
"errors"
"strings"
)
func parseListItem(line string) (level int, offset int, ordered bool, err error) {
for line[level] == '*' {
level++
}
if line[level] == '.' {
ordered = true
offset = level + 2
} else {
ordered = false
offset = level + 1
}
if line[offset-1] != ' ' || len(line) < offset+2 || level < 1 || level > 6 {
err = errors.New("ill-formatted list item")
}
return
}
func MatchesList(line string) bool {
level, _, _, err := parseListItem(line)
return err == nil && level == 1
}
type listItem struct {
content string
parent *listItem
children []*listItem
depth int
}
func newListItem(parent *listItem) *listItem {
depth := 0
if parent != nil {
depth = parent.depth + 1
}
return &listItem{
parent: parent,
children: make([]*listItem, 0),
depth: depth,
}
}
func (item *listItem) renderAsHtmlTo(b *strings.Builder, hyphaName string, ordered bool) {
if len(item.content) > 0 {
b.WriteString("<li>")
b.WriteString(ParagraphToHtml(hyphaName, item.content))
}
if len(item.children) > 0 {
if ordered {
b.WriteString("<ol>")
} else {
b.WriteString("<ul>")
}
for _, child := range item.children {
child.renderAsHtmlTo(b, hyphaName, ordered)
}
if ordered {
b.WriteString("</ol>")
} else {
b.WriteString("</ul>")
}
}
if len(item.content) > 0 {
b.WriteString("</li>")
}
}
// A structure representing ordered and unordered lists in the AST.
type List struct {
curr *listItem
hyphaName string
ordered bool
finalized bool
}
func NewList(line, hyphaName string) (*List, bool) {
list := &List{
hyphaName: hyphaName,
curr: newListItem(nil),
}
return list, list.Parse(line)
}
func (list *List) pushItem() {
item := newListItem(list.curr)
list.curr.children = append(list.curr.children, item)
list.curr = item
}
func (list *List) popItem() {
if list.curr == nil {
return
}
list.curr = list.curr.parent
}
func (list *List) balance(level int) {
for level > list.curr.depth {
list.pushItem()
}
for level < list.curr.depth {
list.popItem()
}
}
func (list *List) Parse(line string) (done bool) {
level, offset, ordered, err := parseListItem(line)
if err != nil {
list.Finalize()
return true
}
// update ordered flag if the current node is the root one
// (i.e. no parsing has been done yet)
if list.curr.parent == nil {
list.ordered = ordered
}
// if list type has suddenly changed (ill-formatted list), quit
if ordered != list.ordered {
list.Finalize()
return true
}
list.balance(level)
// if the current node already has content, create a new one
// to prevent overwriting existing content (effectively creating
// a new sibling node)
if len(list.curr.content) > 0 {
list.popItem()
list.pushItem()
}
list.curr.content = line[offset:]
return false
}
func (list *List) Finalize() {
if !list.finalized {
// close all opened nodes, effectively going up to the root node
list.balance(0)
list.finalized = true
}
}
func (list *List) RenderAsHtml() (html string) {
// for a good measure
list.Finalize()
b := &strings.Builder{}
// fire up recursive render process
list.curr.renderAsHtmlTo(b, list.hyphaName, list.ordered)
return b.String()
}

View File

@ -1,171 +0,0 @@
// This is not done yet
package markup
import (
"fmt"
"html"
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/link"
"github.com/bouncepaw/mycorrhiza/util"
)
// A Mycomarkup-formatted document
type MycoDoc struct {
// data
hyphaName string
contents string
// indicators
parsedAlready bool
// results
ast []Line
html string
firstImageURL string
description string
}
// Constructor
func Doc(hyphaName, contents string) *MycoDoc {
md := &MycoDoc{
hyphaName: hyphaName,
contents: contents,
}
return md
}
func (md *MycoDoc) Lex(recursionLevel int) *MycoDoc {
if !md.parsedAlready {
md.ast = md.lex()
}
md.parsedAlready = true
return md
}
// AsHtml returns an html representation of the document
func (md *MycoDoc) AsHTML() string {
md.html = Parse(md.Lex(0).ast, 0, 0, 0)
return md.html
}
// AsGemtext returns a gemtext representation of the document. Currently really limited, just returns source text
func (md *MycoDoc) AsGemtext() string {
return md.contents
}
// Used to clear opengraph description from html tags. This method is usually bad because of dangers of malformed HTML, but I'm going to use it only for Mycorrhiza-generated HTML, so it's okay. The question mark is required; without it the whole string is eaten away.
var htmlTagRe = regexp.MustCompile(`<.*?>`)
// OpenGraphHTML returns an html representation of og: meta tags.
func (md *MycoDoc) OpenGraphHTML() string {
md.ogFillVars()
return strings.Join([]string{
ogTag("title", md.hyphaName),
ogTag("type", "article"),
ogTag("image", md.firstImageURL),
ogTag("url", util.URL+"/hypha/"+md.hyphaName),
ogTag("determiner", ""),
ogTag("description", htmlTagRe.ReplaceAllString(md.description, "")),
}, "\n")
}
func (md *MycoDoc) ogFillVars() *MycoDoc {
md.firstImageURL = util.URL + "/favicon.ico"
foundDesc := false
foundImg := false
for _, line := range md.ast {
switch v := line.contents.(type) {
case string:
if !foundDesc {
md.description = v
foundDesc = true
}
case Img:
if !foundImg && len(v.entries) > 0 {
md.firstImageURL = v.entries[0].srclink.ImgSrc()
if v.entries[0].srclink.Kind != link.LinkExternal {
md.firstImageURL = util.URL + md.firstImageURL
}
foundImg = true
}
}
}
return md
}
func ogTag(property, content string) string {
return fmt.Sprintf(`<meta property="og:%s" content="%s"/>`, property, content)
}
/* The rest of this file is currently unused. TODO: use it I guess */
type BlockType int
const (
BlockH1 = iota
BlockH2
BlockH3
BlockH4
BlockH5
BlockH6
BlockRocket
BlockPre
BlockQuote
BlockPara
)
type CrawlWhere int
const (
inSomewhere = iota
inPre
inEnd
)
func crawl(name, content string) []string {
stateStack := []CrawlWhere{inSomewhere}
startsWith := func(token string) bool {
return strings.HasPrefix(content, token)
}
pop := func() {
stateStack = stateStack[:len(stateStack)-1]
}
push := func(s CrawlWhere) {
stateStack = append(stateStack, s)
}
readln := func(c string) (string, string) {
parts := strings.SplitN(c, "\n", 1)
return parts[0], parts[1]
}
preAcc := ""
line := ""
for {
switch stateStack[0] {
case inSomewhere:
switch {
case startsWith("```"):
push(inPre)
_, content = readln(content)
default:
}
case inPre:
switch {
case startsWith("```"):
pop()
_, content = readln(content)
default:
line, content = readln(content)
preAcc += html.EscapeString(line)
}
}
break
}
return []string{}
}

View File

@ -1,57 +0,0 @@
package markup
import (
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/link"
)
// OutLinks returns a channel of names of hyphae this mycodocument links.
// Links include:
// * Regular links
// * Rocketlinks
// * Transclusion
// * Image galleries
// Not needed anymore, I guess.
func (md *MycoDoc) OutLinks() chan string {
ch := make(chan string)
if !md.parsedAlready {
md.Lex(0)
}
go func() {
for _, line := range md.ast {
switch v := line.contents.(type) {
case string:
if strings.HasPrefix(v, "<p") || strings.HasPrefix(v, "<ul class='launchpad'") {
extractLinks(v, ch)
}
case Transclusion:
ch <- v.name
case Img:
extractImageLinks(v, ch)
}
}
close(ch)
}()
return ch
}
var reLinks = regexp.MustCompile(`<a href="/hypha/([^"]*)".*?</a>`)
func extractLinks(html string, ch chan string) {
if results := reLinks.FindAllStringSubmatch(html, -1); results != nil {
for _, result := range results {
// result[0] is always present at this point and is not needed, because it is the whole matched substring (which we don't need)
ch <- result[1]
}
}
}
func extractImageLinks(img Img, ch chan string) {
for _, entry := range img.entries {
if entry.srclink.Kind == link.LinkLocalHypha {
ch <- entry.srclink.Address
}
}
}

View File

@ -1,185 +0,0 @@
package markup
import (
"bytes"
"fmt"
"html"
"strings"
"unicode"
)
type spanTokenType int
const (
spanTextNode = iota
spanItalic
spanBold
spanMono
spanSuper
spanSub
spanMark
spanStrike
spanLink
)
func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, originalForm string) string {
if tagState[spanMono] && (stt != spanMono) {
return originalForm
}
if tagState[stt] {
tagState[stt] = false
return fmt.Sprintf("</%s>", tagName)
} else {
tagState[stt] = true
return fmt.Sprintf("<%s>", tagName)
}
}
func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) string {
if isBracketedLink {
input.Next(2) // drop those [[
}
var (
escaping = false
addrBuf = bytes.Buffer{}
displayBuf = bytes.Buffer{}
currBuf = &addrBuf
)
for input.Len() != 0 {
b, _ := input.ReadByte()
if escaping {
currBuf.WriteByte(b)
escaping = false
} else if isBracketedLink && b == '|' && currBuf == &addrBuf {
currBuf = &displayBuf
} else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
input.Next(1)
break
} else if !isBracketedLink && (unicode.IsSpace(rune(b)) || strings.ContainsRune("<>{}|\\^[]`,()", rune(b))) {
input.UnreadByte()
break
} else {
currBuf.WriteByte(b)
}
}
href, text, class := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName)
return fmt.Sprintf(`<a href="%s" class="%s">%s</a>`, href, class, html.EscapeString(text))
}
// getTextNode splits the `input` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = input`, `rest = ""`. It handles escaping with backslash.
func getTextNode(input *bytes.Buffer) string {
var (
textNodeBuffer = bytes.Buffer{}
escaping = false
startsWith = func(t string) bool {
return bytes.HasPrefix(input.Bytes(), []byte(t))
}
couldBeLinkStart = func() bool {
return startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")
}
)
// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
if input.Len() != 0 {
b, _ := input.ReadByte()
textNodeBuffer.WriteByte(b)
}
for input.Len() != 0 {
// Assume no error is possible because we check for length
b, _ := input.ReadByte()
if escaping {
textNodeBuffer.WriteByte(b)
escaping = false
} else if b == '\\' {
escaping = true
} else if strings.IndexByte("/*`^,![~", b) >= 0 {
input.UnreadByte()
break
} else if couldBeLinkStart() {
textNodeBuffer.WriteByte(b)
break
} else {
textNodeBuffer.WriteByte(b)
}
}
return textNodeBuffer.String()
}
func ParagraphToHtml(hyphaName, input string) string {
var (
p = bytes.NewBufferString(input)
ret strings.Builder
// true = tag is opened, false = tag is not opened
tagState = map[spanTokenType]bool{
spanItalic: false,
spanBold: false,
spanMono: false,
spanSuper: false,
spanSub: false,
spanMark: false,
spanLink: false,
}
startsWith = func(t string) bool {
return bytes.HasPrefix(p.Bytes(), []byte(t))
}
noTagsActive = func() bool {
return !(tagState[spanItalic] || tagState[spanBold] || tagState[spanMono] || tagState[spanSuper] || tagState[spanSub] || tagState[spanMark] || tagState[spanLink])
}
)
for p.Len() != 0 {
switch {
case startsWith("//"):
ret.WriteString(tagFromState(spanItalic, tagState, "em", "//"))
p.Next(2)
case startsWith("**"):
ret.WriteString(tagFromState(spanBold, tagState, "strong", "**"))
p.Next(2)
case startsWith("`"):
ret.WriteString(tagFromState(spanMono, tagState, "code", "`"))
p.Next(1)
case startsWith("^"):
ret.WriteString(tagFromState(spanSuper, tagState, "sup", "^"))
p.Next(1)
case startsWith(",,"):
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
p.Next(2)
case startsWith("!!"):
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
p.Next(2)
case startsWith("~~"):
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
p.Next(2)
case startsWith("[["):
ret.WriteString(getLinkNode(p, hyphaName, true))
case (startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")) && noTagsActive():
ret.WriteString(getLinkNode(p, hyphaName, false))
default:
ret.WriteString(html.EscapeString(getTextNode(p)))
}
}
for stt, open := range tagState {
if open {
switch stt {
case spanItalic:
ret.WriteString(tagFromState(spanItalic, tagState, "em", "//"))
case spanBold:
ret.WriteString(tagFromState(spanBold, tagState, "strong", "**"))
case spanMono:
ret.WriteString(tagFromState(spanMono, tagState, "code", "`"))
case spanSuper:
ret.WriteString(tagFromState(spanSuper, tagState, "sup", "^"))
case spanSub:
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
case spanMark:
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
case spanStrike:
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
case spanLink:
ret.WriteString(tagFromState(spanLink, tagState, "a", "[["))
}
}
}
return ret.String()
}

View File

@ -1,28 +0,0 @@
package markup
const maxRecursionLevel = 3
func Parse(ast []Line, from, to int, recursionLevel int) (html string) {
if recursionLevel > maxRecursionLevel {
return "Transclusion depth limit"
}
for _, line := range ast {
if line.id >= from && (line.id <= to || to == 0) || line.id == -1 {
switch v := line.contents.(type) {
case Transclusion:
html += Transclude(v, recursionLevel)
case Img:
html += v.ToHtml()
case Table:
html += v.asHtml()
case *List:
html += v.RenderAsHtml()
case string:
html += v
default:
html += "<b class='error'>Unknown element.</b>"
}
}
}
return html
}

View File

@ -1,231 +0,0 @@
package markup
import (
"fmt"
"regexp"
"strings"
"unicode"
// "github.com/bouncepaw/mycorrhiza/util"
)
var tableRe = regexp.MustCompile(`^table\s+{`)
func MatchesTable(line string) bool {
return tableRe.MatchString(line)
}
func TableFromFirstLine(line, hyphaName string) *Table {
return &Table{
hyphaName: hyphaName,
caption: line[strings.IndexRune(line, '{')+1:],
rows: make([]*tableRow, 0),
}
}
func (t *Table) Process(line string) (shouldGoBackToNormal bool) {
if strings.TrimSpace(line) == "}" && !t.inMultiline {
return true
}
if !t.inMultiline {
t.pushRow()
}
var (
inLink bool
skipNext bool
escaping bool
lookingForNonSpace = !t.inMultiline
countingColspan bool
)
for i, r := range line {
switch {
case skipNext:
skipNext = false
continue
case lookingForNonSpace && unicode.IsSpace(r):
case lookingForNonSpace && (r == '!' || r == '|'):
t.currCellMarker = r
t.currColspan = 1
lookingForNonSpace = false
countingColspan = true
case lookingForNonSpace:
t.currCellMarker = '^' // ^ represents implicit |, not part of syntax
t.currColspan = 1
lookingForNonSpace = false
t.currCellBuilder.WriteRune(r)
case escaping:
t.currCellBuilder.WriteRune(r)
case inLink && r == ']' && len(line)-1 > i && line[i+1] == ']':
t.currCellBuilder.WriteString("]]")
inLink = false
skipNext = true
case inLink:
t.currCellBuilder.WriteRune(r)
case t.inMultiline && r == '}':
t.inMultiline = false
case t.inMultiline && i == len(line)-1:
t.currCellBuilder.WriteRune('\n')
case t.inMultiline:
t.currCellBuilder.WriteRune(r)
// Not in multiline:
case (r == '|' || r == '!') && !countingColspan:
t.pushCell()
t.currCellMarker = r
t.currColspan = 1
countingColspan = true
case r == t.currCellMarker && (r == '|' || r == '!') && countingColspan:
t.currColspan++
case r == '{':
t.inMultiline = true
countingColspan = false
case r == '[' && len(line)-1 > i && line[i+1] == '[':
t.currCellBuilder.WriteString("[[")
inLink = true
skipNext = true
case i == len(line)-1:
t.pushCell()
default:
t.currCellBuilder.WriteRune(r)
countingColspan = false
}
}
return false
}
type Table struct {
// data
hyphaName string
caption string
rows []*tableRow
// state
inMultiline bool
// tmp
currCellMarker rune
currColspan uint
currCellBuilder strings.Builder
}
func (t *Table) pushRow() {
t.rows = append(t.rows, &tableRow{
cells: make([]*tableCell, 0),
})
}
func (t *Table) pushCell() {
tc := &tableCell{
content: t.currCellBuilder.String(),
colspan: t.currColspan,
}
switch t.currCellMarker {
case '|', '^':
tc.kind = tableCellDatum
case '!':
tc.kind = tableCellHeader
}
// We expect the table to have at least one row ready, so no nil-checking
tr := t.rows[len(t.rows)-1]
tr.cells = append(tr.cells, tc)
t.currCellBuilder = strings.Builder{}
}
func (t *Table) asHtml() (html string) {
if t.caption != "" {
html += fmt.Sprintf("<caption>%s</caption>", t.caption)
}
if len(t.rows) > 0 && t.rows[0].looksLikeThead() {
html += fmt.Sprintf("<thead>%s</thead>", t.rows[0].asHtml(t.hyphaName))
t.rows = t.rows[1:]
}
html += "\n<tbody>\n"
for _, tr := range t.rows {
html += tr.asHtml(t.hyphaName)
}
return fmt.Sprintf(`<table>%s</tbody></table>`, html)
}
type tableRow struct {
cells []*tableCell
}
func (tr *tableRow) asHtml(hyphaName string) (html string) {
for _, tc := range tr.cells {
html += tc.asHtml(hyphaName)
}
return fmt.Sprintf("<tr>%s</tr>\n", html)
}
// Most likely, rows with more than two header cells are theads. I allow one extra datum cell for tables like this:
// | ! a ! b
// ! c | d | e
// ! f | g | h
func (tr *tableRow) looksLikeThead() bool {
var (
headerAmount = 0
datumAmount = 0
)
for _, tc := range tr.cells {
switch tc.kind {
case tableCellHeader:
headerAmount++
case tableCellDatum:
datumAmount++
}
}
return headerAmount >= 2 && datumAmount <= 1
}
type tableCell struct {
kind tableCellKind
colspan uint
content string
}
func (tc *tableCell) asHtml(hyphaName string) string {
return fmt.Sprintf(
"<%[1]s %[2]s>%[3]s</%[1]s>\n",
tc.kind.tagName(),
tc.colspanAttribute(),
tc.contentAsHtml(hyphaName),
)
}
func (tc *tableCell) colspanAttribute() string {
if tc.colspan <= 1 {
return ""
}
return fmt.Sprintf(`colspan="%d"`, tc.colspan)
}
func (tc *tableCell) contentAsHtml(hyphaName string) (html string) {
for _, line := range strings.Split(tc.content, "\n") {
if line = strings.TrimSpace(line); line != "" {
if html != "" {
html += `<br>`
}
html += ParagraphToHtml(hyphaName, line)
}
}
return html
}
type tableCellKind int
const (
tableCellUnknown tableCellKind = iota
tableCellHeader
tableCellDatum
)
func (tck tableCellKind) tagName() string {
switch tck {
case tableCellHeader:
return "th"
case tableCellDatum:
return "td"
default:
return "p"
}
}

View File

@ -1,38 +0,0 @@
# 1
## 2
### 3
> quote
* li 1
* li 2
text
more text
=> Pear some link
* li\n"+
```alt text goes here
=> preformatted text
where markup is not lexed
```it ends here"
=>linking
text
```
()
/\
```
<= Apple : 1..3
img {
hypha1
hypha2|
hypha3| 60
hypha4| { line1
line2
} this is ignored
hypha5| {
state of minnesota
}
}

View File

@ -1,23 +0,0 @@
package markup
import (
"strings"
)
// Function that returns a function that can strip `prefix` and trim whitespace when called.
func remover(prefix string) func(string) string {
return func(l string) string {
return strings.TrimSpace(strings.TrimPrefix(l, prefix))
}
}
// Remove #, ## or ### from beginning of `line`.
func removeHeadingOctothorps(line string) string {
f := remover("#")
return f(f(f(line)))
}
// Return a canonical representation of a hypha `name`.
func canonicalName(name string) string {
return strings.ToLower(strings.ReplaceAll(strings.TrimSpace(name), " ", "_"))
}

View File

@ -1,107 +0,0 @@
package markup
import (
"fmt"
"path"
"strconv"
"strings"
)
const xclError = -9
// Transclusion is used by markup parser to remember what hyphae shall be transcluded.
type Transclusion struct {
name string
from int // inclusive
to int // inclusive
}
// Transclude transcludes `xcl` and returns html representation.
func Transclude(xcl Transclusion, recursionLevel int) (html string) {
recursionLevel++
tmptOk := `<section class="transclusion transclusion_ok">
<a class="transclusion__link" href="/page/%s">%s</a>
<div class="transclusion__content">%s</div>
</section>`
tmptFailed := `<section class="transclusion transclusion_failed">
<p class="error">Hypha <a class="wikilink_new" href="/page/%s">%s</a> does not exist</p>
</section>`
if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to {
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
}
rawText, binaryHtml, err := HyphaAccess(xcl.name)
if err != nil {
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
}
md := Doc(xcl.name, rawText)
xclText := Parse(md.lex(), xcl.from, xcl.to, recursionLevel)
return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText)
}
/* Grammar from hypha transclusion:
transclusion_line ::= transclusion_token hypha_name LWS* [":" LWS* range LWS*]
transclusion_token ::= "<=" LWS+
hypha_name ::= canonical_name | noncanonical_name
range ::= id | (from_id two_dots to_id) | (from_id two_dots) | (two_dots to_id)
two_dots ::= ".."
*/
func parseTransclusion(line, hyphaName string) (xclusion Transclusion) {
line = strings.TrimSpace(remover("<=")(line))
if line == "" {
return Transclusion{"", xclError, xclError}
}
if strings.ContainsRune(line, ':') {
parts := strings.SplitN(line, ":", 2)
xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(parts[0]))
selector := strings.TrimSpace(parts[1])
xclusion.from, xclusion.to = parseSelector(selector)
} else {
xclusion.name = xclCanonicalName(hyphaName, strings.TrimSpace(line))
}
return xclusion
}
func xclCanonicalName(hyphaName, xclName string) string {
switch {
case strings.HasPrefix(xclName, "./"):
return canonicalName(path.Join(hyphaName, strings.TrimPrefix(xclName, "./")))
case strings.HasPrefix(xclName, "../"):
return canonicalName(path.Join(path.Dir(hyphaName), strings.TrimPrefix(xclName, "../")))
default:
return canonicalName(xclName)
}
}
// At this point:
// selector ::= id
// | from ".."
// | from ".." to
// | ".." to
// If it is not, return (xclError, xclError).
func parseSelector(selector string) (from, to int) {
if selector == "" {
return 0, 0
}
if strings.Contains(selector, "..") {
parts := strings.Split(selector, "..")
var (
fromStr = strings.TrimSpace(parts[0])
from, fromErr = strconv.Atoi(fromStr)
toStr = strings.TrimSpace(parts[1])
to, toErr = strconv.Atoi(toStr)
)
if fromStr == "" && toStr == "" {
return 0, 0
}
if fromErr == nil || toErr == nil {
return from, to
}
} else if id, err := strconv.Atoi(selector); err == nil {
return id, id
}
return xclError, xclError
}

@ -1 +0,0 @@
Subproject commit 515634c88a51a616f486dfefc2a7b9e6ca692689

34
name.go
View File

@ -1,34 +0,0 @@
package main
import (
"log"
"net/http"
"strings"
"git.sr.ht/~adnano/go-gemini"
"github.com/bouncepaw/mycorrhiza/util"
)
// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
func HyphaNameFromRq(rq *http.Request, actions ...string) string {
p := rq.URL.Path
for _, action := range actions {
if strings.HasPrefix(p, "/"+action+"/") {
return util.CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
}
}
panic("HyphaNameFromRq: no matching action passed")
}
// geminiHyphaNameFromRq extracts hypha name from gemini request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
func geminiHyphaNameFromRq(rq *gemini.Request, actions ...string) string {
p := rq.URL.Path
for _, action := range actions {
if strings.HasPrefix(p, "/"+action+"/") {
return util.CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
}
}
log.Fatal("HyphaNameFromRq: no matching action passed")
return ""
}

View File

@ -4,16 +4,16 @@ import (
"errors" "errors"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views" "github.com/bouncepaw/mycorrhiza/views"
"github.com/bouncepaw/mycomarkup/globals"
) )
func init() { func init() {
markup.HyphaExists = func(hyphaName string) bool { globals.HyphaExists = func(hyphaName string) bool {
return hyphae.ByName(hyphaName).Exists return hyphae.ByName(hyphaName).Exists
} }
markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { globals.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
if h := hyphae.ByName(hyphaName); h.Exists { if h := hyphae.ByName(hyphaName); h.Exists {
rawText, err = FetchTextPart(h) rawText, err = FetchTextPart(h)
if h.BinaryPath != "" { if h.BinaryPath != "" {
@ -24,15 +24,9 @@ func init() {
} }
return return
} }
markup.HyphaIterate = func(λ func(string)) { globals.HyphaIterate = func(λ func(string)) {
for h := range hyphae.YieldExistingHyphae() { for h := range hyphae.YieldExistingHyphae() {
λ(h.Name) λ(h.Name)
} }
} }
markup.HyphaImageForOG = func(hyphaName string) string {
if h := hyphae.ByName(hyphaName); h.Exists && h.BinaryPath != "" {
return util.URL + "/binary/" + hyphaName
}
return util.URL + "/favicon.ico"
}
} }

View File

@ -3,6 +3,7 @@ package shroom
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"io/ioutil" "io/ioutil"
"log" "log"
"mime/multipart" "mime/multipart"
@ -13,7 +14,6 @@ import (
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
) )
func UploadText(h *hyphae.Hypha, data []byte, u *user.User) (hop *history.HistoryOp, errtitle string) { func UploadText(h *hyphae.Hypha, data []byte, u *user.User) (hop *history.HistoryOp, errtitle string) {
@ -56,7 +56,7 @@ func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.Use
// uploadHelp is a helper function for UploadText and UploadBinary // uploadHelp is a helper function for UploadText and UploadBinary
func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) { func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) {
var ( var (
fullPath = filepath.Join(util.WikiDir, h.Name+ext) fullPath = filepath.Join(cfg.WikiDir, h.Name+ext)
originalFullPath = &h.TextPath originalFullPath = &h.TextPath
) )
if hop.Type == history.TypeEditBinary { if hop.Type == history.TypeEditBinary {

View File

@ -4,9 +4,8 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
) )
// FetchTextPart tries to read text file of the given hypha. If there is no file, empty string is returned. // FetchTextPart tries to read text file of the given hypha. If there is no file, empty string is returned.
@ -24,15 +23,15 @@ func FetchTextPart(h *hyphae.Hypha) (string, error) {
} }
func SetHeaderLinks() { func SetHeaderLinks() {
if userLinksHypha := hyphae.ByName(util.HeaderLinksHypha); !userLinksHypha.Exists { if userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha); !userLinksHypha.Exists {
util.SetDefaultHeaderLinks() cfg.SetDefaultHeaderLinks()
} else { } else {
contents, err := ioutil.ReadFile(userLinksHypha.TextPath) contents, err := ioutil.ReadFile(userLinksHypha.TextPath)
if err != nil || len(contents) == 0 { if err != nil || len(contents) == 0 {
util.SetDefaultHeaderLinks() cfg.SetDefaultHeaderLinks()
} else { } else {
text := string(contents) text := string(contents)
util.ParseHeaderLinks(text, markup.Rocketlink) cfg.ParseHeaderLinks(text)
} }
} }
} }

View File

@ -2,30 +2,31 @@ package user
import ( import (
"encoding/json" "encoding/json"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/util"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/util"
) )
// InitUserDatabase checks the configuration for auth methods and loads users // InitUserDatabase checks the configuration for auth methods and loads users
// if necessary. Call it during initialization. // if necessary. Call it during initialization.
func InitUserDatabase() { func InitUserDatabase() {
AuthUsed = util.UseFixedAuth || util.UseRegistration AuthUsed = cfg.UseFixedAuth || cfg.UseRegistration
if AuthUsed && (util.FixedCredentialsPath != "" || util.RegistrationCredentialsPath != "") { if AuthUsed && (cfg.FixedAuthCredentialsPath != "" || cfg.RegistrationCredentialsPath != "") {
ReadUsersFromFilesystem() ReadUsersFromFilesystem()
} }
} }
// ReadUsersFromFilesystem reads all user information from filesystem and stores it internally. // ReadUsersFromFilesystem reads all user information from filesystem and stores it internally.
func ReadUsersFromFilesystem() { func ReadUsersFromFilesystem() {
if util.UseFixedAuth { if cfg.UseFixedAuth {
rememberUsers(usersFromFixedCredentials()) rememberUsers(usersFromFixedCredentials())
} }
if util.UseRegistration { if cfg.UseRegistration {
rememberUsers(usersFromRegistrationCredentials()) rememberUsers(usersFromRegistrationCredentials())
} }
readTokensToUsers() readTokensToUsers()
@ -44,6 +45,7 @@ func usersFromFile(path string, source UserSource) (users []*User) {
log.Fatal(err) log.Fatal(err)
} }
for _, u := range users { for _, u := range users {
u.Name = util.CanonicalName(u.Name)
u.Source = source u.Source = source
} }
return users return users

View File

@ -2,6 +2,7 @@ package user
import ( import (
"errors" "errors"
"github.com/bouncepaw/mycorrhiza/cfg"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
@ -39,8 +40,8 @@ func Register(username, password string) error {
username = util.CanonicalName(username) username = util.CanonicalName(username)
log.Println("Attempt to register user", username) log.Println("Attempt to register user", username)
switch { switch {
case CountRegistered() >= util.LimitRegistration && util.LimitRegistration > 0: case CountRegistered() >= cfg.LimitRegistration && cfg.LimitRegistration > 0:
i := strconv.Itoa(util.LimitRegistration) i := strconv.Itoa(cfg.LimitRegistration)
log.Println("Limit reached: " + i) log.Println("Limit reached: " + i)
return errors.New("Reached the limit of registered users: " + i) return errors.New("Reached the limit of registered users: " + i)
case HasUsername(username): case HasUsername(username):

View File

@ -1,92 +0,0 @@
package util
import (
"log"
"path/filepath"
"strconv"
"github.com/go-ini/ini"
)
// See https://mycorrhiza.lesarbr.es/hypha/configuration/fields
type Config struct {
WikiName string
NaviTitleIcon string
Hyphae
Network
Authorization
}
type Hyphae struct {
HomeHypha string
UserHypha string
HeaderLinksHypha string
}
type Network struct {
HTTPPort uint64
URL string
GeminiCertificatePath string
}
type Authorization struct {
UseFixedAuth bool
FixedAuthCredentialsPath string
UseRegistration bool
RegistrationCredentialsPath string
LimitRegistration uint64
}
func ReadConfigFile(path string) {
cfg := &Config{
WikiName: "MycorrhizaWiki",
NaviTitleIcon: "🍄",
Hyphae: Hyphae{
HomeHypha: "home",
UserHypha: "u",
HeaderLinksHypha: "",
},
Network: Network{
HTTPPort: 1737,
URL: "",
GeminiCertificatePath: "",
},
Authorization: Authorization{
UseFixedAuth: false,
FixedAuthCredentialsPath: "",
UseRegistration: false,
RegistrationCredentialsPath: "",
LimitRegistration: 0,
},
}
if path != "" {
path, err := filepath.Abs(path)
if err != nil {
log.Fatalf("cannot expand config file path: %s", err)
}
log.Println("Loading config at", path)
err = ini.MapTo(cfg, path)
if err != nil {
log.Fatal(err)
}
}
// Map the struct to the global variables
SiteName = cfg.WikiName
SiteNavIcon = cfg.NaviTitleIcon
HomePage = cfg.HomeHypha
UserHypha = cfg.UserHypha
HeaderLinksHypha = cfg.HeaderLinksHypha
ServerPort = strconv.FormatUint(cfg.HTTPPort, 10)
URL = cfg.URL
GeminiCertPath = cfg.GeminiCertificatePath
UseFixedAuth = cfg.UseFixedAuth
FixedCredentialsPath = cfg.FixedAuthCredentialsPath
UseRegistration = cfg.UseRegistration
RegistrationCredentialsPath = cfg.RegistrationCredentialsPath
LimitRegistration = int(cfg.LimitRegistration)
}

View File

@ -1,35 +0,0 @@
package util
import (
"strings"
)
func SetDefaultHeaderLinks() {
HeaderLinks = []HeaderLink{
{"/", SiteName},
{"/recent-changes", "Recent changes"},
{"/list", "All hyphae"},
{"/random", "Random"},
}
}
// rocketlinkλ is markup.Rocketlink. You have to pass it like that to avoid cyclical dependency.
func ParseHeaderLinks(text string, rocketlinkλ func(string, string) (string, string, string)) {
HeaderLinks = []HeaderLink{}
for _, line := range strings.Split(text, "\n") {
if strings.HasPrefix(line, "=>") {
href, text, _ := rocketlinkλ(line, HeaderLinksHypha)
HeaderLinks = append(HeaderLinks, HeaderLink{
Href: href,
Display: text,
})
}
}
}
type HeaderLink struct {
Href string
Display string
}
var HeaderLinks []HeaderLink

View File

@ -3,34 +3,19 @@ package util
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"log"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"unicode" "unicode"
"github.com/bouncepaw/mycorrhiza/cfg"
) )
// TODO: make names match to fields of config file func PrepareRq(rq *http.Request) {
var ( log.Println(rq.RequestURI)
SiteName string rq.URL.Path = strings.TrimSuffix(rq.URL.Path, "/")
SiteNavIcon string }
HomePage string
UserHypha string
HeaderLinksHypha string
ServerPort string
URL string
GeminiCertPath string
WikiDir string
ConfigFilePath string
UseFixedAuth bool
FixedCredentialsPath string
UseRegistration bool
RegistrationCredentialsPath string
LimitRegistration int
)
// LettersNumbersOnly keeps letters and numbers only in the given string. // LettersNumbersOnly keeps letters and numbers only in the given string.
func LettersNumbersOnly(s string) string { func LettersNumbersOnly(s string) string {
@ -52,8 +37,8 @@ func LettersNumbersOnly(s string) string {
// ShorterPath is used by handlerList to display shorter path to the files. It simply strips WikiDir. // ShorterPath is used by handlerList to display shorter path to the files. It simply strips WikiDir.
func ShorterPath(path string) string { func ShorterPath(path string) string {
if strings.HasPrefix(path, WikiDir) { if strings.HasPrefix(path, cfg.WikiDir) {
tmp := strings.TrimPrefix(path, WikiDir) tmp := strings.TrimPrefix(path, cfg.WikiDir)
if tmp == "" { if tmp == "" {
return "" return ""
} }
@ -103,9 +88,9 @@ func CanonicalName(name string) string {
} }
// HyphaPattern is a pattern which all hyphae must match. // HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`)
var UsernamePattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}/]+`) var UsernamePattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}/]+`)
// IsCanonicalName checks if the `name` is canonical. // IsCanonicalName checks if the `name` is canonical.
func IsCanonicalName(name string) bool { func IsCanonicalName(name string) bool {
@ -113,5 +98,17 @@ func IsCanonicalName(name string) bool {
} }
func IsPossibleUsername(username string) bool { func IsPossibleUsername(username string) bool {
return UsernamePattern.MatchString(strings.TrimSpace(username)) return username != "anon" && UsernamePattern.MatchString(strings.TrimSpace(username))
}
// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
func HyphaNameFromRq(rq *http.Request, actions ...string) string {
p := rq.URL.Path
for _, action := range actions {
if strings.HasPrefix(p, "/"+action+"/") {
return CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
}
}
log.Println("HyphaNameFromRq: this request is invalid, fallback to home hypha")
return cfg.HomeHypha
} }

View File

@ -1,15 +1,15 @@
{% import "net/http" %} {% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %} {% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %} {% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% func RegisterHTML(rq *http.Request) %} {% func RegisterHTML(rq *http.Request) %}
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<section> <section>
{% if util.UseRegistration %} {% if cfg.UseRegistration %}
<form class="modal" method="post" action="/register?{%s rq.URL.RawQuery %}" id="register-form" enctype="multipart/form-data" autocomplete="off"> <form class="modal" method="post" action="/register?{%s rq.URL.RawQuery %}" id="register-form" enctype="multipart/form-data" autocomplete="off">
<fieldset class="modal__fieldset"> <fieldset class="modal__fieldset">
<legend class="modal__title">Register to {%s util.SiteName %}</legend> <legend class="modal__title">Register to {%s cfg.WikiName %}</legend>
<label for="register-form__username">Username</label> <label for="register-form__username">Username</label>
<br> <br>
@ -20,16 +20,16 @@
<input type="password" required name="password"> <input type="password" required name="password">
<p>The server stores your password in an encrypted form; even administrators cannot read it.</p> <p>The server stores your password in an encrypted form; even administrators cannot read it.</p>
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> <p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p>
<input class="modal__action modal__submit" type="submit"> <input class="btn" type="submit" value="Register">
<a class="modal__action modal__cancel" href="/">Cancel</a> <a class="btn btn_weak" href="/{%s rq.URL.RawQuery %}">Cancel</a>
</fieldset> </fieldset>
</form> </form>
{% elseif util.UseFixedAuth %} {% elseif cfg.UseFixedAuth %}
<p>Administrators have forbidden registration for this wiki. Administrators can make an account for you by hand; contact them.</p> <p>Administrators have forbidden registration for this wiki. Administrators can make an account for you by hand; contact them.</p>
<p><a href="{%s rq.URL.RawQuery %}">← Go back</a></p> <p><a href="/{%s rq.URL.RawQuery %}">← Go back</a></p>
{% else %} {% else %}
<p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p> <p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p>
<p><a href="{%s rq.URL.RawQuery %}">← Go back</a></p> <p><a href="/{%s rq.URL.RawQuery %}">← Go back</a></p>
{% endif %} {% endif %}
</section> </section>
</main> </main>
@ -43,8 +43,7 @@
{% if user.AuthUsed %} {% if user.AuthUsed %}
<form class="modal" method="post" action="/login-data" id="login-form" enctype="multipart/form-data" autocomplete="on"> <form class="modal" method="post" action="/login-data" id="login-form" enctype="multipart/form-data" autocomplete="on">
<fieldset class="modal__fieldset"> <fieldset class="modal__fieldset">
<legend class="modal__title">Log in to {%s util.SiteName %}</legend> <legend class="modal__title">Log in to {%s cfg.WikiName %}</legend>
<p>Use the data you were given by an administrator.</p>
<label for="login-form__username">Username</label> <label for="login-form__username">Username</label>
<br> <br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username"> <input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
@ -53,8 +52,8 @@
<br> <br>
<input type="password" required name="password" autocomplete="current-password"> <input type="password" required name="password" autocomplete="current-password">
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> <p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p>
<input class="modal__action modal__submit" type="submit"> <input class="btn" type="submit" value="Log in">
<a class="modal__action modal__cancel" href="/">Cancel</a> <a class="btn btn_weak" href="/">Cancel</a>
</fieldset> </fieldset>
</form> </form>
{% else %} {% else %}

View File

@ -11,7 +11,7 @@ import "net/http"
import "github.com/bouncepaw/mycorrhiza/user" import "github.com/bouncepaw/mycorrhiza/user"
//line views/auth.qtpl:3 //line views/auth.qtpl:3
import "github.com/bouncepaw/mycorrhiza/util" import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/auth.qtpl:5 //line views/auth.qtpl:5
import ( import (
@ -35,7 +35,7 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
<section> <section>
`) `)
//line views/auth.qtpl:9 //line views/auth.qtpl:9
if util.UseRegistration { if cfg.UseRegistration {
//line views/auth.qtpl:9 //line views/auth.qtpl:9
qw422016.N().S(` qw422016.N().S(`
<form class="modal" method="post" action="/register?`) <form class="modal" method="post" action="/register?`)
@ -46,7 +46,7 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
<fieldset class="modal__fieldset"> <fieldset class="modal__fieldset">
<legend class="modal__title">Register to `) <legend class="modal__title">Register to `)
//line views/auth.qtpl:12 //line views/auth.qtpl:12
qw422016.E().S(util.SiteName) qw422016.E().S(cfg.WikiName)
//line views/auth.qtpl:12 //line views/auth.qtpl:12
qw422016.N().S(`</legend> qw422016.N().S(`</legend>
@ -59,17 +59,21 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
<input type="password" required name="password"> <input type="password" required name="password">
<p>The server stores your password in an encrypted form; even administrators cannot read it.</p> <p>The server stores your password in an encrypted form; even administrators cannot read it.</p>
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> <p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p>
<input class="modal__action modal__submit" type="submit"> <input class="btn" type="submit" value="Register">
<a class="modal__action modal__cancel" href="/">Cancel</a> <a class="btn btn_weak" href="/`)
//line views/auth.qtpl:24
qw422016.E().S(rq.URL.RawQuery)
//line views/auth.qtpl:24
qw422016.N().S(`">Cancel</a>
</fieldset> </fieldset>
</form> </form>
`) `)
//line views/auth.qtpl:27 //line views/auth.qtpl:27
} else if util.UseFixedAuth { } else if cfg.UseFixedAuth {
//line views/auth.qtpl:27 //line views/auth.qtpl:27
qw422016.N().S(` qw422016.N().S(`
<p>Administrators have forbidden registration for this wiki. Administrators can make an account for you by hand; contact them.</p> <p>Administrators have forbidden registration for this wiki. Administrators can make an account for you by hand; contact them.</p>
<p><a href="`) <p><a href="/`)
//line views/auth.qtpl:29 //line views/auth.qtpl:29
qw422016.E().S(rq.URL.RawQuery) qw422016.E().S(rq.URL.RawQuery)
//line views/auth.qtpl:29 //line views/auth.qtpl:29
@ -80,7 +84,7 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
//line views/auth.qtpl:30 //line views/auth.qtpl:30
qw422016.N().S(` qw422016.N().S(`
<p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p> <p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p>
<p><a href="`) <p><a href="/`)
//line views/auth.qtpl:32 //line views/auth.qtpl:32
qw422016.E().S(rq.URL.RawQuery) qw422016.E().S(rq.URL.RawQuery)
//line views/auth.qtpl:32 //line views/auth.qtpl:32
@ -139,10 +143,9 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
<fieldset class="modal__fieldset"> <fieldset class="modal__fieldset">
<legend class="modal__title">Log in to `) <legend class="modal__title">Log in to `)
//line views/auth.qtpl:46 //line views/auth.qtpl:46
qw422016.E().S(util.SiteName) qw422016.E().S(cfg.WikiName)
//line views/auth.qtpl:46 //line views/auth.qtpl:46
qw422016.N().S(`</legend> qw422016.N().S(`</legend>
<p>Use the data you were given by an administrator.</p>
<label for="login-form__username">Username</label> <label for="login-form__username">Username</label>
<br> <br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username"> <input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
@ -151,182 +154,182 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
<br> <br>
<input type="password" required name="password" autocomplete="current-password"> <input type="password" required name="password" autocomplete="current-password">
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> <p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p>
<input class="modal__action modal__submit" type="submit"> <input class="btn" type="submit" value="Log in">
<a class="modal__action modal__cancel" href="/">Cancel</a> <a class="btn btn_weak" href="/">Cancel</a>
</fieldset> </fieldset>
</form> </form>
`) `)
//line views/auth.qtpl:60 //line views/auth.qtpl:59
} else { } else {
//line views/auth.qtpl:60 //line views/auth.qtpl:59
qw422016.N().S(` qw422016.N().S(`
<p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p> <p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p>
<p><a class="modal__cancel" href="/"> Go home</a></p> <p><a class="modal__cancel" href="/"> Go home</a></p>
`) `)
//line views/auth.qtpl:63 //line views/auth.qtpl:62
} }
//line views/auth.qtpl:63 //line views/auth.qtpl:62
qw422016.N().S(` qw422016.N().S(`
</section> </section>
</main> </main>
</div> </div>
`) `)
//line views/auth.qtpl:67 //line views/auth.qtpl:66
} }
//line views/auth.qtpl:67 //line views/auth.qtpl:66
func WriteLoginHTML(qq422016 qtio422016.Writer) { func WriteLoginHTML(qq422016 qtio422016.Writer) {
//line views/auth.qtpl:67 //line views/auth.qtpl:66
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:67 //line views/auth.qtpl:66
StreamLoginHTML(qw422016) StreamLoginHTML(qw422016)
//line views/auth.qtpl:67 //line views/auth.qtpl:66
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/auth.qtpl:67 //line views/auth.qtpl:66
} }
//line views/auth.qtpl:67 //line views/auth.qtpl:66
func LoginHTML() string { func LoginHTML() string {
//line views/auth.qtpl:67 //line views/auth.qtpl:66
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:67 //line views/auth.qtpl:66
WriteLoginHTML(qb422016) WriteLoginHTML(qb422016)
//line views/auth.qtpl:67 //line views/auth.qtpl:66
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/auth.qtpl:67 //line views/auth.qtpl:66
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:67 //line views/auth.qtpl:66
return qs422016 return qs422016
//line views/auth.qtpl:67 //line views/auth.qtpl:66
} }
//line views/auth.qtpl:69 //line views/auth.qtpl:68
func StreamLoginErrorHTML(qw422016 *qt422016.Writer, err string) { func StreamLoginErrorHTML(qw422016 *qt422016.Writer, err string) {
//line views/auth.qtpl:69 //line views/auth.qtpl:68
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<section> <section>
`) `)
//line views/auth.qtpl:73 //line views/auth.qtpl:72
switch err { switch err {
//line views/auth.qtpl:74 //line views/auth.qtpl:73
case "unknown username": case "unknown username":
//line views/auth.qtpl:74 //line views/auth.qtpl:73
qw422016.N().S(` qw422016.N().S(`
<p class="error">Unknown username.</p> <p class="error">Unknown username.</p>
`) `)
//line views/auth.qtpl:76 //line views/auth.qtpl:75
case "wrong password": case "wrong password":
//line views/auth.qtpl:76 //line views/auth.qtpl:75
qw422016.N().S(` qw422016.N().S(`
<p class="error">Wrong password.</p> <p class="error">Wrong password.</p>
`) `)
//line views/auth.qtpl:78 //line views/auth.qtpl:77
default: default:
//line views/auth.qtpl:78 //line views/auth.qtpl:77
qw422016.N().S(` qw422016.N().S(`
<p class="error">`) <p class="error">`)
//line views/auth.qtpl:79 //line views/auth.qtpl:78
qw422016.E().S(err) qw422016.E().S(err)
//line views/auth.qtpl:79 //line views/auth.qtpl:78
qw422016.N().S(`</p> qw422016.N().S(`</p>
`) `)
//line views/auth.qtpl:80 //line views/auth.qtpl:79
} }
//line views/auth.qtpl:80 //line views/auth.qtpl:79
qw422016.N().S(` qw422016.N().S(`
<p><a href="/login"> Try again</a></p> <p><a href="/login"> Try again</a></p>
</section> </section>
</main> </main>
</div> </div>
`) `)
//line views/auth.qtpl:85 //line views/auth.qtpl:84
} }
//line views/auth.qtpl:85 //line views/auth.qtpl:84
func WriteLoginErrorHTML(qq422016 qtio422016.Writer, err string) { func WriteLoginErrorHTML(qq422016 qtio422016.Writer, err string) {
//line views/auth.qtpl:85 //line views/auth.qtpl:84
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:85 //line views/auth.qtpl:84
StreamLoginErrorHTML(qw422016, err) StreamLoginErrorHTML(qw422016, err)
//line views/auth.qtpl:85 //line views/auth.qtpl:84
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/auth.qtpl:85 //line views/auth.qtpl:84
} }
//line views/auth.qtpl:85 //line views/auth.qtpl:84
func LoginErrorHTML(err string) string { func LoginErrorHTML(err string) string {
//line views/auth.qtpl:85 //line views/auth.qtpl:84
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:85 //line views/auth.qtpl:84
WriteLoginErrorHTML(qb422016, err) WriteLoginErrorHTML(qb422016, err)
//line views/auth.qtpl:85 //line views/auth.qtpl:84
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/auth.qtpl:85 //line views/auth.qtpl:84
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:85 //line views/auth.qtpl:84
return qs422016 return qs422016
//line views/auth.qtpl:85 //line views/auth.qtpl:84
} }
//line views/auth.qtpl:87 //line views/auth.qtpl:86
func StreamLogoutHTML(qw422016 *qt422016.Writer, can bool) { func StreamLogoutHTML(qw422016 *qt422016.Writer, can bool) {
//line views/auth.qtpl:87 //line views/auth.qtpl:86
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<section> <section>
`) `)
//line views/auth.qtpl:91 //line views/auth.qtpl:90
if can { if can {
//line views/auth.qtpl:91 //line views/auth.qtpl:90
qw422016.N().S(` qw422016.N().S(`
<h1>Log out?</h1> <h1>Log out?</h1>
<p><a href="/logout-confirm"><strong>Confirm</strong></a></p> <p><a href="/logout-confirm"><strong>Confirm</strong></a></p>
<p><a href="/">Cancel</a></p> <p><a href="/">Cancel</a></p>
`) `)
//line views/auth.qtpl:95 //line views/auth.qtpl:94
} else { } else {
//line views/auth.qtpl:95 //line views/auth.qtpl:94
qw422016.N().S(` qw422016.N().S(`
<p>You cannot log out because you are not logged in.</p> <p>You cannot log out because you are not logged in.</p>
<p><a href="/login">Login</a></p> <p><a href="/login">Login</a></p>
<p><a href="/login"> Home</a></p> <p><a href="/login"> Home</a></p>
`) `)
//line views/auth.qtpl:99 //line views/auth.qtpl:98
} }
//line views/auth.qtpl:99 //line views/auth.qtpl:98
qw422016.N().S(` qw422016.N().S(`
</section> </section>
</main> </main>
</div> </div>
`) `)
//line views/auth.qtpl:103 //line views/auth.qtpl:102
} }
//line views/auth.qtpl:103 //line views/auth.qtpl:102
func WriteLogoutHTML(qq422016 qtio422016.Writer, can bool) { func WriteLogoutHTML(qq422016 qtio422016.Writer, can bool) {
//line views/auth.qtpl:103 //line views/auth.qtpl:102
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:103 //line views/auth.qtpl:102
StreamLogoutHTML(qw422016, can) StreamLogoutHTML(qw422016, can)
//line views/auth.qtpl:103 //line views/auth.qtpl:102
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/auth.qtpl:103 //line views/auth.qtpl:102
} }
//line views/auth.qtpl:103 //line views/auth.qtpl:102
func LogoutHTML(can bool) string { func LogoutHTML(can bool) string {
//line views/auth.qtpl:103 //line views/auth.qtpl:102
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:103 //line views/auth.qtpl:102
WriteLogoutHTML(qb422016, can) WriteLogoutHTML(qb422016, can)
//line views/auth.qtpl:103 //line views/auth.qtpl:102
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/auth.qtpl:103 //line views/auth.qtpl:102
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:103 //line views/auth.qtpl:102
return qs422016 return qs422016
//line views/auth.qtpl:103 //line views/auth.qtpl:102
} }

View File

@ -1,5 +1,6 @@
{% import "net/http" %} {% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %} {% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %} {% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} {% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
@ -44,7 +45,7 @@ if err != nil {
recent changes recent changes
</nav> </nav>
<p><img class="icon" width="20" height="20" src="/static/icon/feed">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p> <p><img class="icon" width="20" height="20" src="/assets/icon/feed">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p>
{% comment %} {% comment %}
Here I am, willing to add some accessibility using ARIA. Turns out, Here I am, willing to add some accessibility using ARIA. Turns out,
@ -76,7 +77,7 @@ if err != nil {
<li class="rc-entry__time"><time>{%s rev.TimeString() %}</time></li> <li class="rc-entry__time"><time>{%s rev.TimeString() %}</time></li>
<li class="rc-entry__hash">{%s rev.Hash %}</li> <li class="rc-entry__hash">{%s rev.Hash %}</li>
<li class="rc-entry__links">{%s= rev.HyphaeLinksHTML() %}</li> <li class="rc-entry__links">{%s= rev.HyphaeLinksHTML() %}</li>
<li class="rc-entry__msg">{%s rev.Message %} {% if rev.Username != "anon" %}<span class="rc-entry__author">by <a href="/hypha/{%s util.UserHypha %}/{%s rev.Username %}" rel="author">{%s rev.Username %}</a></span>{% endif %}</li> <li class="rc-entry__msg">{%s rev.Message %} {% if rev.Username != "anon" %}<span class="rc-entry__author">by <a href="/hypha/{%s cfg.UserHypha %}/{%s rev.Username %}" rel="author">{%s rev.Username %}</a></span>{% endif %}</li>
{% endfunc %} {% endfunc %}
{% func HistoryHTML(rq *http.Request, hyphaName, list string) %} {% func HistoryHTML(rq *http.Request, hyphaName, list string) %}

View File

@ -8,101 +8,104 @@ package views
import "net/http" import "net/http"
//line views/history.qtpl:3 //line views/history.qtpl:3
import "github.com/bouncepaw/mycorrhiza/util" import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/history.qtpl:4 //line views/history.qtpl:4
import "github.com/bouncepaw/mycorrhiza/user" import "github.com/bouncepaw/mycorrhiza/util"
//line views/history.qtpl:5 //line views/history.qtpl:5
import "github.com/bouncepaw/mycorrhiza/hyphae" import "github.com/bouncepaw/mycorrhiza/user"
//line views/history.qtpl:6 //line views/history.qtpl:6
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/history.qtpl:7
import "github.com/bouncepaw/mycorrhiza/history" import "github.com/bouncepaw/mycorrhiza/history"
//line views/history.qtpl:9 //line views/history.qtpl:10
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line views/history.qtpl:9 //line views/history.qtpl:10
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line views/history.qtpl:9 //line views/history.qtpl:10
func StreamPrimitiveDiffHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) { func StreamPrimitiveDiffHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) {
//line views/history.qtpl:9 //line views/history.qtpl:10
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:11 //line views/history.qtpl:12
text, err := history.PrimitiveDiffAtRevision(h.TextPath, hash) text, err := history.PrimitiveDiffAtRevision(h.TextPath, hash)
if err != nil { if err != nil {
text = err.Error() text = err.Error()
} }
//line views/history.qtpl:15 //line views/history.qtpl:16
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:16 //line views/history.qtpl:17
StreamNavHTML(qw422016, rq, h.Name, "history") StreamNavHTML(qw422016, rq, h.Name, "history")
//line views/history.qtpl:16 //line views/history.qtpl:17
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<article> <article>
<h1>Diff `) <h1>Diff `)
//line views/history.qtpl:20 //line views/history.qtpl:21
qw422016.E().S(util.BeautifulName(h.Name)) qw422016.E().S(util.BeautifulName(h.Name))
//line views/history.qtpl:20 //line views/history.qtpl:21
qw422016.N().S(` at `) qw422016.N().S(` at `)
//line views/history.qtpl:20 //line views/history.qtpl:21
qw422016.E().S(hash) qw422016.E().S(hash)
//line views/history.qtpl:20 //line views/history.qtpl:21
qw422016.N().S(`</h1> qw422016.N().S(`</h1>
<pre class="codeblock"><code>`) <pre class="codeblock"><code>`)
//line views/history.qtpl:21 //line views/history.qtpl:22
qw422016.E().S(text) qw422016.E().S(text)
//line views/history.qtpl:21 //line views/history.qtpl:22
qw422016.N().S(`</code></pre> qw422016.N().S(`</code></pre>
</article> </article>
</main> </main>
</div> </div>
`) `)
//line views/history.qtpl:25 //line views/history.qtpl:26
} }
//line views/history.qtpl:25 //line views/history.qtpl:26
func WritePrimitiveDiffHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) { func WritePrimitiveDiffHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) {
//line views/history.qtpl:25 //line views/history.qtpl:26
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/history.qtpl:25 //line views/history.qtpl:26
StreamPrimitiveDiffHTML(qw422016, rq, h, u, hash) StreamPrimitiveDiffHTML(qw422016, rq, h, u, hash)
//line views/history.qtpl:25 //line views/history.qtpl:26
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/history.qtpl:25 //line views/history.qtpl:26
} }
//line views/history.qtpl:25 //line views/history.qtpl:26
func PrimitiveDiffHTML(rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) string { func PrimitiveDiffHTML(rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) string {
//line views/history.qtpl:25 //line views/history.qtpl:26
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/history.qtpl:25 //line views/history.qtpl:26
WritePrimitiveDiffHTML(qb422016, rq, h, u, hash) WritePrimitiveDiffHTML(qb422016, rq, h, u, hash)
//line views/history.qtpl:25 //line views/history.qtpl:26
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/history.qtpl:25 //line views/history.qtpl:26
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/history.qtpl:25 //line views/history.qtpl:26
return qs422016 return qs422016
//line views/history.qtpl:25 //line views/history.qtpl:26
} }
//line views/history.qtpl:27 //line views/history.qtpl:28
func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) { func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) {
//line views/history.qtpl:27 //line views/history.qtpl:28
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width recent-changes"> <main class="main-width recent-changes">
@ -111,268 +114,268 @@ func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) {
<nav class="recent-changes__count"> <nav class="recent-changes__count">
See See
`) `)
//line views/history.qtpl:34 //line views/history.qtpl:35
for _, m := range []int{20, 0, 50, 0, 100} { for _, m := range []int{20, 0, 50, 0, 100} {
//line views/history.qtpl:34 //line views/history.qtpl:35
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:35 //line views/history.qtpl:36
switch m { switch m {
//line views/history.qtpl:36 //line views/history.qtpl:37
case 0: case 0:
//line views/history.qtpl:36 //line views/history.qtpl:37
qw422016.N().S(` qw422016.N().S(`
<span aria-hidden="true">|</span> <span aria-hidden="true">|</span>
`) `)
//line views/history.qtpl:38 //line views/history.qtpl:39
case n: case n:
//line views/history.qtpl:38 //line views/history.qtpl:39
qw422016.N().S(` qw422016.N().S(`
<b>`) <b>`)
//line views/history.qtpl:39 //line views/history.qtpl:40
qw422016.N().D(n) qw422016.N().D(n)
//line views/history.qtpl:39 //line views/history.qtpl:40
qw422016.N().S(`</b> qw422016.N().S(`</b>
`) `)
//line views/history.qtpl:40 //line views/history.qtpl:41
default: default:
//line views/history.qtpl:40 //line views/history.qtpl:41
qw422016.N().S(` qw422016.N().S(`
<a href="/recent-changes/`) <a href="/recent-changes/`)
//line views/history.qtpl:41 //line views/history.qtpl:42
qw422016.N().D(m) qw422016.N().D(m)
//line views/history.qtpl:41 //line views/history.qtpl:42
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/history.qtpl:41 //line views/history.qtpl:42
qw422016.N().D(m) qw422016.N().D(m)
//line views/history.qtpl:41 //line views/history.qtpl:42
qw422016.N().S(`</a> qw422016.N().S(`</a>
`) `)
//line views/history.qtpl:42 //line views/history.qtpl:43
} }
//line views/history.qtpl:42 //line views/history.qtpl:43
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:43 //line views/history.qtpl:44
} }
//line views/history.qtpl:43 //line views/history.qtpl:44
qw422016.N().S(` qw422016.N().S(`
recent changes recent changes
</nav> </nav>
<p><img class="icon" width="20" height="20" src="/static/icon/feed">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p> <p><img class="icon" width="20" height="20" src="/assets/icon/feed">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p>
`) `)
//line views/history.qtpl:54 //line views/history.qtpl:55
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:57 //line views/history.qtpl:58
changes := history.RecentChanges(n) changes := history.RecentChanges(n)
//line views/history.qtpl:58 //line views/history.qtpl:59
qw422016.N().S(` qw422016.N().S(`
<section class="recent-changes__list" role="feed"> <section class="recent-changes__list" role="feed">
`) `)
//line views/history.qtpl:60 //line views/history.qtpl:61
if len(changes) == 0 { if len(changes) == 0 {
//line views/history.qtpl:60 //line views/history.qtpl:61
qw422016.N().S(` qw422016.N().S(`
<p>Could not find any recent changes.</p> <p>Could not find any recent changes.</p>
`) `)
//line views/history.qtpl:62 //line views/history.qtpl:63
} else { } else {
//line views/history.qtpl:62 //line views/history.qtpl:63
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:63 //line views/history.qtpl:64
for i, entry := range changes { for i, entry := range changes {
//line views/history.qtpl:63 //line views/history.qtpl:64
qw422016.N().S(` qw422016.N().S(`
<ul class="recent-changes__entry rc-entry" role="article" <ul class="recent-changes__entry rc-entry" role="article"
aria-setsize="`) aria-setsize="`)
//line views/history.qtpl:65 //line views/history.qtpl:66
qw422016.N().D(n) qw422016.N().D(n)
//line views/history.qtpl:65 //line views/history.qtpl:66
qw422016.N().S(`" aria-posinset="`) qw422016.N().S(`" aria-posinset="`)
//line views/history.qtpl:65 //line views/history.qtpl:66
qw422016.N().D(i) qw422016.N().D(i)
//line views/history.qtpl:65 //line views/history.qtpl:66
qw422016.N().S(`"> qw422016.N().S(`">
`) `)
//line views/history.qtpl:66 //line views/history.qtpl:67
qw422016.N().S(recentChangesEntry(entry)) qw422016.N().S(recentChangesEntry(entry))
//line views/history.qtpl:66 //line views/history.qtpl:67
qw422016.N().S(` qw422016.N().S(`
</ul> </ul>
`) `)
//line views/history.qtpl:68 //line views/history.qtpl:69
} }
//line views/history.qtpl:68 //line views/history.qtpl:69
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:69 //line views/history.qtpl:70
} }
//line views/history.qtpl:69 //line views/history.qtpl:70
qw422016.N().S(` qw422016.N().S(`
</section> </section>
</main> </main>
</div> </div>
`) `)
//line views/history.qtpl:73 //line views/history.qtpl:74
} }
//line views/history.qtpl:73 //line views/history.qtpl:74
func WriteRecentChangesHTML(qq422016 qtio422016.Writer, n int) { func WriteRecentChangesHTML(qq422016 qtio422016.Writer, n int) {
//line views/history.qtpl:73 //line views/history.qtpl:74
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/history.qtpl:73 //line views/history.qtpl:74
StreamRecentChangesHTML(qw422016, n) StreamRecentChangesHTML(qw422016, n)
//line views/history.qtpl:73 //line views/history.qtpl:74
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/history.qtpl:73 //line views/history.qtpl:74
} }
//line views/history.qtpl:73 //line views/history.qtpl:74
func RecentChangesHTML(n int) string { func RecentChangesHTML(n int) string {
//line views/history.qtpl:73 //line views/history.qtpl:74
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/history.qtpl:73 //line views/history.qtpl:74
WriteRecentChangesHTML(qb422016, n) WriteRecentChangesHTML(qb422016, n)
//line views/history.qtpl:73 //line views/history.qtpl:74
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/history.qtpl:73 //line views/history.qtpl:74
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/history.qtpl:73 //line views/history.qtpl:74
return qs422016 return qs422016
//line views/history.qtpl:73 //line views/history.qtpl:74
} }
//line views/history.qtpl:75 //line views/history.qtpl:76
func streamrecentChangesEntry(qw422016 *qt422016.Writer, rev history.Revision) { func streamrecentChangesEntry(qw422016 *qt422016.Writer, rev history.Revision) {
//line views/history.qtpl:75 //line views/history.qtpl:76
qw422016.N().S(` qw422016.N().S(`
<li class="rc-entry__time"><time>`) <li class="rc-entry__time"><time>`)
//line views/history.qtpl:76 //line views/history.qtpl:77
qw422016.E().S(rev.TimeString()) qw422016.E().S(rev.TimeString())
//line views/history.qtpl:76 //line views/history.qtpl:77
qw422016.N().S(`</time></li> qw422016.N().S(`</time></li>
<li class="rc-entry__hash">`) <li class="rc-entry__hash">`)
//line views/history.qtpl:77 //line views/history.qtpl:78
qw422016.E().S(rev.Hash) qw422016.E().S(rev.Hash)
//line views/history.qtpl:77 //line views/history.qtpl:78
qw422016.N().S(`</li> qw422016.N().S(`</li>
<li class="rc-entry__links">`) <li class="rc-entry__links">`)
//line views/history.qtpl:78 //line views/history.qtpl:79
qw422016.N().S(rev.HyphaeLinksHTML()) qw422016.N().S(rev.HyphaeLinksHTML())
//line views/history.qtpl:78 //line views/history.qtpl:79
qw422016.N().S(`</li> qw422016.N().S(`</li>
<li class="rc-entry__msg">`) <li class="rc-entry__msg">`)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.E().S(rev.Message) qw422016.E().S(rev.Message)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.N().S(` `) qw422016.N().S(` `)
//line views/history.qtpl:79 //line views/history.qtpl:80
if rev.Username != "anon" { if rev.Username != "anon" {
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.N().S(`<span class="rc-entry__author">by <a href="/hypha/`) qw422016.N().S(`<span class="rc-entry__author">by <a href="/hypha/`)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.E().S(util.UserHypha) qw422016.E().S(cfg.UserHypha)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.E().S(rev.Username) qw422016.E().S(rev.Username)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.N().S(`" rel="author">`) qw422016.N().S(`" rel="author">`)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.E().S(rev.Username) qw422016.E().S(rev.Username)
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.N().S(`</a></span>`) qw422016.N().S(`</a></span>`)
//line views/history.qtpl:79 //line views/history.qtpl:80
} }
//line views/history.qtpl:79 //line views/history.qtpl:80
qw422016.N().S(`</li> qw422016.N().S(`</li>
`) `)
//line views/history.qtpl:80 //line views/history.qtpl:81
} }
//line views/history.qtpl:80 //line views/history.qtpl:81
func writerecentChangesEntry(qq422016 qtio422016.Writer, rev history.Revision) { func writerecentChangesEntry(qq422016 qtio422016.Writer, rev history.Revision) {
//line views/history.qtpl:80 //line views/history.qtpl:81
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/history.qtpl:80 //line views/history.qtpl:81
streamrecentChangesEntry(qw422016, rev) streamrecentChangesEntry(qw422016, rev)
//line views/history.qtpl:80 //line views/history.qtpl:81
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/history.qtpl:80 //line views/history.qtpl:81
} }
//line views/history.qtpl:80 //line views/history.qtpl:81
func recentChangesEntry(rev history.Revision) string { func recentChangesEntry(rev history.Revision) string {
//line views/history.qtpl:80 //line views/history.qtpl:81
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/history.qtpl:80 //line views/history.qtpl:81
writerecentChangesEntry(qb422016, rev) writerecentChangesEntry(qb422016, rev)
//line views/history.qtpl:80 //line views/history.qtpl:81
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/history.qtpl:80 //line views/history.qtpl:81
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/history.qtpl:80 //line views/history.qtpl:81
return qs422016 return qs422016
//line views/history.qtpl:80 //line views/history.qtpl:81
} }
//line views/history.qtpl:82 //line views/history.qtpl:83
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) { func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) {
//line views/history.qtpl:82 //line views/history.qtpl:83
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/history.qtpl:83 //line views/history.qtpl:84
StreamNavHTML(qw422016, rq, hyphaName, "history") StreamNavHTML(qw422016, rq, hyphaName, "history")
//line views/history.qtpl:83 //line views/history.qtpl:84
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<article class="history"> <article class="history">
<h1>History of `) <h1>History of `)
//line views/history.qtpl:87 //line views/history.qtpl:88
qw422016.E().S(util.BeautifulName(hyphaName)) qw422016.E().S(util.BeautifulName(hyphaName))
//line views/history.qtpl:87 //line views/history.qtpl:88
qw422016.N().S(`</h1> qw422016.N().S(`</h1>
`) `)
//line views/history.qtpl:88 //line views/history.qtpl:89
qw422016.N().S(list) qw422016.N().S(list)
//line views/history.qtpl:88 //line views/history.qtpl:89
qw422016.N().S(` qw422016.N().S(`
</article> </article>
</main> </main>
</div> </div>
`) `)
//line views/history.qtpl:92 //line views/history.qtpl:93
} }
//line views/history.qtpl:92 //line views/history.qtpl:93
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) { func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) {
//line views/history.qtpl:92 //line views/history.qtpl:93
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/history.qtpl:92 //line views/history.qtpl:93
StreamHistoryHTML(qw422016, rq, hyphaName, list) StreamHistoryHTML(qw422016, rq, hyphaName, list)
//line views/history.qtpl:92 //line views/history.qtpl:93
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/history.qtpl:92 //line views/history.qtpl:93
} }
//line views/history.qtpl:92 //line views/history.qtpl:93
func HistoryHTML(rq *http.Request, hyphaName, list string) string { func HistoryHTML(rq *http.Request, hyphaName, list string) string {
//line views/history.qtpl:92 //line views/history.qtpl:93
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/history.qtpl:92 //line views/history.qtpl:93
WriteHistoryHTML(qb422016, rq, hyphaName, list) WriteHistoryHTML(qb422016, rq, hyphaName, list)
//line views/history.qtpl:92 //line views/history.qtpl:93
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/history.qtpl:92 //line views/history.qtpl:93
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/history.qtpl:92 //line views/history.qtpl:93
return qs422016 return qs422016
//line views/history.qtpl:92 //line views/history.qtpl:93
} }

View File

@ -1,8 +1,47 @@
{% import "path/filepath" %} {% import "path/filepath" %}
{% import "strings" %} {% import "strings" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} {% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %} {% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func nonExistentHyphaNotice(h *hyphae.Hypha, u *user.User) %}
<section class="non-existent-hypha">
<h2 class="non-existent-hypha__title">This hypha does not exist</h2>
{% if user.AuthUsed && u.Group == "anon" %}
<p>You are not authorized to create new hyphae. Here is what you can do:</p>
<ul>
<li><a href="/login">Log in to your account, if you have one</a></li>
{% if cfg.UseRegistration %}<li><a href="/register">Register a new account</a></li>{% endif %}
</ul>
{% else %}
<div class="non-existent-hypha__ways">
<section class="non-existent-hypha__way">
<h3 class="non-existent-hypha__subtitle">📝 Write a text</h3>
<p>Write a note, a diary, an article, a story or anything textual using <a href="https://mycorrhiza.lesarbr.es/hypha/mycomarkup">Mycomarkup</a>. Full history of edits to the document will be saved.</p>
<p>Make sure to follow this wiki&apos;s writing conventions if there are any.</p>
<a class="btn stick-to-bottom" href="/edit/{%s h.Name %}">Create</a>
</section>
<section class="non-existent-hypha__way">
<h3 class="non-existent-hypha__subtitle">🖼 Upload a media</h3>
<p>Upload a picture, a video or an audio. Most common formats can be accessed from the browser, others can be only downloaded afterwards. You can write a description for the media later.</p>
<form action="/upload-binary/{%s h.Name %}"
method="post" enctype="multipart/form-data"
class="upload-binary">
<label for="upload-binary__input"></label>
<input type="file" id="upload-binary__input" name="binary">
<input type="submit" class="btn stick-to-bottom" value="Upload">
</form>
</section>
</div>
{% endif %}
</section>
{% endfunc %}
{% func NaviTitleHTML(h *hyphae.Hypha) %} {% func NaviTitleHTML(h *hyphae.Hypha) %}
{% code {% code
var ( var (
@ -12,8 +51,8 @@
%} %}
<h1 class="navi-title"> <h1 class="navi-title">
{% stripspace %} {% stripspace %}
<a href="/hypha/{%s util.HomePage %}"> <a href="/hypha/{%s cfg.HomeHypha %}">
{%-s= util.SiteNavIcon -%} {%-s= cfg.NaviTitleIcon -%}
<span aria-hidden="true" class="navi-title__colon">:</span> <span aria-hidden="true" class="navi-title__colon">:</span>
</a> </a>

View File

@ -10,224 +10,325 @@ import "path/filepath"
//line views/hypha.qtpl:2 //line views/hypha.qtpl:2
import "strings" import "strings"
//line views/hypha.qtpl:3 //line views/hypha.qtpl:4
import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/hypha.qtpl:5
import "github.com/bouncepaw/mycorrhiza/hyphae" import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/hypha.qtpl:4 //line views/hypha.qtpl:6
import "github.com/bouncepaw/mycorrhiza/user"
//line views/hypha.qtpl:7
import "github.com/bouncepaw/mycorrhiza/util" import "github.com/bouncepaw/mycorrhiza/util"
//line views/hypha.qtpl:6 //line views/hypha.qtpl:9
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line views/hypha.qtpl:6 //line views/hypha.qtpl:9
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line views/hypha.qtpl:6 //line views/hypha.qtpl:9
func streamnonExistentHyphaNotice(qw422016 *qt422016.Writer, h *hyphae.Hypha, u *user.User) {
//line views/hypha.qtpl:9
qw422016.N().S(`
<section class="non-existent-hypha">
<h2 class="non-existent-hypha__title">This hypha does not exist</h2>
`)
//line views/hypha.qtpl:12
if user.AuthUsed && u.Group == "anon" {
//line views/hypha.qtpl:12
qw422016.N().S(`
<p>You are not authorized to create new hyphae. Here is what you can do:</p>
<ul>
<li><a href="/login">Log in to your account, if you have one</a></li>
`)
//line views/hypha.qtpl:16
if cfg.UseRegistration {
//line views/hypha.qtpl:16
qw422016.N().S(`<li><a href="/register">Register a new account</a></li>`)
//line views/hypha.qtpl:16
}
//line views/hypha.qtpl:16
qw422016.N().S(`
</ul>
`)
//line views/hypha.qtpl:18
} else {
//line views/hypha.qtpl:18
qw422016.N().S(`
<div class="non-existent-hypha__ways">
<section class="non-existent-hypha__way">
<h3 class="non-existent-hypha__subtitle">📝 Write a text</h3>
<p>Write a note, a diary, an article, a story or anything textual using <a href="https://mycorrhiza.lesarbr.es/hypha/mycomarkup">Mycomarkup</a>. Full history of edits to the document will be saved.</p>
<p>Make sure to follow this wiki&apos;s writing conventions if there are any.</p>
<a class="btn stick-to-bottom" href="/edit/`)
//line views/hypha.qtpl:25
qw422016.E().S(h.Name)
//line views/hypha.qtpl:25
qw422016.N().S(`">Create</a>
</section>
<section class="non-existent-hypha__way">
<h3 class="non-existent-hypha__subtitle">🖼 Upload a media</h3>
<p>Upload a picture, a video or an audio. Most common formats can be accessed from the browser, others can be only downloaded afterwards. You can write a description for the media later.</p>
<form action="/upload-binary/`)
//line views/hypha.qtpl:31
qw422016.E().S(h.Name)
//line views/hypha.qtpl:31
qw422016.N().S(`"
method="post" enctype="multipart/form-data"
class="upload-binary">
<label for="upload-binary__input"></label>
<input type="file" id="upload-binary__input" name="binary">
<input type="submit" class="btn stick-to-bottom" value="Upload">
</form>
</section>
</div>
`)
//line views/hypha.qtpl:41
}
//line views/hypha.qtpl:41
qw422016.N().S(`
</section>
`)
//line views/hypha.qtpl:43
}
//line views/hypha.qtpl:43
func writenonExistentHyphaNotice(qq422016 qtio422016.Writer, h *hyphae.Hypha, u *user.User) {
//line views/hypha.qtpl:43
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:43
streamnonExistentHyphaNotice(qw422016, h, u)
//line views/hypha.qtpl:43
qt422016.ReleaseWriter(qw422016)
//line views/hypha.qtpl:43
}
//line views/hypha.qtpl:43
func nonExistentHyphaNotice(h *hyphae.Hypha, u *user.User) string {
//line views/hypha.qtpl:43
qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:43
writenonExistentHyphaNotice(qb422016, h, u)
//line views/hypha.qtpl:43
qs422016 := string(qb422016.B)
//line views/hypha.qtpl:43
qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:43
return qs422016
//line views/hypha.qtpl:43
}
//line views/hypha.qtpl:45
func StreamNaviTitleHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) { func StreamNaviTitleHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:6 //line views/hypha.qtpl:45
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/hypha.qtpl:8 //line views/hypha.qtpl:47
var ( var (
prevAcc = "/hypha/" prevAcc = "/hypha/"
parts = strings.Split(h.Name, "/") parts = strings.Split(h.Name, "/")
) )
//line views/hypha.qtpl:12 //line views/hypha.qtpl:51
qw422016.N().S(` qw422016.N().S(`
<h1 class="navi-title"> <h1 class="navi-title">
`) `)
//line views/hypha.qtpl:14 //line views/hypha.qtpl:53
qw422016.N().S(`<a href="/hypha/`) qw422016.N().S(`<a href="/hypha/`)
//line views/hypha.qtpl:15 //line views/hypha.qtpl:54
qw422016.E().S(util.HomePage) qw422016.E().S(cfg.HomeHypha)
//line views/hypha.qtpl:15 //line views/hypha.qtpl:54
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/hypha.qtpl:16 //line views/hypha.qtpl:55
qw422016.N().S(util.SiteNavIcon) qw422016.N().S(cfg.NaviTitleIcon)
//line views/hypha.qtpl:16 //line views/hypha.qtpl:55
qw422016.N().S(`<span aria-hidden="true" class="navi-title__colon">:</span></a>`) qw422016.N().S(`<span aria-hidden="true" class="navi-title__colon">:</span></a>`)
//line views/hypha.qtpl:20 //line views/hypha.qtpl:59
for i, part := range parts { for i, part := range parts {
//line views/hypha.qtpl:21 //line views/hypha.qtpl:60
if i > 0 { if i > 0 {
//line views/hypha.qtpl:21 //line views/hypha.qtpl:60
qw422016.N().S(`<span aria-hidden="true" class="navi-title__separator">/</span>`) qw422016.N().S(`<span aria-hidden="true" class="navi-title__separator">/</span>`)
//line views/hypha.qtpl:23 //line views/hypha.qtpl:62
} }
//line views/hypha.qtpl:23 //line views/hypha.qtpl:62
qw422016.N().S(`<a href="`) qw422016.N().S(`<a href="`)
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
qw422016.E().S(prevAcc + part) qw422016.E().S(prevAcc + part)
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
qw422016.N().S(`" rel="`) qw422016.N().S(`" rel="`)
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
if i == len(parts)-1 { if i == len(parts)-1 {
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
qw422016.N().S(`bookmark`) qw422016.N().S(`bookmark`)
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
} else { } else {
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
qw422016.N().S(`up`) qw422016.N().S(`up`)
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
} }
//line views/hypha.qtpl:25 //line views/hypha.qtpl:64
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/hypha.qtpl:26 //line views/hypha.qtpl:65
qw422016.N().S(util.BeautifulName(part)) qw422016.N().S(util.BeautifulName(part))
//line views/hypha.qtpl:26 //line views/hypha.qtpl:65
qw422016.N().S(`</a>`) qw422016.N().S(`</a>`)
//line views/hypha.qtpl:28 //line views/hypha.qtpl:67
prevAcc += part + "/" prevAcc += part + "/"
//line views/hypha.qtpl:29 //line views/hypha.qtpl:68
} }
//line views/hypha.qtpl:30 //line views/hypha.qtpl:69
qw422016.N().S(` qw422016.N().S(`
</h1> </h1>
`) `)
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
} }
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
func WriteNaviTitleHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) { func WriteNaviTitleHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
StreamNaviTitleHTML(qw422016, h) StreamNaviTitleHTML(qw422016, h)
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
} }
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
func NaviTitleHTML(h *hyphae.Hypha) string { func NaviTitleHTML(h *hyphae.Hypha) string {
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
WriteNaviTitleHTML(qb422016, h) WriteNaviTitleHTML(qb422016, h)
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
return qs422016 return qs422016
//line views/hypha.qtpl:32 //line views/hypha.qtpl:71
} }
//line views/hypha.qtpl:34 //line views/hypha.qtpl:73
func StreamAttachmentHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) { func StreamAttachmentHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:34 //line views/hypha.qtpl:73
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/hypha.qtpl:35 //line views/hypha.qtpl:74
switch filepath.Ext(h.BinaryPath) { switch filepath.Ext(h.BinaryPath) {
//line views/hypha.qtpl:37 //line views/hypha.qtpl:76
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico": case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
//line views/hypha.qtpl:37 //line views/hypha.qtpl:76
qw422016.N().S(` qw422016.N().S(`
<div class="binary-container binary-container_with-img"> <div class="binary-container binary-container_with-img">
<a href="/binary/`) <a href="/binary/`)
//line views/hypha.qtpl:39 //line views/hypha.qtpl:78
qw422016.N().S(h.Name) qw422016.N().S(h.Name)
//line views/hypha.qtpl:39 //line views/hypha.qtpl:78
qw422016.N().S(`"><img src="/binary/`) qw422016.N().S(`"><img src="/binary/`)
//line views/hypha.qtpl:39 //line views/hypha.qtpl:78
qw422016.N().S(h.Name) qw422016.N().S(h.Name)
//line views/hypha.qtpl:39 //line views/hypha.qtpl:78
qw422016.N().S(`"/></a> qw422016.N().S(`"/></a>
</div> </div>
`) `)
//line views/hypha.qtpl:42 //line views/hypha.qtpl:81
case ".ogg", ".webm", ".mp4": case ".ogg", ".webm", ".mp4":
//line views/hypha.qtpl:42 //line views/hypha.qtpl:81
qw422016.N().S(` qw422016.N().S(`
<div class="binary-container binary-container_with-video"> <div class="binary-container binary-container_with-video">
<video controls> <video controls>
<source src="/binary/`) <source src="/binary/`)
//line views/hypha.qtpl:45 //line views/hypha.qtpl:84
qw422016.N().S(h.Name) qw422016.N().S(h.Name)
//line views/hypha.qtpl:45 //line views/hypha.qtpl:84
qw422016.N().S(`"/> qw422016.N().S(`"/>
<p>Your browser does not support video. <a href="/binary/`) <p>Your browser does not support video. <a href="/binary/`)
//line views/hypha.qtpl:46 //line views/hypha.qtpl:85
qw422016.N().S(h.Name) qw422016.N().S(h.Name)
//line views/hypha.qtpl:46 //line views/hypha.qtpl:85
qw422016.N().S(`">Download video</a></p> qw422016.N().S(`">Download video</a></p>
</video> </video>
</div> </div>
`) `)
//line views/hypha.qtpl:50 //line views/hypha.qtpl:89
case ".mp3": case ".mp3":
//line views/hypha.qtpl:50 //line views/hypha.qtpl:89
qw422016.N().S(` qw422016.N().S(`
<div class="binary-container binary-container_with-audio"> <div class="binary-container binary-container_with-audio">
<audio controls> <audio controls>
<source src="/binary/`) <source src="/binary/`)
//line views/hypha.qtpl:53 //line views/hypha.qtpl:92
qw422016.N().S(h.Name) qw422016.N().S(h.Name)
//line views/hypha.qtpl:53 //line views/hypha.qtpl:92
qw422016.N().S(`"/> qw422016.N().S(`"/>
<p>Your browser does not support audio. <a href="/binary/`) <p>Your browser does not support audio. <a href="/binary/`)
//line views/hypha.qtpl:54 //line views/hypha.qtpl:93
qw422016.N().S(h.Name) qw422016.N().S(h.Name)
//line views/hypha.qtpl:54 //line views/hypha.qtpl:93
qw422016.N().S(`">Download audio</a></p> qw422016.N().S(`">Download audio</a></p>
</audio> </audio>
</div> </div>
`) `)
//line views/hypha.qtpl:58 //line views/hypha.qtpl:97
default: default:
//line views/hypha.qtpl:58 //line views/hypha.qtpl:97
qw422016.N().S(` qw422016.N().S(`
<div class="binary-container binary-container_with-nothing"> <div class="binary-container binary-container_with-nothing">
<p><a href="/binary/`) <p><a href="/binary/`)
//line views/hypha.qtpl:60 //line views/hypha.qtpl:99
qw422016.N().S(h.Name) qw422016.N().S(h.Name)
//line views/hypha.qtpl:60 //line views/hypha.qtpl:99
qw422016.N().S(`">Download media</a></p> qw422016.N().S(`">Download media</a></p>
</div> </div>
`) `)
//line views/hypha.qtpl:62 //line views/hypha.qtpl:101
} }
//line views/hypha.qtpl:62 //line views/hypha.qtpl:101
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
} }
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
func WriteAttachmentHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) { func WriteAttachmentHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
StreamAttachmentHTML(qw422016, h) StreamAttachmentHTML(qw422016, h)
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
} }
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
func AttachmentHTML(h *hyphae.Hypha) string { func AttachmentHTML(h *hyphae.Hypha) string {
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
WriteAttachmentHTML(qb422016, h) WriteAttachmentHTML(qb422016, h)
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
return qs422016 return qs422016
//line views/hypha.qtpl:63 //line views/hypha.qtpl:102
} }

View File

@ -54,8 +54,8 @@
{% endfunc %} {% endfunc %}
{% func modalEnd(hyphaName string, shouldFocusOnConfirm bool) %} {% func modalEnd(hyphaName string, shouldFocusOnConfirm bool) %}
<input type="submit" value="Confirm" class="modal__action modal__submit" {% if shouldFocusOnConfirm %}autofocus{% endif %}> <input type="submit" value="Confirm" class="btn" {% if shouldFocusOnConfirm %}autofocus{% endif %}>
<a href="/hypha/{%s hyphaName %}" class="modal__action modal__cancel">Cancel</a> <a href="/hypha/{%s hyphaName %}" class="btn btn_weak">Cancel</a>
</fieldset> </fieldset>
</form> </form>
</main> </main>

View File

@ -306,7 +306,7 @@ func modalBegin(path, hyphaName, formAttrs, legend string) string {
func streammodalEnd(qw422016 *qt422016.Writer, hyphaName string, shouldFocusOnConfirm bool) { func streammodalEnd(qw422016 *qt422016.Writer, hyphaName string, shouldFocusOnConfirm bool) {
//line views/modal.qtpl:56 //line views/modal.qtpl:56
qw422016.N().S(` qw422016.N().S(`
<input type="submit" value="Confirm" class="modal__action modal__submit" `) <input type="submit" value="Confirm" class="btn" `)
//line views/modal.qtpl:57 //line views/modal.qtpl:57
if shouldFocusOnConfirm { if shouldFocusOnConfirm {
//line views/modal.qtpl:57 //line views/modal.qtpl:57
@ -319,7 +319,7 @@ func streammodalEnd(qw422016 *qt422016.Writer, hyphaName string, shouldFocusOnCo
//line views/modal.qtpl:58 //line views/modal.qtpl:58
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line views/modal.qtpl:58 //line views/modal.qtpl:58
qw422016.N().S(`" class="modal__action modal__cancel">Cancel</a> qw422016.N().S(`" class="btn btn_weak">Cancel</a>
</fieldset> </fieldset>
</form> </form>
</main> </main>

View File

@ -1,4 +1,6 @@
{% import "net/http" %} {% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %} {% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %} {% import "github.com/bouncepaw/mycorrhiza/user" %}
@ -18,7 +20,7 @@
{"italic", "wrapItalic()", "<i>//Italic//</i>"}, {"italic", "wrapItalic()", "<i>//Italic//</i>"},
{"highlighted", "wrapHighlighted()", "<mark>!!Highlight!!</mark>"}, {"highlighted", "wrapHighlighted()", "<mark>!!Highlight!!</mark>"},
{"monospace", "wrapMonospace()", "<code>`Monospace`</code>"}, {"monospace", "wrapMonospace()", "<code>`Monospace`</code>"},
{"lifted", "wrapLifted()", "<sup>^Lifted^</sup>"}, {"lifted", "wrapLifted()", "<sup>^^Lifted^^</sup>"},
{"lowered", "wrapLowered()", "<sub>,,Lowered,,</sub>"}, {"lowered", "wrapLowered()", "<sub>,,Lowered,,</sub>"},
{"strikethrough", "wrapStrikethrough()", "<strike>~~Strikethrough~~</strike>"}, {"strikethrough", "wrapStrikethrough()", "<strike>~~Strikethrough~~</strike>"},
{"rocket", "insertRocket()", "=> rocketlink"}, {"rocket", "insertRocket()", "=> rocketlink"},
@ -46,6 +48,7 @@
display string display string
}{ }{
{"date", "insertDate()", "Insert current date"}, {"date", "insertDate()", "Insert current date"},
{"time", "insertTimeUTC()", "Insert current time"},
} %} } %}
<button <button
class="edit-toolbar__btn edit-toolbar__{%s el.class %}" class="edit-toolbar__btn edit-toolbar__{%s el.class %}"
@ -62,7 +65,7 @@
{% endif %} {% endif %}
</section> </section>
</aside> </aside>
<script src="/static/toolbar.js"></script> <script src="/assets/toolbar.js"></script>
{% endfunc %} {% endfunc %}
{% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %} {% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %}
@ -82,6 +85,7 @@
</main> </main>
{%s= Toolbar(user.FromRequest(rq)) %} {%s= Toolbar(user.FromRequest(rq)) %}
</div> </div>
{%= editScripts() %}
{% endfunc %} {% endfunc %}
{% func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) %} {% func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) %}
@ -103,4 +107,11 @@
</main> </main>
{%s= Toolbar(user.FromRequest(rq)) %} {%s= Toolbar(user.FromRequest(rq)) %}
</div> </div>
{%= editScripts() %}
{% endfunc %} {% endfunc %}
{% func editScripts() %}
{% for _, scriptPath := range cfg.EditScripts %}
<script src="{%s scriptPath %}"></script>
{% endfor %}
{% endfunc %}

View File

@ -7,34 +7,37 @@ package views
//line views/mutators.qtpl:1 //line views/mutators.qtpl:1
import "net/http" import "net/http"
//line views/mutators.qtpl:2 //line views/mutators.qtpl:3
import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/mutators.qtpl:4
import "github.com/bouncepaw/mycorrhiza/util" import "github.com/bouncepaw/mycorrhiza/util"
//line views/mutators.qtpl:3 //line views/mutators.qtpl:5
import "github.com/bouncepaw/mycorrhiza/user" import "github.com/bouncepaw/mycorrhiza/user"
//line views/mutators.qtpl:5 //line views/mutators.qtpl:7
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line views/mutators.qtpl:5 //line views/mutators.qtpl:7
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line views/mutators.qtpl:5 //line views/mutators.qtpl:7
func StreamToolbar(qw422016 *qt422016.Writer, u *user.User) { func StreamToolbar(qw422016 *qt422016.Writer, u *user.User) {
//line views/mutators.qtpl:5 //line views/mutators.qtpl:7
qw422016.N().S(` qw422016.N().S(`
<aside class="edit-toolbar layout-card"> <aside class="edit-toolbar layout-card">
<h2 class="edit-toolbar__title layout-card__title">Markup</h2> <h2 class="edit-toolbar__title layout-card__title">Markup</h2>
<section class="edit-toolbar__buttons"> <section class="edit-toolbar__buttons">
`) `)
//line views/mutators.qtpl:9 //line views/mutators.qtpl:11
for _, el := range []struct { for _, el := range []struct {
class string class string
onclick string onclick string
@ -47,7 +50,7 @@ func StreamToolbar(qw422016 *qt422016.Writer, u *user.User) {
{"italic", "wrapItalic()", "<i>//Italic//</i>"}, {"italic", "wrapItalic()", "<i>//Italic//</i>"},
{"highlighted", "wrapHighlighted()", "<mark>!!Highlight!!</mark>"}, {"highlighted", "wrapHighlighted()", "<mark>!!Highlight!!</mark>"},
{"monospace", "wrapMonospace()", "<code>`Monospace`</code>"}, {"monospace", "wrapMonospace()", "<code>`Monospace`</code>"},
{"lifted", "wrapLifted()", "<sup>^Lifted^</sup>"}, {"lifted", "wrapLifted()", "<sup>^^Lifted^^</sup>"},
{"lowered", "wrapLowered()", "<sub>,,Lowered,,</sub>"}, {"lowered", "wrapLowered()", "<sub>,,Lowered,,</sub>"},
{"strikethrough", "wrapStrikethrough()", "<strike>~~Strikethrough~~</strike>"}, {"strikethrough", "wrapStrikethrough()", "<strike>~~Strikethrough~~</strike>"},
{"rocket", "insertRocket()", "=> rocketlink"}, {"rocket", "insertRocket()", "=> rocketlink"},
@ -59,71 +62,72 @@ func StreamToolbar(qw422016 *qt422016.Writer, u *user.User) {
{"bulletedlist", "insertBulletedList()", "* bullet list"}, {"bulletedlist", "insertBulletedList()", "* bullet list"},
{"numberedlist", "insertNumberedList()", "*. number list"}, {"numberedlist", "insertNumberedList()", "*. number list"},
} { } {
//line views/mutators.qtpl:32 //line views/mutators.qtpl:34
qw422016.N().S(` qw422016.N().S(`
<button <button
class="edit-toolbar__btn edit-toolbar__`) class="edit-toolbar__btn edit-toolbar__`)
//line views/mutators.qtpl:34 //line views/mutators.qtpl:36
qw422016.E().S(el.class) qw422016.E().S(el.class)
//line views/mutators.qtpl:34 //line views/mutators.qtpl:36
qw422016.N().S(`" qw422016.N().S(`"
onclick="`) onclick="`)
//line views/mutators.qtpl:35 //line views/mutators.qtpl:37
qw422016.E().S(el.onclick) qw422016.E().S(el.onclick)
//line views/mutators.qtpl:35 //line views/mutators.qtpl:37
qw422016.N().S(`"> qw422016.N().S(`">
`) `)
//line views/mutators.qtpl:36 //line views/mutators.qtpl:38
qw422016.N().S(el.display) qw422016.N().S(el.display)
//line views/mutators.qtpl:36 //line views/mutators.qtpl:38
qw422016.N().S(` qw422016.N().S(`
</button> </button>
`) `)
//line views/mutators.qtpl:38 //line views/mutators.qtpl:40
} }
//line views/mutators.qtpl:38 //line views/mutators.qtpl:40
qw422016.N().S(` qw422016.N().S(`
</section> </section>
<p class="edit-toolbar__ad"><a href="https://mycorrhiza.lesarbr.es/hypha/mycomarkup" target="_blank">Learn more</a> about mycomarkup</p> <p class="edit-toolbar__ad"><a href="https://mycorrhiza.lesarbr.es/hypha/mycomarkup" target="_blank">Learn more</a> about mycomarkup</p>
<h2 class="edit-toolbar__title layout-card__title">Actions</h2> <h2 class="edit-toolbar__title layout-card__title">Actions</h2>
<section class="edit-toolbar__buttons"> <section class="edit-toolbar__buttons">
`) `)
//line views/mutators.qtpl:43 //line views/mutators.qtpl:45
for _, el := range []struct { for _, el := range []struct {
class string class string
onclick string onclick string
display string display string
}{ }{
{"date", "insertDate()", "Insert current date"}, {"date", "insertDate()", "Insert current date"},
{"time", "insertTimeUTC()", "Insert current time"},
} { } {
//line views/mutators.qtpl:49 //line views/mutators.qtpl:52
qw422016.N().S(` qw422016.N().S(`
<button <button
class="edit-toolbar__btn edit-toolbar__`) class="edit-toolbar__btn edit-toolbar__`)
//line views/mutators.qtpl:51 //line views/mutators.qtpl:54
qw422016.E().S(el.class) qw422016.E().S(el.class)
//line views/mutators.qtpl:51 //line views/mutators.qtpl:54
qw422016.N().S(`" qw422016.N().S(`"
onclick="`) onclick="`)
//line views/mutators.qtpl:52 //line views/mutators.qtpl:55
qw422016.E().S(el.onclick) qw422016.E().S(el.onclick)
//line views/mutators.qtpl:52 //line views/mutators.qtpl:55
qw422016.N().S(`"> qw422016.N().S(`">
`) `)
//line views/mutators.qtpl:53 //line views/mutators.qtpl:56
qw422016.N().S(el.display) qw422016.N().S(el.display)
//line views/mutators.qtpl:53 //line views/mutators.qtpl:56
qw422016.N().S(` qw422016.N().S(`
</button> </button>
`) `)
//line views/mutators.qtpl:55 //line views/mutators.qtpl:58
} }
//line views/mutators.qtpl:55 //line views/mutators.qtpl:58
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/mutators.qtpl:56 //line views/mutators.qtpl:59
if u.Group != "anon" { if u.Group != "anon" {
//line views/mutators.qtpl:56 //line views/mutators.qtpl:59
qw422016.N().S(` qw422016.N().S(`
<button <button
class="edit-toolbar__btn edit-toolbar__user-link" class="edit-toolbar__btn edit-toolbar__user-link"
@ -131,201 +135,260 @@ func StreamToolbar(qw422016 *qt422016.Writer, u *user.User) {
Link yourself Link yourself
</button> </button>
`) `)
//line views/mutators.qtpl:62 //line views/mutators.qtpl:65
} }
//line views/mutators.qtpl:62 //line views/mutators.qtpl:65
qw422016.N().S(` qw422016.N().S(`
</section> </section>
</aside> </aside>
<script src="/static/toolbar.js"></script> <script src="/assets/toolbar.js"></script>
`) `)
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
} }
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
func WriteToolbar(qq422016 qtio422016.Writer, u *user.User) { func WriteToolbar(qq422016 qtio422016.Writer, u *user.User) {
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
StreamToolbar(qw422016, u) StreamToolbar(qw422016, u)
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
} }
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
func Toolbar(u *user.User) string { func Toolbar(u *user.User) string {
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
WriteToolbar(qb422016, u) WriteToolbar(qb422016, u)
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
return qs422016 return qs422016
//line views/mutators.qtpl:66 //line views/mutators.qtpl:69
} }
//line views/mutators.qtpl:68 //line views/mutators.qtpl:71
func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) { func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
//line views/mutators.qtpl:68 //line views/mutators.qtpl:71
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/mutators.qtpl:69 //line views/mutators.qtpl:72
qw422016.N().S(NavHTML(rq, hyphaName, "edit")) qw422016.N().S(NavHTML(rq, hyphaName, "edit"))
//line views/mutators.qtpl:69 //line views/mutators.qtpl:72
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width edit edit_no-preview"> <main class="main-width edit edit_no-preview">
<h1 class="edit__title">Edit `) <h1 class="edit__title">Edit `)
//line views/mutators.qtpl:72 //line views/mutators.qtpl:75
qw422016.E().S(util.BeautifulName(hyphaName)) qw422016.E().S(util.BeautifulName(hyphaName))
//line views/mutators.qtpl:72 //line views/mutators.qtpl:75
qw422016.N().S(`</h1> qw422016.N().S(`</h1>
`) `)
//line views/mutators.qtpl:73 //line views/mutators.qtpl:76
qw422016.N().S(warning) qw422016.N().S(warning)
//line views/mutators.qtpl:73 //line views/mutators.qtpl:76
qw422016.N().S(` qw422016.N().S(`
<form method="post" class="edit-form" <form method="post" class="edit-form"
action="/upload-text/`) action="/upload-text/`)
//line views/mutators.qtpl:75 //line views/mutators.qtpl:78
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line views/mutators.qtpl:75 //line views/mutators.qtpl:78
qw422016.N().S(`"> qw422016.N().S(`">
<textarea name="text" class="edit-form__textarea">`) <textarea name="text" class="edit-form__textarea">`)
//line views/mutators.qtpl:76 //line views/mutators.qtpl:79
qw422016.E().S(textAreaFill) qw422016.E().S(textAreaFill)
//line views/mutators.qtpl:76 //line views/mutators.qtpl:79
qw422016.N().S(`</textarea> qw422016.N().S(`</textarea>
<br/> <br/>
<input type="submit" name="action" value="Save" class="edit-form__save"/> <input type="submit" name="action" value="Save" class="edit-form__save"/>
<input type="submit" name="action" value="Preview" class="edit-form__preview"> <input type="submit" name="action" value="Preview" class="edit-form__preview">
<a href="/page/`) <a href="/page/`)
//line views/mutators.qtpl:80 //line views/mutators.qtpl:83
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line views/mutators.qtpl:80 //line views/mutators.qtpl:83
qw422016.N().S(`" class="edit-form__cancel">Cancel</a> qw422016.N().S(`" class="edit-form__cancel">Cancel</a>
</form> </form>
</main> </main>
`) `)
//line views/mutators.qtpl:83 //line views/mutators.qtpl:86
qw422016.N().S(Toolbar(user.FromRequest(rq))) qw422016.N().S(Toolbar(user.FromRequest(rq)))
//line views/mutators.qtpl:83 //line views/mutators.qtpl:86
qw422016.N().S(` qw422016.N().S(`
</div> </div>
`) `)
//line views/mutators.qtpl:85 //line views/mutators.qtpl:88
} streameditScripts(qw422016)
//line views/mutators.qtpl:88
//line views/mutators.qtpl:85
func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
//line views/mutators.qtpl:85
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/mutators.qtpl:85
StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
//line views/mutators.qtpl:85
qt422016.ReleaseWriter(qw422016)
//line views/mutators.qtpl:85
}
//line views/mutators.qtpl:85
func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string {
//line views/mutators.qtpl:85
qb422016 := qt422016.AcquireByteBuffer()
//line views/mutators.qtpl:85
WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
//line views/mutators.qtpl:85
qs422016 := string(qb422016.B)
//line views/mutators.qtpl:85
qt422016.ReleaseByteBuffer(qb422016)
//line views/mutators.qtpl:85
return qs422016
//line views/mutators.qtpl:85
}
//line views/mutators.qtpl:87
func StreamPreviewHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) {
//line views/mutators.qtpl:87
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/mutators.qtpl:88 //line views/mutators.qtpl:89
}
//line views/mutators.qtpl:89
func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
//line views/mutators.qtpl:89
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/mutators.qtpl:89
StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
//line views/mutators.qtpl:89
qt422016.ReleaseWriter(qw422016)
//line views/mutators.qtpl:89
}
//line views/mutators.qtpl:89
func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string {
//line views/mutators.qtpl:89
qb422016 := qt422016.AcquireByteBuffer()
//line views/mutators.qtpl:89
WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
//line views/mutators.qtpl:89
qs422016 := string(qb422016.B)
//line views/mutators.qtpl:89
qt422016.ReleaseByteBuffer(qb422016)
//line views/mutators.qtpl:89
return qs422016
//line views/mutators.qtpl:89
}
//line views/mutators.qtpl:91
func StreamPreviewHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) {
//line views/mutators.qtpl:91
qw422016.N().S(`
`)
//line views/mutators.qtpl:92
qw422016.N().S(NavHTML(rq, hyphaName, "edit")) qw422016.N().S(NavHTML(rq, hyphaName, "edit"))
//line views/mutators.qtpl:88 //line views/mutators.qtpl:92
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width edit edit_with-preview"> <main class="main-width edit edit_with-preview">
<h1>Edit `) <h1>Edit `)
//line views/mutators.qtpl:91 //line views/mutators.qtpl:95
qw422016.E().S(util.BeautifulName(hyphaName)) qw422016.E().S(util.BeautifulName(hyphaName))
//line views/mutators.qtpl:91 //line views/mutators.qtpl:95
qw422016.N().S(` (preview)</h1> qw422016.N().S(` (preview)</h1>
`) `)
//line views/mutators.qtpl:92 //line views/mutators.qtpl:96
qw422016.N().S(warning) qw422016.N().S(warning)
//line views/mutators.qtpl:92 //line views/mutators.qtpl:96
qw422016.N().S(` qw422016.N().S(`
<form method="post" class="edit-form" <form method="post" class="edit-form"
action="/upload-text/`) action="/upload-text/`)
//line views/mutators.qtpl:94 //line views/mutators.qtpl:98
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line views/mutators.qtpl:94 //line views/mutators.qtpl:98
qw422016.N().S(`"> qw422016.N().S(`">
<textarea class="edit-form__textarea" name="text">`) <textarea class="edit-form__textarea" name="text">`)
//line views/mutators.qtpl:95 //line views/mutators.qtpl:99
qw422016.E().S(textAreaFill) qw422016.E().S(textAreaFill)
//line views/mutators.qtpl:95 //line views/mutators.qtpl:99
qw422016.N().S(`</textarea> qw422016.N().S(`</textarea>
<br/> <br/>
<input type="submit" name="action" value="Save" class="edit-form__save"/> <input type="submit" name="action" value="Save" class="edit-form__save"/>
<input type="submit" name="action" value="Preview" class="edit-form__preview"> <input type="submit" name="action" value="Preview" class="edit-form__preview">
<a href="/page/`) <a href="/page/`)
//line views/mutators.qtpl:99 //line views/mutators.qtpl:103
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line views/mutators.qtpl:99 //line views/mutators.qtpl:103
qw422016.N().S(`" class="edit-form__cancel">Cancel</a> qw422016.N().S(`" class="edit-form__cancel">Cancel</a>
</form> </form>
<p class="warning">Note that the hypha is not saved yet. You can preview the changes </p> <p class="warning">Note that the hypha is not saved yet. You can preview the changes </p>
<article class="edit__preview">`) <article class="edit__preview">`)
//line views/mutators.qtpl:102 //line views/mutators.qtpl:106
qw422016.N().S(renderedPage) qw422016.N().S(renderedPage)
//line views/mutators.qtpl:102 //line views/mutators.qtpl:106
qw422016.N().S(`</article> qw422016.N().S(`</article>
</main> </main>
`) `)
//line views/mutators.qtpl:104 //line views/mutators.qtpl:108
qw422016.N().S(Toolbar(user.FromRequest(rq))) qw422016.N().S(Toolbar(user.FromRequest(rq)))
//line views/mutators.qtpl:104 //line views/mutators.qtpl:108
qw422016.N().S(` qw422016.N().S(`
</div> </div>
`) `)
//line views/mutators.qtpl:106 //line views/mutators.qtpl:110
streameditScripts(qw422016)
//line views/mutators.qtpl:110
qw422016.N().S(`
`)
//line views/mutators.qtpl:111
} }
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
func WritePreviewHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) { func WritePreviewHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) {
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
StreamPreviewHTML(qw422016, rq, hyphaName, textAreaFill, warning, renderedPage) StreamPreviewHTML(qw422016, rq, hyphaName, textAreaFill, warning, renderedPage)
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
} }
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) string { func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) string {
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
WritePreviewHTML(qb422016, rq, hyphaName, textAreaFill, warning, renderedPage) WritePreviewHTML(qb422016, rq, hyphaName, textAreaFill, warning, renderedPage)
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
return qs422016 return qs422016
//line views/mutators.qtpl:106 //line views/mutators.qtpl:111
}
//line views/mutators.qtpl:113
func streameditScripts(qw422016 *qt422016.Writer) {
//line views/mutators.qtpl:113
qw422016.N().S(`
`)
//line views/mutators.qtpl:114
for _, scriptPath := range cfg.EditScripts {
//line views/mutators.qtpl:114
qw422016.N().S(`
<script src="`)
//line views/mutators.qtpl:115
qw422016.E().S(scriptPath)
//line views/mutators.qtpl:115
qw422016.N().S(`"></script>
`)
//line views/mutators.qtpl:116
}
//line views/mutators.qtpl:116
qw422016.N().S(`
`)
//line views/mutators.qtpl:117
}
//line views/mutators.qtpl:117
func writeeditScripts(qq422016 qtio422016.Writer) {
//line views/mutators.qtpl:117
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/mutators.qtpl:117
streameditScripts(qw422016)
//line views/mutators.qtpl:117
qt422016.ReleaseWriter(qw422016)
//line views/mutators.qtpl:117
}
//line views/mutators.qtpl:117
func editScripts() string {
//line views/mutators.qtpl:117
qb422016 := qt422016.AcquireByteBuffer()
//line views/mutators.qtpl:117
writeeditScripts(qb422016)
//line views/mutators.qtpl:117
qs422016 := string(qb422016.B)
//line views/mutators.qtpl:117
qt422016.ReleaseByteBuffer(qb422016)
//line views/mutators.qtpl:117
return qs422016
//line views/mutators.qtpl:117
} }

View File

@ -1,5 +1,6 @@
{% import "net/http" %} {% import "net/http" %}
{% import "strings" %} {% import "strings" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %} {% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %} {% import "github.com/bouncepaw/mycorrhiza/util" %}
@ -48,13 +49,18 @@ var navEntries = []navEntry{
{% func UserMenuHTML(u *user.User) %} {% func UserMenuHTML(u *user.User) %}
{% if user.AuthUsed %} {% if user.AuthUsed %}
<li class="header-links__entry header-links__entry_user"> <li class="header-links__entry header-links__entry_user">
{% if u.Group == "anon" %} {% if u.Group == "anon" %}
<a href="/login" class="header-links__link">Login</a> <a href="/login" class="header-links__link">Login</a>
{% else %} {% else %}
<a href="/hypha/{%s util.UserHypha %}/{%s u.Name %}" class="header-links__link">{%s u.Name %}</a> <a href="/hypha/{%s cfg.UserHypha %}/{%s u.Name %}" class="header-links__link">{%s util.BeautifulName(u.Name) %}</a>
{% endif %} {% endif %}
</li> </li>
{% endif %}
{% if user.AuthUsed && cfg.UseRegistration && u.Group == "anon" %}
<li class="header-links__entry header-links__entry_register">
<a href="/register" class="header-links__link">Register</a>
</li>
{% endif %} {% endif %}
{% endfunc %} {% endfunc %}

View File

@ -11,27 +11,30 @@ import "net/http"
import "strings" import "strings"
//line views/nav.qtpl:3 //line views/nav.qtpl:3
import "github.com/bouncepaw/mycorrhiza/user" import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/nav.qtpl:4 //line views/nav.qtpl:4
import "github.com/bouncepaw/mycorrhiza/user"
//line views/nav.qtpl:5
import "github.com/bouncepaw/mycorrhiza/util" import "github.com/bouncepaw/mycorrhiza/util"
// This is the <nav> seen on top of many pages. // This is the <nav> seen on top of many pages.
//line views/nav.qtpl:7 //line views/nav.qtpl:8
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line views/nav.qtpl:7 //line views/nav.qtpl:8
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line views/nav.qtpl:8 //line views/nav.qtpl:9
type navEntry struct { type navEntry struct {
path string path string
title string title string
@ -48,268 +51,281 @@ var navEntries = []navEntry{
{"text", "Raw text"}, {"text", "Raw text"},
} }
//line views/nav.qtpl:24 //line views/nav.qtpl:25
func StreamNavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) { func StreamNavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line views/nav.qtpl:24 //line views/nav.qtpl:25
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/nav.qtpl:26 //line views/nav.qtpl:27
u := user.FromRequest(rq) u := user.FromRequest(rq)
//line views/nav.qtpl:27 //line views/nav.qtpl:28
qw422016.N().S(` qw422016.N().S(`
<nav class="hypha-tabs main-width"> <nav class="hypha-tabs main-width">
<ul class="hypha-tabs__flex"> <ul class="hypha-tabs__flex">
`) `)
//line views/nav.qtpl:30 //line views/nav.qtpl:31
for _, entry := range navEntries { for _, entry := range navEntries {
//line views/nav.qtpl:31 //line views/nav.qtpl:32
if navType == "revision" && entry.path == "revision" { if navType == "revision" && entry.path == "revision" {
//line views/nav.qtpl:31 //line views/nav.qtpl:32
qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active"> qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active">
<span class="hypha-tabs__selection">`) <span class="hypha-tabs__selection">`)
//line views/nav.qtpl:33 //line views/nav.qtpl:34
qw422016.E().S(revisionHash[0]) qw422016.E().S(revisionHash[0])
//line views/nav.qtpl:33 //line views/nav.qtpl:34
qw422016.N().S(`</span> qw422016.N().S(`</span>
</li> </li>
`) `)
//line views/nav.qtpl:35 //line views/nav.qtpl:36
} else if navType == entry.path { } else if navType == entry.path {
//line views/nav.qtpl:35 //line views/nav.qtpl:36
qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active"> qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active">
<span class="hypha-tabs__selection">`) <span class="hypha-tabs__selection">`)
//line views/nav.qtpl:37 //line views/nav.qtpl:38
qw422016.E().S(entry.title) qw422016.E().S(entry.title)
//line views/nav.qtpl:37 //line views/nav.qtpl:38
qw422016.N().S(`</span> qw422016.N().S(`</span>
</li> </li>
`) `)
//line views/nav.qtpl:39 //line views/nav.qtpl:40
} else if entry.path != "revision" && u.CanProceed(entry.path) { } else if entry.path != "revision" && u.CanProceed(entry.path) {
//line views/nav.qtpl:39 //line views/nav.qtpl:40
qw422016.N().S(` <li class="hypha-tabs__tab"> qw422016.N().S(` <li class="hypha-tabs__tab">
<a class="hypha-tabs__link" href="/`) <a class="hypha-tabs__link" href="/`)
//line views/nav.qtpl:41 //line views/nav.qtpl:42
qw422016.E().S(entry.path) qw422016.E().S(entry.path)
//line views/nav.qtpl:41 //line views/nav.qtpl:42
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line views/nav.qtpl:41 //line views/nav.qtpl:42
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line views/nav.qtpl:41 //line views/nav.qtpl:42
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/nav.qtpl:41 //line views/nav.qtpl:42
qw422016.E().S(entry.title) qw422016.E().S(entry.title)
//line views/nav.qtpl:41 //line views/nav.qtpl:42
qw422016.N().S(`</a> qw422016.N().S(`</a>
</li> </li>
`) `)
//line views/nav.qtpl:43 //line views/nav.qtpl:44
} }
//line views/nav.qtpl:44 //line views/nav.qtpl:45
} }
//line views/nav.qtpl:44 //line views/nav.qtpl:45
qw422016.N().S(` </ul> qw422016.N().S(` </ul>
</nav> </nav>
`) `)
//line views/nav.qtpl:47 //line views/nav.qtpl:48
} }
//line views/nav.qtpl:47 //line views/nav.qtpl:48
func WriteNavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) { func WriteNavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line views/nav.qtpl:47 //line views/nav.qtpl:48
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:47 //line views/nav.qtpl:48
StreamNavHTML(qw422016, rq, hyphaName, navType, revisionHash...) StreamNavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
//line views/nav.qtpl:47 //line views/nav.qtpl:48
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:47 //line views/nav.qtpl:48
} }
//line views/nav.qtpl:47 //line views/nav.qtpl:48
func NavHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string { func NavHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
//line views/nav.qtpl:47 //line views/nav.qtpl:48
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:47 //line views/nav.qtpl:48
WriteNavHTML(qb422016, rq, hyphaName, navType, revisionHash...) WriteNavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
//line views/nav.qtpl:47 //line views/nav.qtpl:48
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/nav.qtpl:47 //line views/nav.qtpl:48
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:47 //line views/nav.qtpl:48
return qs422016 return qs422016
//line views/nav.qtpl:47 //line views/nav.qtpl:48
} }
//line views/nav.qtpl:49 //line views/nav.qtpl:50
func StreamUserMenuHTML(qw422016 *qt422016.Writer, u *user.User) { func StreamUserMenuHTML(qw422016 *qt422016.Writer, u *user.User) {
//line views/nav.qtpl:49 //line views/nav.qtpl:50
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/nav.qtpl:50 //line views/nav.qtpl:51
if user.AuthUsed { if user.AuthUsed {
//line views/nav.qtpl:50 //line views/nav.qtpl:51
qw422016.N().S(` qw422016.N().S(`
<li class="header-links__entry header-links__entry_user"> <li class="header-links__entry header-links__entry_user">
`) `)
//line views/nav.qtpl:52 //line views/nav.qtpl:53
if u.Group == "anon" { if u.Group == "anon" {
//line views/nav.qtpl:52 //line views/nav.qtpl:53
qw422016.N().S(` qw422016.N().S(`
<a href="/login" class="header-links__link">Login</a> <a href="/login" class="header-links__link">Login</a>
`) `)
//line views/nav.qtpl:54 //line views/nav.qtpl:55
} else { } else {
//line views/nav.qtpl:54 //line views/nav.qtpl:55
qw422016.N().S(` qw422016.N().S(`
<a href="/hypha/`) <a href="/hypha/`)
//line views/nav.qtpl:55 //line views/nav.qtpl:56
qw422016.E().S(util.UserHypha) qw422016.E().S(cfg.UserHypha)
//line views/nav.qtpl:55 //line views/nav.qtpl:56
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line views/nav.qtpl:55 //line views/nav.qtpl:56
qw422016.E().S(u.Name) qw422016.E().S(u.Name)
//line views/nav.qtpl:55 //line views/nav.qtpl:56
qw422016.N().S(`" class="header-links__link">`) qw422016.N().S(`" class="header-links__link">`)
//line views/nav.qtpl:55 //line views/nav.qtpl:56
qw422016.E().S(u.Name) qw422016.E().S(util.BeautifulName(u.Name))
//line views/nav.qtpl:55 //line views/nav.qtpl:56
qw422016.N().S(`</a> qw422016.N().S(`</a>
`) `)
//line views/nav.qtpl:56 //line views/nav.qtpl:57
} }
//line views/nav.qtpl:56 //line views/nav.qtpl:57
qw422016.N().S(` qw422016.N().S(`
</li> </li>
`) `)
//line views/nav.qtpl:58 //line views/nav.qtpl:59
} }
//line views/nav.qtpl:58 //line views/nav.qtpl:59
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/nav.qtpl:59 //line views/nav.qtpl:60
if user.AuthUsed && cfg.UseRegistration && u.Group == "anon" {
//line views/nav.qtpl:60
qw422016.N().S(`
<li class="header-links__entry header-links__entry_register">
<a href="/register" class="header-links__link">Register</a>
</li>
`)
//line views/nav.qtpl:64
}
//line views/nav.qtpl:64
qw422016.N().S(`
`)
//line views/nav.qtpl:65
} }
//line views/nav.qtpl:59 //line views/nav.qtpl:65
func WriteUserMenuHTML(qq422016 qtio422016.Writer, u *user.User) { func WriteUserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
//line views/nav.qtpl:59 //line views/nav.qtpl:65
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:59 //line views/nav.qtpl:65
StreamUserMenuHTML(qw422016, u) StreamUserMenuHTML(qw422016, u)
//line views/nav.qtpl:59 //line views/nav.qtpl:65
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:59 //line views/nav.qtpl:65
} }
//line views/nav.qtpl:59 //line views/nav.qtpl:65
func UserMenuHTML(u *user.User) string { func UserMenuHTML(u *user.User) string {
//line views/nav.qtpl:59 //line views/nav.qtpl:65
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:59 //line views/nav.qtpl:65
WriteUserMenuHTML(qb422016, u) WriteUserMenuHTML(qb422016, u)
//line views/nav.qtpl:59 //line views/nav.qtpl:65
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/nav.qtpl:59 //line views/nav.qtpl:65
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:59 //line views/nav.qtpl:65
return qs422016 return qs422016
//line views/nav.qtpl:59 //line views/nav.qtpl:65
} }
//line views/nav.qtpl:61 //line views/nav.qtpl:67
func StreamRelativeHyphaeHTML(qw422016 *qt422016.Writer, relatives string) { func StreamRelativeHyphaeHTML(qw422016 *qt422016.Writer, relatives string) {
//line views/nav.qtpl:61 //line views/nav.qtpl:67
qw422016.N().S(` qw422016.N().S(`
<aside class="relative-hyphae layout-card"> <aside class="relative-hyphae layout-card">
<h2 class="relative-hyphae__title layout-card__title">Relative hyphae</h2> <h2 class="relative-hyphae__title layout-card__title">Relative hyphae</h2>
`) `)
//line views/nav.qtpl:64 //line views/nav.qtpl:70
qw422016.N().S(relatives) qw422016.N().S(relatives)
//line views/nav.qtpl:64 //line views/nav.qtpl:70
qw422016.N().S(` qw422016.N().S(`
</aside> </aside>
`) `)
//line views/nav.qtpl:66 //line views/nav.qtpl:72
} }
//line views/nav.qtpl:66 //line views/nav.qtpl:72
func WriteRelativeHyphaeHTML(qq422016 qtio422016.Writer, relatives string) { func WriteRelativeHyphaeHTML(qq422016 qtio422016.Writer, relatives string) {
//line views/nav.qtpl:66 //line views/nav.qtpl:72
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:66 //line views/nav.qtpl:72
StreamRelativeHyphaeHTML(qw422016, relatives) StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/nav.qtpl:66 //line views/nav.qtpl:72
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:66 //line views/nav.qtpl:72
} }
//line views/nav.qtpl:66 //line views/nav.qtpl:72
func RelativeHyphaeHTML(relatives string) string { func RelativeHyphaeHTML(relatives string) string {
//line views/nav.qtpl:66 //line views/nav.qtpl:72
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:66 //line views/nav.qtpl:72
WriteRelativeHyphaeHTML(qb422016, relatives) WriteRelativeHyphaeHTML(qb422016, relatives)
//line views/nav.qtpl:66 //line views/nav.qtpl:72
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/nav.qtpl:66 //line views/nav.qtpl:72
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:66 //line views/nav.qtpl:72
return qs422016 return qs422016
//line views/nav.qtpl:66 //line views/nav.qtpl:72
} }
//line views/nav.qtpl:68 //line views/nav.qtpl:74
func StreamSubhyphaeHTML(qw422016 *qt422016.Writer, subhyphae string) { func StreamSubhyphaeHTML(qw422016 *qt422016.Writer, subhyphae string) {
//line views/nav.qtpl:68 //line views/nav.qtpl:74
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/nav.qtpl:69 //line views/nav.qtpl:75
if strings.TrimSpace(subhyphae) != "" { if strings.TrimSpace(subhyphae) != "" {
//line views/nav.qtpl:69 //line views/nav.qtpl:75
qw422016.N().S(` qw422016.N().S(`
<section class="subhyphae"> <section class="subhyphae">
<h2 class="subhyphae__title">Subhyphae</h2> <h2 class="subhyphae__title">Subhyphae</h2>
<nav class="subhyphae__nav"> <nav class="subhyphae__nav">
<ul class="subhyphae__list"> <ul class="subhyphae__list">
`) `)
//line views/nav.qtpl:74 //line views/nav.qtpl:80
qw422016.N().S(subhyphae) qw422016.N().S(subhyphae)
//line views/nav.qtpl:74 //line views/nav.qtpl:80
qw422016.N().S(` qw422016.N().S(`
</ul> </ul>
</nav> </nav>
</section> </section>
`) `)
//line views/nav.qtpl:78 //line views/nav.qtpl:84
} }
//line views/nav.qtpl:78 //line views/nav.qtpl:84
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/nav.qtpl:79 //line views/nav.qtpl:85
} }
//line views/nav.qtpl:79 //line views/nav.qtpl:85
func WriteSubhyphaeHTML(qq422016 qtio422016.Writer, subhyphae string) { func WriteSubhyphaeHTML(qq422016 qtio422016.Writer, subhyphae string) {
//line views/nav.qtpl:79 //line views/nav.qtpl:85
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:79 //line views/nav.qtpl:85
StreamSubhyphaeHTML(qw422016, subhyphae) StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/nav.qtpl:79 //line views/nav.qtpl:85
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:79 //line views/nav.qtpl:85
} }
//line views/nav.qtpl:79 //line views/nav.qtpl:85
func SubhyphaeHTML(subhyphae string) string { func SubhyphaeHTML(subhyphae string) string {
//line views/nav.qtpl:79 //line views/nav.qtpl:85
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:79 //line views/nav.qtpl:85
WriteSubhyphaeHTML(qb422016, subhyphae) WriteSubhyphaeHTML(qb422016, subhyphae)
//line views/nav.qtpl:79 //line views/nav.qtpl:85
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/nav.qtpl:79 //line views/nav.qtpl:85
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:79 //line views/nav.qtpl:85
return qs422016 return qs422016
//line views/nav.qtpl:79 //line views/nav.qtpl:85
} }

View File

@ -3,6 +3,7 @@
{% import "path" %} {% import "path" %}
{% import "os" %} {% import "os" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} {% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/mimetype" %} {% import "github.com/bouncepaw/mycorrhiza/mimetype" %}
{% import "github.com/bouncepaw/mycorrhiza/tree" %} {% import "github.com/bouncepaw/mycorrhiza/tree" %}
@ -46,12 +47,14 @@
{% if u.CanProceed("upload-binary") %} {% if u.CanProceed("upload-binary") %}
<form action="/upload-binary/{%s h.Name %}" <form action="/upload-binary/{%s h.Name %}"
method="post" enctype="multipart/form-data" method="post" enctype="multipart/form-data"
class="modal amnt-menu-block"> class="upload-binary modal amnt-menu-block">
<fieldset class="modal__fieldset upload-binary"> <fieldset class="modal__fieldset">
<legend class="modal__title modal__title_small">Attach</legend> <legend class="modal__title modal__title_small">Attach</legend>
<p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p> <p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p>
<input type="file" class="upload-binary__input" name="binary"> <label for="upload-binary__input"></label>
<input type="submit" class="modal__action modal__submit"> <input type="file" id="upload-binary__input" name="binary">
<input type="submit" class="btn stick-to-bottom" value="Upload">
</fieldset> </fieldset>
</form> </form>
{% endif %} {% endif %}
@ -61,7 +64,7 @@
<fieldset class="modal__fieldset"> <fieldset class="modal__fieldset">
<legend class="modal__title modal__title_small">Unattach</legend> <legend class="modal__title modal__title_small">Unattach</legend>
<p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p> <p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p>
<input type="submit" class="modal__action modal__submit"> <input type="submit" class="btn" value="Unattach">
</fieldset> </fieldset>
</form> </form>
{% endif %} {% endif %}
@ -75,26 +78,17 @@ If `contents` == "", a helpful message is shown instead.
{% func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) %} {% func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) %}
{% code {% code
relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name) relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name)
u := user.FromRequest(rq)
%} %}
{%= NavHTML(rq, h.Name, "page") %} {%= NavHTML(rq, h.Name, "page") %}
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<article> <article>
{%s= NaviTitleHTML(h) %} {%s= NaviTitleHTML(h) %}
{% if contents == "" %} {% if h.Exists %}
<p>This hypha has no text. Why not <a href="/edit/{%s h.Name %}">create it</a>?</p>
{% if u := user.FromRequest(rq); (!user.AuthUsed || u.Group != "anon") && !h.Exists %}
<form action="/upload-binary/{%s h.Name %}"
method="post" enctype="multipart/form-data"
class="upload-binary">
<label for="upload-binary__input">Upload an attachment:</label>
<input type="file" id="upload-binary__input" name="binary">
<input type="submit">
</form>
<br>
{% endif %}
{% else %}
{%s= contents %} {%s= contents %}
{% else %}
{%= nonExistentHyphaNotice(h, u) %}
{% endif %} {% endif %}
</article> </article>
<section class="prevnext"> <section class="prevnext">
@ -109,6 +103,7 @@ If `contents` == "", a helpful message is shown instead.
</main> </main>
{%= RelativeHyphaeHTML(relatives) %} {%= RelativeHyphaeHTML(relatives) %}
</div> </div>
{%= viewScripts() %}
{% endfunc %} {% endfunc %}
{% func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) %} {% func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) %}
@ -127,4 +122,11 @@ If `contents` == "", a helpful message is shown instead.
</main> </main>
{%= RelativeHyphaeHTML(relatives) %} {%= RelativeHyphaeHTML(relatives) %}
</div> </div>
{%= viewScripts() %}
{% endfunc %} {% endfunc %}
{% func viewScripts() %}
{% for _, scriptPath := range cfg.ViewScripts %}
<script src="{%s scriptPath %}"></script>
{% endfor %}
{% endfunc %}

View File

@ -17,433 +17,476 @@ import "path"
import "os" import "os"
//line views/readers.qtpl:6 //line views/readers.qtpl:6
import "github.com/bouncepaw/mycorrhiza/hyphae" import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/readers.qtpl:7 //line views/readers.qtpl:7
import "github.com/bouncepaw/mycorrhiza/mimetype" import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/readers.qtpl:8 //line views/readers.qtpl:8
import "github.com/bouncepaw/mycorrhiza/tree" import "github.com/bouncepaw/mycorrhiza/mimetype"
//line views/readers.qtpl:9 //line views/readers.qtpl:9
import "github.com/bouncepaw/mycorrhiza/user" import "github.com/bouncepaw/mycorrhiza/tree"
//line views/readers.qtpl:10 //line views/readers.qtpl:10
import "github.com/bouncepaw/mycorrhiza/user"
//line views/readers.qtpl:11
import "github.com/bouncepaw/mycorrhiza/util" import "github.com/bouncepaw/mycorrhiza/util"
//line views/readers.qtpl:12 //line views/readers.qtpl:13
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line views/readers.qtpl:12 //line views/readers.qtpl:13
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line views/readers.qtpl:12 //line views/readers.qtpl:13
func StreamAttachmentMenuHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) { func StreamAttachmentMenuHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) {
//line views/readers.qtpl:12 //line views/readers.qtpl:13
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:13 //line views/readers.qtpl:14
StreamNavHTML(qw422016, rq, h.Name, "attachment") StreamNavHTML(qw422016, rq, h.Name, "attachment")
//line views/readers.qtpl:13 //line views/readers.qtpl:14
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<h1>Attachment of `) <h1>Attachment of `)
//line views/readers.qtpl:16 //line views/readers.qtpl:17
qw422016.E().S(util.BeautifulName(h.Name)) qw422016.E().S(util.BeautifulName(h.Name))
//line views/readers.qtpl:16 //line views/readers.qtpl:17
qw422016.N().S(`</h1> qw422016.N().S(`</h1>
`) `)
//line views/readers.qtpl:17 //line views/readers.qtpl:18
if h.BinaryPath == "" { if h.BinaryPath == "" {
//line views/readers.qtpl:17 //line views/readers.qtpl:18
qw422016.N().S(` qw422016.N().S(`
<p class="warning">This hypha has no attachment, you can upload it here.</p> <p class="warning">This hypha has no attachment, you can upload it here.</p>
`) `)
//line views/readers.qtpl:19 //line views/readers.qtpl:20
} else { } else {
//line views/readers.qtpl:19 //line views/readers.qtpl:20
qw422016.N().S(` qw422016.N().S(`
<p class="warning">You can manage the hypha's attachment on this page.</p> <p class="warning">You can manage the hypha's attachment on this page.</p>
`) `)
//line views/readers.qtpl:21 //line views/readers.qtpl:22
} }
//line views/readers.qtpl:21 //line views/readers.qtpl:22
qw422016.N().S(` qw422016.N().S(`
<section class="amnt-grid"> <section class="amnt-grid">
`) `)
//line views/readers.qtpl:25 //line views/readers.qtpl:26
if h.BinaryPath != "" { if h.BinaryPath != "" {
//line views/readers.qtpl:25 //line views/readers.qtpl:26
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:27 //line views/readers.qtpl:28
mime := mimetype.FromExtension(path.Ext(h.BinaryPath)) mime := mimetype.FromExtension(path.Ext(h.BinaryPath))
fileinfo, err := os.Stat(h.BinaryPath) fileinfo, err := os.Stat(h.BinaryPath)
//line views/readers.qtpl:28 //line views/readers.qtpl:29
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:29 //line views/readers.qtpl:30
if err == nil { if err == nil {
//line views/readers.qtpl:29 //line views/readers.qtpl:30
qw422016.N().S(` qw422016.N().S(`
<fieldset class="amnt-menu-block"> <fieldset class="amnt-menu-block">
<legend class="modal__title modal__title_small">Stat</legend> <legend class="modal__title modal__title_small">Stat</legend>
<p class="modal__confirmation-msg"><b>File size:</b> `) <p class="modal__confirmation-msg"><b>File size:</b> `)
//line views/readers.qtpl:32 //line views/readers.qtpl:33
qw422016.N().DL(fileinfo.Size()) qw422016.N().DL(fileinfo.Size())
//line views/readers.qtpl:32 //line views/readers.qtpl:33
qw422016.N().S(` bytes</p> qw422016.N().S(` bytes</p>
<p><b>MIME type:</b> `) <p><b>MIME type:</b> `)
//line views/readers.qtpl:33 //line views/readers.qtpl:34
qw422016.E().S(mime) qw422016.E().S(mime)
//line views/readers.qtpl:33 //line views/readers.qtpl:34
qw422016.N().S(`</p> qw422016.N().S(`</p>
</fieldset> </fieldset>
`) `)
//line views/readers.qtpl:35 //line views/readers.qtpl:36
} }
//line views/readers.qtpl:35 //line views/readers.qtpl:36
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:37 //line views/readers.qtpl:38
if strings.HasPrefix(mime, "image/") { if strings.HasPrefix(mime, "image/") {
//line views/readers.qtpl:37 //line views/readers.qtpl:38
qw422016.N().S(` qw422016.N().S(`
<fieldset class="amnt-menu-block"> <fieldset class="amnt-menu-block">
<legend class="modal__title modal__title_small">Include</legend> <legend class="modal__title modal__title_small">Include</legend>
<p class="modal__confirmation-msg">This attachment is an image. To include it n a hypha, use a syntax like this:</p> <p class="modal__confirmation-msg">This attachment is an image. To include it n a hypha, use a syntax like this:</p>
<pre class="codebleck"><code>img { `) <pre class="codebleck"><code>img { `)
//line views/readers.qtpl:41 //line views/readers.qtpl:42
qw422016.E().S(h.Name) qw422016.E().S(h.Name)
//line views/readers.qtpl:41 //line views/readers.qtpl:42
qw422016.N().S(` }</code></pre> qw422016.N().S(` }</code></pre>
</fieldset> </fieldset>
`) `)
//line views/readers.qtpl:43 //line views/readers.qtpl:44
} }
//line views/readers.qtpl:43 //line views/readers.qtpl:44
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:44 //line views/readers.qtpl:45
} }
//line views/readers.qtpl:44 //line views/readers.qtpl:45
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:46 //line views/readers.qtpl:47
if u.CanProceed("upload-binary") { if u.CanProceed("upload-binary") {
//line views/readers.qtpl:46 //line views/readers.qtpl:47
qw422016.N().S(` qw422016.N().S(`
<form action="/upload-binary/`) <form action="/upload-binary/`)
//line views/readers.qtpl:47 //line views/readers.qtpl:48
qw422016.E().S(h.Name) qw422016.E().S(h.Name)
//line views/readers.qtpl:47 //line views/readers.qtpl:48
qw422016.N().S(`" qw422016.N().S(`"
method="post" enctype="multipart/form-data" method="post" enctype="multipart/form-data"
class="modal amnt-menu-block"> class="upload-binary modal amnt-menu-block">
<fieldset class="modal__fieldset upload-binary"> <fieldset class="modal__fieldset">
<legend class="modal__title modal__title_small">Attach</legend> <legend class="modal__title modal__title_small">Attach</legend>
<p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p> <p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p>
<input type="file" class="upload-binary__input" name="binary"> <label for="upload-binary__input"></label>
<input type="submit" class="modal__action modal__submit"> <input type="file" id="upload-binary__input" name="binary">
<input type="submit" class="btn stick-to-bottom" value="Upload">
</fieldset> </fieldset>
</form> </form>
`) `)
//line views/readers.qtpl:57 //line views/readers.qtpl:60
} }
//line views/readers.qtpl:57 //line views/readers.qtpl:60
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:59 //line views/readers.qtpl:62
if h.BinaryPath != "" && u.CanProceed("unattach-confirm") { if h.BinaryPath != "" && u.CanProceed("unattach-confirm") {
//line views/readers.qtpl:59 //line views/readers.qtpl:62
qw422016.N().S(` qw422016.N().S(`
<form action="/unattach-confirm/`) <form action="/unattach-confirm/`)
//line views/readers.qtpl:60 //line views/readers.qtpl:63
qw422016.E().S(h.Name) qw422016.E().S(h.Name)
//line views/readers.qtpl:60 //line views/readers.qtpl:63
qw422016.N().S(`" method="post" class="modal amnt-menu-block"> qw422016.N().S(`" method="post" class="modal amnt-menu-block">
<fieldset class="modal__fieldset"> <fieldset class="modal__fieldset">
<legend class="modal__title modal__title_small">Unattach</legend> <legend class="modal__title modal__title_small">Unattach</legend>
<p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p> <p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p>
<input type="submit" class="modal__action modal__submit"> <input type="submit" class="btn" value="Unattach">
</fieldset> </fieldset>
</form> </form>
`) `)
//line views/readers.qtpl:67 //line views/readers.qtpl:70
} }
//line views/readers.qtpl:67 //line views/readers.qtpl:70
qw422016.N().S(` qw422016.N().S(`
</section> </section>
</main> </main>
</div> </div>
`) `)
//line views/readers.qtpl:72 //line views/readers.qtpl:75
} }
//line views/readers.qtpl:72 //line views/readers.qtpl:75
func WriteAttachmentMenuHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) { func WriteAttachmentMenuHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) {
//line views/readers.qtpl:72 //line views/readers.qtpl:75
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:72 //line views/readers.qtpl:75
StreamAttachmentMenuHTML(qw422016, rq, h, u) StreamAttachmentMenuHTML(qw422016, rq, h, u)
//line views/readers.qtpl:72 //line views/readers.qtpl:75
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:72 //line views/readers.qtpl:75
} }
//line views/readers.qtpl:72 //line views/readers.qtpl:75
func AttachmentMenuHTML(rq *http.Request, h *hyphae.Hypha, u *user.User) string { func AttachmentMenuHTML(rq *http.Request, h *hyphae.Hypha, u *user.User) string {
//line views/readers.qtpl:72 //line views/readers.qtpl:75
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:72 //line views/readers.qtpl:75
WriteAttachmentMenuHTML(qb422016, rq, h, u) WriteAttachmentMenuHTML(qb422016, rq, h, u)
//line views/readers.qtpl:72 //line views/readers.qtpl:75
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/readers.qtpl:72 //line views/readers.qtpl:75
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:72 //line views/readers.qtpl:75
return qs422016 return qs422016
//line views/readers.qtpl:72 //line views/readers.qtpl:75
} }
// If `contents` == "", a helpful message is shown instead. // If `contents` == "", a helpful message is shown instead.
//line views/readers.qtpl:75 //line views/readers.qtpl:78
func StreamHyphaHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) { func StreamHyphaHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) {
//line views/readers.qtpl:75
qw422016.N().S(`
`)
//line views/readers.qtpl:77
relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name)
//line views/readers.qtpl:78 //line views/readers.qtpl:78
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:79 //line views/readers.qtpl:80
relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name)
u := user.FromRequest(rq)
//line views/readers.qtpl:82
qw422016.N().S(`
`)
//line views/readers.qtpl:83
StreamNavHTML(qw422016, rq, h.Name, "page") StreamNavHTML(qw422016, rq, h.Name, "page")
//line views/readers.qtpl:79 //line views/readers.qtpl:83
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<article> <article>
`) `)
//line views/readers.qtpl:83 //line views/readers.qtpl:87
qw422016.N().S(NaviTitleHTML(h)) qw422016.N().S(NaviTitleHTML(h))
//line views/readers.qtpl:83 //line views/readers.qtpl:87
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:84 //line views/readers.qtpl:88
if contents == "" { if h.Exists {
//line views/readers.qtpl:84 //line views/readers.qtpl:88
qw422016.N().S(`
<p>This hypha has no text. Why not <a href="/edit/`)
//line views/readers.qtpl:85
qw422016.E().S(h.Name)
//line views/readers.qtpl:85
qw422016.N().S(`">create it</a>?</p>
`)
//line views/readers.qtpl:86
if u := user.FromRequest(rq); (!user.AuthUsed || u.Group != "anon") && !h.Exists {
//line views/readers.qtpl:86
qw422016.N().S(`
<form action="/upload-binary/`)
//line views/readers.qtpl:87
qw422016.E().S(h.Name)
//line views/readers.qtpl:87
qw422016.N().S(`"
method="post" enctype="multipart/form-data"
class="upload-binary">
<label for="upload-binary__input">Upload an attachment:</label>
<input type="file" id="upload-binary__input" name="binary">
<input type="submit">
</form>
<br>
`)
//line views/readers.qtpl:95
}
//line views/readers.qtpl:95
qw422016.N().S(`
`)
//line views/readers.qtpl:96
} else {
//line views/readers.qtpl:96
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:97 //line views/readers.qtpl:89
qw422016.N().S(contents) qw422016.N().S(contents)
//line views/readers.qtpl:97 //line views/readers.qtpl:89
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:98 //line views/readers.qtpl:90
} else {
//line views/readers.qtpl:90
qw422016.N().S(`
`)
//line views/readers.qtpl:91
streamnonExistentHyphaNotice(qw422016, h, u)
//line views/readers.qtpl:91
qw422016.N().S(`
`)
//line views/readers.qtpl:92
} }
//line views/readers.qtpl:98 //line views/readers.qtpl:92
qw422016.N().S(` qw422016.N().S(`
</article> </article>
<section class="prevnext"> <section class="prevnext">
`) `)
//line views/readers.qtpl:101 //line views/readers.qtpl:95
if prevHyphaName != "" { if prevHyphaName != "" {
//line views/readers.qtpl:101 //line views/readers.qtpl:95
qw422016.N().S(` qw422016.N().S(`
<a class="prevnext__el prevnext__prev" href="/hypha/`) <a class="prevnext__el prevnext__prev" href="/hypha/`)
//line views/readers.qtpl:102 //line views/readers.qtpl:96
qw422016.E().S(prevHyphaName) qw422016.E().S(prevHyphaName)
//line views/readers.qtpl:102 //line views/readers.qtpl:96
qw422016.N().S(`" rel="prev">← `) qw422016.N().S(`" rel="prev">← `)
//line views/readers.qtpl:102 //line views/readers.qtpl:96
qw422016.E().S(util.BeautifulName(path.Base(prevHyphaName))) qw422016.E().S(util.BeautifulName(path.Base(prevHyphaName)))
//line views/readers.qtpl:102 //line views/readers.qtpl:96
qw422016.N().S(`</a> qw422016.N().S(`</a>
`) `)
//line views/readers.qtpl:103 //line views/readers.qtpl:97
} }
//line views/readers.qtpl:103 //line views/readers.qtpl:97
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:104 //line views/readers.qtpl:98
if nextHyphaName != "" { if nextHyphaName != "" {
//line views/readers.qtpl:104 //line views/readers.qtpl:98
qw422016.N().S(` qw422016.N().S(`
<a class="prevnext__el prevnext__next" href="/hypha/`) <a class="prevnext__el prevnext__next" href="/hypha/`)
//line views/readers.qtpl:105 //line views/readers.qtpl:99
qw422016.E().S(nextHyphaName) qw422016.E().S(nextHyphaName)
//line views/readers.qtpl:105 //line views/readers.qtpl:99
qw422016.N().S(`" rel="next">`) qw422016.N().S(`" rel="next">`)
//line views/readers.qtpl:105 //line views/readers.qtpl:99
qw422016.E().S(util.BeautifulName(path.Base(nextHyphaName))) qw422016.E().S(util.BeautifulName(path.Base(nextHyphaName)))
//line views/readers.qtpl:105 //line views/readers.qtpl:99
qw422016.N().S(` </a> qw422016.N().S(` </a>
`) `)
//line views/readers.qtpl:106 //line views/readers.qtpl:100
} }
//line views/readers.qtpl:106 //line views/readers.qtpl:100
qw422016.N().S(` qw422016.N().S(`
</section> </section>
`) `)
//line views/readers.qtpl:108 //line views/readers.qtpl:102
StreamSubhyphaeHTML(qw422016, subhyphae) StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/readers.qtpl:108 //line views/readers.qtpl:102
qw422016.N().S(` qw422016.N().S(`
</main> </main>
`) `)
//line views/readers.qtpl:110 //line views/readers.qtpl:104
StreamRelativeHyphaeHTML(qw422016, relatives) StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/readers.qtpl:110 //line views/readers.qtpl:104
qw422016.N().S(` qw422016.N().S(`
</div> </div>
`) `)
//line views/readers.qtpl:112 //line views/readers.qtpl:106
} streamviewScripts(qw422016)
//line views/readers.qtpl:106
//line views/readers.qtpl:112
func WriteHyphaHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) {
//line views/readers.qtpl:112
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:112
StreamHyphaHTML(qw422016, rq, h, contents)
//line views/readers.qtpl:112
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:112
}
//line views/readers.qtpl:112
func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) string {
//line views/readers.qtpl:112
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:112
WriteHyphaHTML(qb422016, rq, h, contents)
//line views/readers.qtpl:112
qs422016 := string(qb422016.B)
//line views/readers.qtpl:112
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:112
return qs422016
//line views/readers.qtpl:112
}
//line views/readers.qtpl:114
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) {
//line views/readers.qtpl:114
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:116 //line views/readers.qtpl:107
}
//line views/readers.qtpl:107
func WriteHyphaHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) {
//line views/readers.qtpl:107
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:107
StreamHyphaHTML(qw422016, rq, h, contents)
//line views/readers.qtpl:107
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:107
}
//line views/readers.qtpl:107
func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) string {
//line views/readers.qtpl:107
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:107
WriteHyphaHTML(qb422016, rq, h, contents)
//line views/readers.qtpl:107
qs422016 := string(qb422016.B)
//line views/readers.qtpl:107
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:107
return qs422016
//line views/readers.qtpl:107
}
//line views/readers.qtpl:109
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) {
//line views/readers.qtpl:109
qw422016.N().S(`
`)
//line views/readers.qtpl:111
relatives, subhyphae, _, _ := tree.Tree(h.Name) relatives, subhyphae, _, _ := tree.Tree(h.Name)
//line views/readers.qtpl:117 //line views/readers.qtpl:112
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:118 //line views/readers.qtpl:113
StreamNavHTML(qw422016, rq, h.Name, "revision", revHash) StreamNavHTML(qw422016, rq, h.Name, "revision", revHash)
//line views/readers.qtpl:118 //line views/readers.qtpl:113
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<article> <article>
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p> <p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
`) `)
//line views/readers.qtpl:123 //line views/readers.qtpl:118
qw422016.N().S(NaviTitleHTML(h)) qw422016.N().S(NaviTitleHTML(h))
//line views/readers.qtpl:123 //line views/readers.qtpl:118
qw422016.N().S(` qw422016.N().S(`
`) `)
//line views/readers.qtpl:124 //line views/readers.qtpl:119
qw422016.N().S(contents) qw422016.N().S(contents)
//line views/readers.qtpl:124 //line views/readers.qtpl:119
qw422016.N().S(` qw422016.N().S(`
</article> </article>
`) `)
//line views/readers.qtpl:126 //line views/readers.qtpl:121
StreamSubhyphaeHTML(qw422016, subhyphae) StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/readers.qtpl:126 //line views/readers.qtpl:121
qw422016.N().S(` qw422016.N().S(`
</main> </main>
`) `)
//line views/readers.qtpl:128 //line views/readers.qtpl:123
StreamRelativeHyphaeHTML(qw422016, relatives) StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/readers.qtpl:128 //line views/readers.qtpl:123
qw422016.N().S(` qw422016.N().S(`
</div> </div>
`) `)
//line views/readers.qtpl:130 //line views/readers.qtpl:125
streamviewScripts(qw422016)
//line views/readers.qtpl:125
qw422016.N().S(`
`)
//line views/readers.qtpl:126
} }
//line views/readers.qtpl:130 //line views/readers.qtpl:126
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) { func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) {
//line views/readers.qtpl:130 //line views/readers.qtpl:126
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:130 //line views/readers.qtpl:126
StreamRevisionHTML(qw422016, rq, h, contents, revHash) StreamRevisionHTML(qw422016, rq, h, contents, revHash)
//line views/readers.qtpl:130 //line views/readers.qtpl:126
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:130 //line views/readers.qtpl:126
} }
//line views/readers.qtpl:130 //line views/readers.qtpl:126
func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) string { func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) string {
//line views/readers.qtpl:130 //line views/readers.qtpl:126
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:130 //line views/readers.qtpl:126
WriteRevisionHTML(qb422016, rq, h, contents, revHash) WriteRevisionHTML(qb422016, rq, h, contents, revHash)
//line views/readers.qtpl:130 //line views/readers.qtpl:126
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/readers.qtpl:130 //line views/readers.qtpl:126
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:130 //line views/readers.qtpl:126
return qs422016 return qs422016
//line views/readers.qtpl:130 //line views/readers.qtpl:126
}
//line views/readers.qtpl:128
func streamviewScripts(qw422016 *qt422016.Writer) {
//line views/readers.qtpl:128
qw422016.N().S(`
`)
//line views/readers.qtpl:129
for _, scriptPath := range cfg.ViewScripts {
//line views/readers.qtpl:129
qw422016.N().S(`
<script src="`)
//line views/readers.qtpl:130
qw422016.E().S(scriptPath)
//line views/readers.qtpl:130
qw422016.N().S(`"></script>
`)
//line views/readers.qtpl:131
}
//line views/readers.qtpl:131
qw422016.N().S(`
`)
//line views/readers.qtpl:132
}
//line views/readers.qtpl:132
func writeviewScripts(qq422016 qtio422016.Writer) {
//line views/readers.qtpl:132
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:132
streamviewScripts(qw422016)
//line views/readers.qtpl:132
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:132
}
//line views/readers.qtpl:132
func viewScripts() string {
//line views/readers.qtpl:132
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:132
writeviewScripts(qb422016)
//line views/readers.qtpl:132
qs422016 := string(qb422016.B)
//line views/readers.qtpl:132
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:132
return qs422016
//line views/readers.qtpl:132
} }

View File

@ -1,4 +1,5 @@
{% import "path/filepath" %} {% import "path/filepath" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} {% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %} {% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %} {% import "github.com/bouncepaw/mycorrhiza/util" %}
@ -9,7 +10,7 @@
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="/static/common.css"> <link rel="stylesheet" type="text/css" href="/assets/common.css">
<title>{%s title %}</title> <title>{%s title %}</title>
{% for _, el := range headElements %}{%s= el %}{% endfor %} {% for _, el := range headElements %}{%s= el %}{% endfor %}
</head> </head>
@ -17,7 +18,7 @@
<header> <header>
<nav class="header-links main-width"> <nav class="header-links main-width">
<ul class="header-links__list"> <ul class="header-links__list">
{%- for _, link := range util.HeaderLinks -%} {%- for _, link := range cfg.HeaderLinks -%}
<li class="header-links__entry"><a class="header-links__link" href="{%s link.Href %}">{%s link.Display %}</a></li> <li class="header-links__entry"><a class="header-links__link" href="{%s link.Href %}">{%s link.Display %}</a></li>
{%- endfor -%} {%- endfor -%}
{%s= UserMenuHTML(u) %} {%s= UserMenuHTML(u) %}
@ -25,6 +26,7 @@
</nav> </nav>
</header> </header>
{%s= body %} {%s= body %}
{%= omnipresentScripts() %}
</body> </body>
</html> </html>
{% endfunc %} {% endfunc %}
@ -53,19 +55,19 @@ for u := range user.YieldUsers() {
<section> <section>
<h2>Admins</h2> <h2>Admins</h2>
<ol>{% for _, name := range admins %} <ol>{% for _, name := range admins %}
<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li> <li><a href="/page/{%s cfg.UserHypha %}/{%s name %}">{%s name %}</a></li>
{% endfor %}</ol> {% endfor %}</ol>
</section> </section>
<section> <section>
<h2>Moderators</h2> <h2>Moderators</h2>
<ol>{% for _, name := range moderators %} <ol>{% for _, name := range moderators %}
<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li> <li><a href="/page/{%s cfg.UserHypha %}/{%s name %}">{%s name %}</a></li>
{% endfor %}</ol> {% endfor %}</ol>
</section> </section>
<section> <section>
<h2>Editors</h2> <h2>Editors</h2>
<ol>{% for _, name := range editors %} <ol>{% for _, name := range editors %}
<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li> <li><a href="/page/{%s cfg.UserHypha %}/{%s name %}">{%s name %}</a></li>
{% endfor %}</ol> {% endfor %}</ol>
</section> </section>
</main> </main>
@ -95,16 +97,16 @@ for u := range user.YieldUsers() {
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<section> <section>
<h1>About {%s util.SiteName %}</h1> <h1>About {%s cfg.WikiName %}</h1>
<ul> <ul>
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> 1.1.0</li> <li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> 1.2.0 indev</li>
{%- if user.AuthUsed -%} {%- if user.AuthUsed -%}
<li><b>User count:</b> {%d user.Count() %}</li> <li><b>User count:</b> {%d user.Count() %}</li>
<li><b>Home page:</b> <a href="/">{%s util.HomePage %}</a></li> <li><b>Home page:</b> <a href="/">{%s cfg.HomeHypha %}</a></li>
<li><b>Administrators:</b> {%- for i, username := range user.ListUsersWithGroup("admin") -%} <li><b>Administrators:</b> {%- for i, username := range user.ListUsersWithGroup("admin") -%}
{%- if i > 0 -%}<span aria-hidden="true">, </span> {%- if i > 0 -%}<span aria-hidden="true">, </span>
{%- endif -%} {%- endif -%}
<a href="/page/{%s util.UserHypha %}/{%s username %}">{%s username %}</a>{%- endfor -%}</li> <a href="/page/{%s cfg.UserHypha %}/{%s username %}">{%s username %}</a>{%- endfor -%}</li>
{%- else -%} {%- else -%}
<li>This wiki does not use authorization</li> <li>This wiki does not use authorization</li>
{%- endif -%} {%- endif -%}
@ -151,3 +153,9 @@ for u := range user.YieldUsers() {
</main> </main>
</div> </div>
{% endfunc %} {% endfunc %}
{% func omnipresentScripts() %}
{% for _, scriptPath := range cfg.OmnipresentScripts %}
<script src="{%s scriptPath %}"></script>
{% endfor %}
{% endfunc %}

View File

@ -8,50 +8,53 @@ package views
import "path/filepath" import "path/filepath"
//line views/stuff.qtpl:2 //line views/stuff.qtpl:2
import "github.com/bouncepaw/mycorrhiza/hyphae" import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/stuff.qtpl:3 //line views/stuff.qtpl:3
import "github.com/bouncepaw/mycorrhiza/user" import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/stuff.qtpl:4 //line views/stuff.qtpl:4
import "github.com/bouncepaw/mycorrhiza/user"
//line views/stuff.qtpl:5
import "github.com/bouncepaw/mycorrhiza/util" import "github.com/bouncepaw/mycorrhiza/util"
//line views/stuff.qtpl:6 //line views/stuff.qtpl:7
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line views/stuff.qtpl:6 //line views/stuff.qtpl:7
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line views/stuff.qtpl:6 //line views/stuff.qtpl:7
func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) { func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) {
//line views/stuff.qtpl:6 //line views/stuff.qtpl:7
qw422016.N().S(` qw422016.N().S(`
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="/static/common.css"> <link rel="stylesheet" type="text/css" href="/assets/common.css">
<title>`) <title>`)
//line views/stuff.qtpl:13 //line views/stuff.qtpl:14
qw422016.E().S(title) qw422016.E().S(title)
//line views/stuff.qtpl:13 //line views/stuff.qtpl:14
qw422016.N().S(`</title> qw422016.N().S(`</title>
`) `)
//line views/stuff.qtpl:14 //line views/stuff.qtpl:15
for _, el := range headElements { for _, el := range headElements {
//line views/stuff.qtpl:14 //line views/stuff.qtpl:15
qw422016.N().S(el) qw422016.N().S(el)
//line views/stuff.qtpl:14 //line views/stuff.qtpl:15
} }
//line views/stuff.qtpl:14 //line views/stuff.qtpl:15
qw422016.N().S(` qw422016.N().S(`
</head> </head>
<body> <body>
@ -59,76 +62,81 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User,
<nav class="header-links main-width"> <nav class="header-links main-width">
<ul class="header-links__list"> <ul class="header-links__list">
`) `)
//line views/stuff.qtpl:20 //line views/stuff.qtpl:21
for _, link := range util.HeaderLinks { for _, link := range cfg.HeaderLinks {
//line views/stuff.qtpl:20 //line views/stuff.qtpl:21
qw422016.N().S(` <li class="header-links__entry"><a class="header-links__link" href="`) qw422016.N().S(` <li class="header-links__entry"><a class="header-links__link" href="`)
//line views/stuff.qtpl:21 //line views/stuff.qtpl:22
qw422016.E().S(link.Href) qw422016.E().S(link.Href)
//line views/stuff.qtpl:21 //line views/stuff.qtpl:22
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/stuff.qtpl:21 //line views/stuff.qtpl:22
qw422016.E().S(link.Display) qw422016.E().S(link.Display)
//line views/stuff.qtpl:21 //line views/stuff.qtpl:22
qw422016.N().S(`</a></li> qw422016.N().S(`</a></li>
`) `)
//line views/stuff.qtpl:22 //line views/stuff.qtpl:23
} }
//line views/stuff.qtpl:22 //line views/stuff.qtpl:23
qw422016.N().S(` `) qw422016.N().S(` `)
//line views/stuff.qtpl:23 //line views/stuff.qtpl:24
qw422016.N().S(UserMenuHTML(u)) qw422016.N().S(UserMenuHTML(u))
//line views/stuff.qtpl:23 //line views/stuff.qtpl:24
qw422016.N().S(` qw422016.N().S(`
</ul> </ul>
</nav> </nav>
</header> </header>
`) `)
//line views/stuff.qtpl:27 //line views/stuff.qtpl:28
qw422016.N().S(body) qw422016.N().S(body)
//line views/stuff.qtpl:27 //line views/stuff.qtpl:28
qw422016.N().S(`
`)
//line views/stuff.qtpl:29
streamomnipresentScripts(qw422016)
//line views/stuff.qtpl:29
qw422016.N().S(` qw422016.N().S(`
</body> </body>
</html> </html>
`) `)
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
} }
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) { func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) {
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
StreamBaseHTML(qw422016, title, body, u, headElements...) StreamBaseHTML(qw422016, title, body, u, headElements...)
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
} }
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
func BaseHTML(title, body string, u *user.User, headElements ...string) string { func BaseHTML(title, body string, u *user.User, headElements ...string) string {
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
WriteBaseHTML(qb422016, title, body, u, headElements...) WriteBaseHTML(qb422016, title, body, u, headElements...)
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
return qs422016 return qs422016
//line views/stuff.qtpl:30 //line views/stuff.qtpl:32
} }
//line views/stuff.qtpl:32 //line views/stuff.qtpl:34
func StreamUserListHTML(qw422016 *qt422016.Writer) { func StreamUserListHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:32 //line views/stuff.qtpl:34
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width user-list"> <main class="main-width user-list">
<h1>List of users</h1> <h1>List of users</h1>
`) `)
//line views/stuff.qtpl:37 //line views/stuff.qtpl:39
var ( var (
admins = make([]string, 0) admins = make([]string, 0)
moderators = make([]string, 0) moderators = make([]string, 0)
@ -145,303 +153,303 @@ func StreamUserListHTML(qw422016 *qt422016.Writer) {
} }
} }
//line views/stuff.qtpl:52 //line views/stuff.qtpl:54
qw422016.N().S(` qw422016.N().S(`
<section> <section>
<h2>Admins</h2> <h2>Admins</h2>
<ol>`) <ol>`)
//line views/stuff.qtpl:55 //line views/stuff.qtpl:57
for _, name := range admins { for _, name := range admins {
//line views/stuff.qtpl:55 //line views/stuff.qtpl:57
qw422016.N().S(` qw422016.N().S(`
<li><a href="/page/`) <li><a href="/page/`)
//line views/stuff.qtpl:56 //line views/stuff.qtpl:58
qw422016.E().S(util.UserHypha) qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:56 //line views/stuff.qtpl:58
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line views/stuff.qtpl:56 //line views/stuff.qtpl:58
qw422016.E().S(name) qw422016.E().S(name)
//line views/stuff.qtpl:56 //line views/stuff.qtpl:58
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/stuff.qtpl:56 //line views/stuff.qtpl:58
qw422016.E().S(name) qw422016.E().S(name)
//line views/stuff.qtpl:56 //line views/stuff.qtpl:58
qw422016.N().S(`</a></li> qw422016.N().S(`</a></li>
`) `)
//line views/stuff.qtpl:57 //line views/stuff.qtpl:59
} }
//line views/stuff.qtpl:57 //line views/stuff.qtpl:59
qw422016.N().S(`</ol> qw422016.N().S(`</ol>
</section> </section>
<section> <section>
<h2>Moderators</h2> <h2>Moderators</h2>
<ol>`) <ol>`)
//line views/stuff.qtpl:61 //line views/stuff.qtpl:63
for _, name := range moderators { for _, name := range moderators {
//line views/stuff.qtpl:61 //line views/stuff.qtpl:63
qw422016.N().S(` qw422016.N().S(`
<li><a href="/page/`) <li><a href="/page/`)
//line views/stuff.qtpl:62 //line views/stuff.qtpl:64
qw422016.E().S(util.UserHypha) qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:62 //line views/stuff.qtpl:64
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line views/stuff.qtpl:62 //line views/stuff.qtpl:64
qw422016.E().S(name) qw422016.E().S(name)
//line views/stuff.qtpl:62 //line views/stuff.qtpl:64
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/stuff.qtpl:62 //line views/stuff.qtpl:64
qw422016.E().S(name) qw422016.E().S(name)
//line views/stuff.qtpl:62 //line views/stuff.qtpl:64
qw422016.N().S(`</a></li> qw422016.N().S(`</a></li>
`) `)
//line views/stuff.qtpl:63 //line views/stuff.qtpl:65
} }
//line views/stuff.qtpl:63 //line views/stuff.qtpl:65
qw422016.N().S(`</ol> qw422016.N().S(`</ol>
</section> </section>
<section> <section>
<h2>Editors</h2> <h2>Editors</h2>
<ol>`) <ol>`)
//line views/stuff.qtpl:67 //line views/stuff.qtpl:69
for _, name := range editors { for _, name := range editors {
//line views/stuff.qtpl:67 //line views/stuff.qtpl:69
qw422016.N().S(` qw422016.N().S(`
<li><a href="/page/`) <li><a href="/page/`)
//line views/stuff.qtpl:68 //line views/stuff.qtpl:70
qw422016.E().S(util.UserHypha) qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:68 //line views/stuff.qtpl:70
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line views/stuff.qtpl:68 //line views/stuff.qtpl:70
qw422016.E().S(name) qw422016.E().S(name)
//line views/stuff.qtpl:68 //line views/stuff.qtpl:70
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/stuff.qtpl:68 //line views/stuff.qtpl:70
qw422016.E().S(name) qw422016.E().S(name)
//line views/stuff.qtpl:68 //line views/stuff.qtpl:70
qw422016.N().S(`</a></li> qw422016.N().S(`</a></li>
`) `)
//line views/stuff.qtpl:69 //line views/stuff.qtpl:71
} }
//line views/stuff.qtpl:69 //line views/stuff.qtpl:71
qw422016.N().S(`</ol> qw422016.N().S(`</ol>
</section> </section>
</main> </main>
</div> </div>
`) `)
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
} }
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
func WriteUserListHTML(qq422016 qtio422016.Writer) { func WriteUserListHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
StreamUserListHTML(qw422016) StreamUserListHTML(qw422016)
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
} }
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
func UserListHTML() string { func UserListHTML() string {
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
WriteUserListHTML(qb422016) WriteUserListHTML(qb422016)
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
return qs422016 return qs422016
//line views/stuff.qtpl:73 //line views/stuff.qtpl:75
} }
//line views/stuff.qtpl:75 //line views/stuff.qtpl:77
func StreamHyphaListHTML(qw422016 *qt422016.Writer) { func StreamHyphaListHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:75 //line views/stuff.qtpl:77
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<h1>List of hyphae</h1> <h1>List of hyphae</h1>
<p>This wiki has `) <p>This wiki has `)
//line views/stuff.qtpl:79 //line views/stuff.qtpl:81
qw422016.N().D(hyphae.Count()) qw422016.N().D(hyphae.Count())
//line views/stuff.qtpl:79 //line views/stuff.qtpl:81
qw422016.N().S(` hyphae.</p> qw422016.N().S(` hyphae.</p>
<ul class="hypha-list"> <ul class="hypha-list">
`) `)
//line views/stuff.qtpl:81 //line views/stuff.qtpl:83
for h := range hyphae.YieldExistingHyphae() { for h := range hyphae.YieldExistingHyphae() {
//line views/stuff.qtpl:81 //line views/stuff.qtpl:83
qw422016.N().S(` qw422016.N().S(`
<li class="hypha-list__entry"> <li class="hypha-list__entry">
<a class="hypha-list__link" href="/hypha/`) <a class="hypha-list__link" href="/hypha/`)
//line views/stuff.qtpl:83 //line views/stuff.qtpl:85
qw422016.E().S(h.Name) qw422016.E().S(h.Name)
//line views/stuff.qtpl:83 //line views/stuff.qtpl:85
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/stuff.qtpl:83 //line views/stuff.qtpl:85
qw422016.E().S(util.BeautifulName(h.Name)) qw422016.E().S(util.BeautifulName(h.Name))
//line views/stuff.qtpl:83 //line views/stuff.qtpl:85
qw422016.N().S(`</a> qw422016.N().S(`</a>
`) `)
//line views/stuff.qtpl:84 //line views/stuff.qtpl:86
if h.BinaryPath != "" { if h.BinaryPath != "" {
//line views/stuff.qtpl:84 //line views/stuff.qtpl:86
qw422016.N().S(` qw422016.N().S(`
<span class="hypha-list__amnt-type">`) <span class="hypha-list__amnt-type">`)
//line views/stuff.qtpl:85 //line views/stuff.qtpl:87
qw422016.E().S(filepath.Ext(h.BinaryPath)[1:]) qw422016.E().S(filepath.Ext(h.BinaryPath)[1:])
//line views/stuff.qtpl:85 //line views/stuff.qtpl:87
qw422016.N().S(`</span> qw422016.N().S(`</span>
`) `)
//line views/stuff.qtpl:86 //line views/stuff.qtpl:88
} }
//line views/stuff.qtpl:86 //line views/stuff.qtpl:88
qw422016.N().S(` qw422016.N().S(`
</li> </li>
`) `)
//line views/stuff.qtpl:88 //line views/stuff.qtpl:90
} }
//line views/stuff.qtpl:88 //line views/stuff.qtpl:90
qw422016.N().S(` qw422016.N().S(`
</ul> </ul>
</main> </main>
</div> </div>
`) `)
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
} }
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
func WriteHyphaListHTML(qq422016 qtio422016.Writer) { func WriteHyphaListHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
StreamHyphaListHTML(qw422016) StreamHyphaListHTML(qw422016)
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
} }
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
func HyphaListHTML() string { func HyphaListHTML() string {
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
WriteHyphaListHTML(qb422016) WriteHyphaListHTML(qb422016)
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
return qs422016 return qs422016
//line views/stuff.qtpl:92 //line views/stuff.qtpl:94
} }
//line views/stuff.qtpl:94 //line views/stuff.qtpl:96
func StreamAboutHTML(qw422016 *qt422016.Writer) { func StreamAboutHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:94 //line views/stuff.qtpl:96
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
<section> <section>
<h1>About `) <h1>About `)
//line views/stuff.qtpl:98 //line views/stuff.qtpl:100
qw422016.E().S(util.SiteName) qw422016.E().S(cfg.WikiName)
//line views/stuff.qtpl:98 //line views/stuff.qtpl:100
qw422016.N().S(`</h1> qw422016.N().S(`</h1>
<ul> <ul>
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> 1.1.0</li> <li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> 1.2.0 indev</li>
`) `)
//line views/stuff.qtpl:101 //line views/stuff.qtpl:103
if user.AuthUsed { if user.AuthUsed {
//line views/stuff.qtpl:101 //line views/stuff.qtpl:103
qw422016.N().S(` <li><b>User count:</b> `) qw422016.N().S(` <li><b>User count:</b> `)
//line views/stuff.qtpl:102 //line views/stuff.qtpl:104
qw422016.N().D(user.Count()) qw422016.N().D(user.Count())
//line views/stuff.qtpl:102 //line views/stuff.qtpl:104
qw422016.N().S(`</li> qw422016.N().S(`</li>
<li><b>Home page:</b> <a href="/">`) <li><b>Home page:</b> <a href="/">`)
//line views/stuff.qtpl:103 //line views/stuff.qtpl:105
qw422016.E().S(util.HomePage) qw422016.E().S(cfg.HomeHypha)
//line views/stuff.qtpl:103 //line views/stuff.qtpl:105
qw422016.N().S(`</a></li> qw422016.N().S(`</a></li>
<li><b>Administrators:</b>`) <li><b>Administrators:</b>`)
//line views/stuff.qtpl:104 //line views/stuff.qtpl:106
for i, username := range user.ListUsersWithGroup("admin") { for i, username := range user.ListUsersWithGroup("admin") {
//line views/stuff.qtpl:105 //line views/stuff.qtpl:107
if i > 0 { if i > 0 {
//line views/stuff.qtpl:105 //line views/stuff.qtpl:107
qw422016.N().S(`<span aria-hidden="true">, </span> qw422016.N().S(`<span aria-hidden="true">, </span>
`) `)
//line views/stuff.qtpl:106 //line views/stuff.qtpl:108
} }
//line views/stuff.qtpl:106 //line views/stuff.qtpl:108
qw422016.N().S(` <a href="/page/`) qw422016.N().S(` <a href="/page/`)
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
qw422016.E().S(util.UserHypha) qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
qw422016.E().S(username) qw422016.E().S(username)
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
qw422016.E().S(username) qw422016.E().S(username)
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
qw422016.N().S(`</a>`) qw422016.N().S(`</a>`)
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
} }
//line views/stuff.qtpl:107 //line views/stuff.qtpl:109
qw422016.N().S(`</li> qw422016.N().S(`</li>
`) `)
//line views/stuff.qtpl:108 //line views/stuff.qtpl:110
} else { } else {
//line views/stuff.qtpl:108 //line views/stuff.qtpl:110
qw422016.N().S(` <li>This wiki does not use authorization</li> qw422016.N().S(` <li>This wiki does not use authorization</li>
`) `)
//line views/stuff.qtpl:110 //line views/stuff.qtpl:112
} }
//line views/stuff.qtpl:110 //line views/stuff.qtpl:112
qw422016.N().S(` </ul> qw422016.N().S(` </ul>
<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p> <p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p>
</section> </section>
</main> </main>
</div> </div>
`) `)
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
} }
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
func WriteAboutHTML(qq422016 qtio422016.Writer) { func WriteAboutHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
StreamAboutHTML(qw422016) StreamAboutHTML(qw422016)
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
} }
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
func AboutHTML() string { func AboutHTML() string {
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
WriteAboutHTML(qb422016) WriteAboutHTML(qb422016)
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
return qs422016 return qs422016
//line views/stuff.qtpl:116 //line views/stuff.qtpl:118
} }
//line views/stuff.qtpl:118 //line views/stuff.qtpl:120
func StreamAdminPanelHTML(qw422016 *qt422016.Writer) { func StreamAdminPanelHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:118 //line views/stuff.qtpl:120
qw422016.N().S(` qw422016.N().S(`
<div class="layout"> <div class="layout">
<main class="main-width"> <main class="main-width">
@ -478,31 +486,80 @@ func StreamAdminPanelHTML(qw422016 *qt422016.Writer) {
</main> </main>
</div> </div>
`) `)
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
} }
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
func WriteAdminPanelHTML(qq422016 qtio422016.Writer) { func WriteAdminPanelHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
StreamAdminPanelHTML(qw422016) StreamAdminPanelHTML(qw422016)
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
} }
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
func AdminPanelHTML() string { func AdminPanelHTML() string {
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
WriteAdminPanelHTML(qb422016) WriteAdminPanelHTML(qb422016)
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
return qs422016 return qs422016
//line views/stuff.qtpl:153 //line views/stuff.qtpl:155
}
//line views/stuff.qtpl:157
func streamomnipresentScripts(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:157
qw422016.N().S(`
`)
//line views/stuff.qtpl:158
for _, scriptPath := range cfg.OmnipresentScripts {
//line views/stuff.qtpl:158
qw422016.N().S(`
<script src="`)
//line views/stuff.qtpl:159
qw422016.E().S(scriptPath)
//line views/stuff.qtpl:159
qw422016.N().S(`"></script>
`)
//line views/stuff.qtpl:160
}
//line views/stuff.qtpl:160
qw422016.N().S(`
`)
//line views/stuff.qtpl:161
}
//line views/stuff.qtpl:161
func writeomnipresentScripts(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:161
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:161
streamomnipresentScripts(qw422016)
//line views/stuff.qtpl:161
qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:161
}
//line views/stuff.qtpl:161
func omnipresentScripts() string {
//line views/stuff.qtpl:161
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:161
writeomnipresentScripts(qb422016)
//line views/stuff.qtpl:161
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:161
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:161
return qs422016
//line views/stuff.qtpl:161
} }

View File

@ -1,15 +1,17 @@
package main package web
import ( import (
"io"
"log" "log"
"net/http" "net/http"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views" "github.com/bouncepaw/mycorrhiza/views"
) )
// This is not init(), because user.AuthUsed is not set at init-stage. // initAdmin sets up /admin routes if auth is used. Call it after you have decided if you want to use auth.
func initAdmin() { func initAdmin() {
if user.AuthUsed { if user.AuthUsed {
http.HandleFunc("/admin", handlerAdmin) http.HandleFunc("/admin", handlerAdmin)
@ -18,26 +20,32 @@ func initAdmin() {
} }
} }
// handlerAdmin provides the admin panel.
func handlerAdmin(w http.ResponseWriter, rq *http.Request) { func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
if user.CanProceed(rq, "admin") { if user.CanProceed(rq, "admin") {
w.Header().Set("Content-Type", "text/html;charset=utf-8") w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte(base("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq)))) _, err := io.WriteString(w, views.BaseHTML("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq)))
if err != nil {
log.Println(err)
}
} }
} }
// handlerAdminShutdown kills the wiki.
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" { if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" {
log.Fatal("An admin commanded the wiki to shutdown") log.Fatal("An admin commanded the wiki to shutdown")
} }
} }
// handlerAdminReindexUsers reinitialises the user system.
func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) { func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
if user.CanProceed(rq, "admin") && rq.Method == "POST" { if user.CanProceed(rq, "admin") && rq.Method == "POST" {
user.ReadUsersFromFilesystem() user.ReadUsersFromFilesystem()
http.Redirect(w, rq, "/hypha/"+util.UserHypha, http.StatusSeeOther) http.Redirect(w, rq, "/hypha/"+cfg.UserHypha, http.StatusSeeOther)
} }
} }

View File

@ -1,6 +1,7 @@
package main package web
import ( import (
"github.com/bouncepaw/mycorrhiza/cfg"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -10,23 +11,29 @@ import (
"github.com/bouncepaw/mycorrhiza/views" "github.com/bouncepaw/mycorrhiza/views"
) )
func init() { func initAuth() {
http.HandleFunc("/register", handlerRegister) if !user.AuthUsed {
return
}
if cfg.UseRegistration {
http.HandleFunc("/register", handlerRegister)
}
http.HandleFunc("/login", handlerLogin) http.HandleFunc("/login", handlerLogin)
http.HandleFunc("/login-data", handlerLoginData) http.HandleFunc("/login-data", handlerLoginData)
http.HandleFunc("/logout", handlerLogout) http.HandleFunc("/logout", handlerLogout)
http.HandleFunc("/logout-confirm", handlerLogoutConfirm) http.HandleFunc("/logout-confirm", handlerLogoutConfirm)
} }
// handlerRegister both displays the register form (GET) and registers users (POST).
func handlerRegister(w http.ResponseWriter, rq *http.Request) { func handlerRegister(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
if !util.UseRegistration { if !cfg.UseRegistration {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
} }
if rq.Method == http.MethodGet { if rq.Method == http.MethodGet {
io.WriteString( io.WriteString(
w, w,
base( views.BaseHTML(
"Register", "Register",
views.RegisterHTML(rq), views.RegisterHTML(rq),
user.FromRequest(rq), user.FromRequest(rq),
@ -47,6 +54,7 @@ func handlerRegister(w http.ResponseWriter, rq *http.Request) {
} }
} }
// handlerLogout shows the logout form.
func handlerLogout(w http.ResponseWriter, rq *http.Request) { func handlerLogout(w http.ResponseWriter, rq *http.Request) {
var ( var (
u = user.FromRequest(rq) u = user.FromRequest(rq)
@ -60,35 +68,42 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) {
log.Println("Unknown user tries to log out") log.Println("Unknown user tries to log out")
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
} }
w.Write([]byte(base("Logout?", views.LogoutHTML(can), u))) w.Write([]byte(views.BaseHTML("Logout?", views.LogoutHTML(can), u)))
} }
// handlerLogoutConfirm logs the user out.
//
// TODO: merge into handlerLogout as POST method.
func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) { func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
user.LogoutFromRequest(w, rq) user.LogoutFromRequest(w, rq)
http.Redirect(w, rq, "/", http.StatusSeeOther) http.Redirect(w, rq, "/", http.StatusSeeOther)
} }
func handlerLoginData(w http.ResponseWriter, rq *http.Request) { // handlerLogin shows the login form.
log.Println(rq.URL)
var (
username = util.CanonicalName(rq.PostFormValue("username"))
password = rq.PostFormValue("password")
err = user.LoginDataHTTP(w, rq, username, password)
)
if err != "" {
w.Write([]byte(base(err, views.LoginErrorHTML(err), user.EmptyUser())))
} else {
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
}
func handlerLogin(w http.ResponseWriter, rq *http.Request) { func handlerLogin(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
w.Header().Set("Content-Type", "text/html;charset=utf-8") w.Header().Set("Content-Type", "text/html;charset=utf-8")
if user.AuthUsed { if user.AuthUsed {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} else { } else {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
} }
w.Write([]byte(base("Login", views.LoginHTML(), user.EmptyUser()))) w.Write([]byte(views.BaseHTML("Login", views.LoginHTML(), user.EmptyUser())))
}
// handlerLoginData logs the user in.
//
// TODO: merge into handlerLogin as POST method.
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
var (
username = util.CanonicalName(rq.PostFormValue("username"))
password = rq.PostFormValue("password")
err = user.LoginDataHTTP(w, rq, username, password)
)
if err != "" {
w.Write([]byte(views.BaseHTML(err, views.LoginErrorHTML(err), user.EmptyUser())))
} else {
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
} }

View File

@ -1,4 +1,4 @@
package main package web
import ( import (
"fmt" "fmt"
@ -13,7 +13,7 @@ import (
"github.com/bouncepaw/mycorrhiza/views" "github.com/bouncepaw/mycorrhiza/views"
) )
func init() { func initHistory() {
http.HandleFunc("/history/", handlerHistory) http.HandleFunc("/history/", handlerHistory)
http.HandleFunc("/recent-changes/", handlerRecentChanges) http.HandleFunc("/recent-changes/", handlerRecentChanges)
http.HandleFunc("/recent-changes-rss", handlerRecentChangesRSS) http.HandleFunc("/recent-changes-rss", handlerRecentChangesRSS)
@ -21,10 +21,10 @@ func init() {
http.HandleFunc("/recent-changes-json", handlerRecentChangesJSON) http.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
} }
// handlerHistory lists all revisions of a hypha // handlerHistory lists all revisions of a hypha.
func handlerHistory(w http.ResponseWriter, rq *http.Request) { func handlerHistory(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
hyphaName := HyphaNameFromRq(rq, "history") hyphaName := util.HyphaNameFromRq(rq, "history")
var list string var list string
// History can be found for files that do not exist anymore. // History can be found for files that do not exist anymore.
@ -35,25 +35,26 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
log.Println("Found", len(revs), "revisions for", hyphaName) log.Println("Found", len(revs), "revisions for", hyphaName)
util.HTTP200Page(w, util.HTTP200Page(w,
base(hyphaName, views.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq))) views.BaseHTML(hyphaName, views.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq)))
} }
// Recent changes // handlerRecentChanges displays the /recent-changes/ page.
func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/") noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
n, err = strconv.Atoi(noPrefix) n, err = strconv.Atoi(noPrefix)
) )
if err == nil && n < 101 { if err == nil && n < 101 {
util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", views.RecentChangesHTML(n), user.FromRequest(rq))) util.HTTP200Page(w, views.BaseHTML(strconv.Itoa(n)+" recent changes", views.RecentChangesHTML(n), user.FromRequest(rq)))
} else { } else {
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther) http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
} }
} }
// genericHandlerOfFeeds is a helper function for the web feed handlers.
func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) { func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) {
log.Println(rq.URL) util.PrepareRq(rq)
if content, err := f(); err != nil { if content, err := f(); err != nil {
w.Header().Set("Content-Type", "text/plain;charset=utf-8") w.Header().Set("Content-Type", "text/plain;charset=utf-8")
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View File

@ -1,20 +1,21 @@
package main package web
import ( import (
"fmt" "fmt"
"github.com/bouncepaw/mycomarkup"
"github.com/bouncepaw/mycomarkup/mycocontext"
"log" "log"
"net/http" "net/http"
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/shroom" "github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views" "github.com/bouncepaw/mycorrhiza/views"
) )
func init() { func initMutators() {
// Those that do not actually mutate anything: // Those that do not actually mutate anything:
http.HandleFunc("/edit/", handlerEdit) http.HandleFunc("/edit/", handlerEdit)
http.HandleFunc("/delete-ask/", handlerDeleteAsk) http.HandleFunc("/delete-ask/", handlerDeleteAsk)
@ -35,14 +36,14 @@ func factoryHandlerAsker(
succPageTemplate func(*http.Request, string, bool) string, succPageTemplate func(*http.Request, string, bool) string,
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, rq *http.Request) { return func(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, actionPath) hyphaName = util.HyphaNameFromRq(rq, actionPath)
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
if err, errtitle := asker(u, h); err != nil { if err, errtitle := asker(u, h); err != nil {
HttpErr( httpErr(
w, w,
http.StatusInternalServerError, http.StatusInternalServerError,
hyphaName, hyphaName,
@ -52,7 +53,7 @@ func factoryHandlerAsker(
} }
util.HTTP200Page( util.HTTP200Page(
w, w,
base( views.BaseHTML(
fmt.Sprintf(succTitleTemplate, hyphaName), fmt.Sprintf(succTitleTemplate, hyphaName),
succPageTemplate(rq, hyphaName, h.Exists), succPageTemplate(rq, hyphaName, h.Exists),
u)) u))
@ -85,14 +86,14 @@ func factoryHandlerConfirmer(
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string), confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string),
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, rq *http.Request) { return func(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, actionPath) hyphaName = util.HyphaNameFromRq(rq, actionPath)
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() { if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() {
HttpErr(w, http.StatusInternalServerError, hyphaName, httpErr(w, http.StatusInternalServerError, hyphaName,
errtitle, errtitle,
hop.FirstErrorText()) hop.FirstErrorText())
return return
@ -129,9 +130,9 @@ var handlerRenameConfirm = factoryHandlerConfirmer(
// handlerEdit shows the edit form. It doesn't edit anything actually. // handlerEdit shows the edit form. It doesn't edit anything actually.
func handlerEdit(w http.ResponseWriter, rq *http.Request) { func handlerEdit(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "edit") hyphaName = util.HyphaNameFromRq(rq, "edit")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
warning string warning string
textAreaFill string textAreaFill string
@ -139,7 +140,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
if err, errtitle := shroom.CanEdit(u, h); err != nil { if err, errtitle := shroom.CanEdit(u, h); err != nil {
HttpErr(w, http.StatusInternalServerError, hyphaName, httpErr(w, http.StatusInternalServerError, hyphaName,
errtitle, errtitle,
err.Error()) err.Error())
return return
@ -148,7 +149,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
textAreaFill, err = shroom.FetchTextPart(h) textAreaFill, err = shroom.FetchTextPart(h)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
HttpErr(w, http.StatusInternalServerError, hyphaName, httpErr(w, http.StatusInternalServerError, hyphaName,
"Error", "Error",
"Could not fetch text data") "Could not fetch text data")
return return
@ -158,7 +159,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
} }
util.HTTP200Page( util.HTTP200Page(
w, w,
base( views.BaseHTML(
"Edit "+hyphaName, "Edit "+hyphaName,
views.EditHTML(rq, hyphaName, textAreaFill, warning), views.EditHTML(rq, hyphaName, textAreaFill, warning),
u)) u))
@ -166,9 +167,9 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
// handlerUploadText uploads a new text part for the hypha. // handlerUploadText uploads a new text part for the hypha.
func handlerUploadText(w http.ResponseWriter, rq *http.Request) { func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "upload-text") hyphaName = util.HyphaNameFromRq(rq, "upload-text")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
textData = rq.PostFormValue("text") textData = rq.PostFormValue("text")
action = rq.PostFormValue("action") action = rq.PostFormValue("action")
@ -180,7 +181,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
if action != "Preview" { if action != "Preview" {
hop, errtitle = shroom.UploadText(h, []byte(textData), u) hop, errtitle = shroom.UploadText(h, []byte(textData), u)
if hop.HasErrors() { if hop.HasErrors() {
HttpErr(w, http.StatusForbidden, hyphaName, httpErr(w, http.StatusForbidden, hyphaName,
errtitle, errtitle,
hop.FirstErrorText()) hop.FirstErrorText())
return return
@ -188,16 +189,18 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
} }
if action == "Preview" { if action == "Preview" {
ctx, _ := mycocontext.ContextFromStringInput(hyphaName, textData)
util.HTTP200Page( util.HTTP200Page(
w, w,
base( views.BaseHTML(
"Preview "+hyphaName, "Preview "+hyphaName,
views.PreviewHTML( views.PreviewHTML(
rq, rq,
hyphaName, hyphaName,
textData, textData,
"", "",
markup.Doc(hyphaName, textData).AsHTML()), mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))),
u)) u))
} else { } else {
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther) http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
@ -206,21 +209,21 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
// handlerUploadBinary uploads a new binary part for the hypha. // handlerUploadBinary uploads a new binary part for the hypha.
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
rq.ParseMultipartForm(10 << 20) // Set upload limit rq.ParseMultipartForm(10 << 20) // Set upload limit
var ( var (
hyphaName = HyphaNameFromRq(rq, "upload-binary") hyphaName = util.HyphaNameFromRq(rq, "upload-binary")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
file, handler, err = rq.FormFile("binary") file, handler, err = rq.FormFile("binary")
) )
if err != nil { if err != nil {
HttpErr(w, http.StatusInternalServerError, hyphaName, httpErr(w, http.StatusInternalServerError, hyphaName,
"Error", "Error",
err.Error()) err.Error())
} }
if err, errtitle := shroom.CanAttach(u, h); err != nil { if err, errtitle := shroom.CanAttach(u, h); err != nil {
HttpErr(w, http.StatusInternalServerError, hyphaName, httpErr(w, http.StatusInternalServerError, hyphaName,
errtitle, errtitle,
err.Error()) err.Error())
} }
@ -240,7 +243,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
) )
if hop.HasErrors() { if hop.HasErrors() {
HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, hop.FirstErrorText()) httpErr(w, http.StatusInternalServerError, hyphaName, errtitle, hop.FirstErrorText())
return return
} }
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther) http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)

View File

@ -1,7 +1,8 @@
package main package web
import ( import (
"fmt" "fmt"
"github.com/bouncepaw/mycomarkup/mycocontext"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
@ -11,14 +12,15 @@ import (
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views" "github.com/bouncepaw/mycorrhiza/views"
"github.com/bouncepaw/mycomarkup"
) )
func init() { func initReaders() {
http.HandleFunc("/page/", handlerHypha) http.HandleFunc("/page/", handlerHypha)
http.HandleFunc("/hypha/", handlerHypha) http.HandleFunc("/hypha/", handlerHypha)
http.HandleFunc("/text/", handlerText) http.HandleFunc("/text/", handlerText)
@ -29,9 +31,9 @@ func init() {
} }
func handlerAttachment(w http.ResponseWriter, rq *http.Request) { func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "attachment") hyphaName = util.HyphaNameFromRq(rq, "attachment")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
@ -43,7 +45,7 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
} }
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/") shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/') firstSlashIndex = strings.IndexRune(shorterUrl, '/')
@ -61,7 +63,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
// handlerRevision displays a specific revision of text part a page // handlerRevision displays a specific revision of text part a page
func handlerRevision(w http.ResponseWriter, rq *http.Request) { func handlerRevision(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/") shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/') firstSlashIndex = strings.IndexRune(shorterUrl, '/')
@ -73,7 +75,8 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
if err == nil { if err == nil {
contents = markup.Doc(hyphaName, textContents).AsHTML() ctx, _ := mycocontext.ContextFromStringInput(hyphaName, textContents)
contents = mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))
} }
page := views.RevisionHTML( page := views.RevisionHTML(
rq, rq,
@ -83,13 +86,13 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
) )
w.Header().Set("Content-Type", "text/html;charset=utf-8") w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write([]byte(base(util.BeautifulName(hyphaName), page, u))) _, _ = fmt.Fprint(w, views.BaseHTML(util.BeautifulName(hyphaName), page, u))
} }
// handlerText serves raw source text of the hypha. // handlerText serves raw source text of the hypha.
func handlerText(w http.ResponseWriter, rq *http.Request) { func handlerText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
hyphaName := HyphaNameFromRq(rq, "text") hyphaName := util.HyphaNameFromRq(rq, "text")
if h := hyphae.ByName(hyphaName); h.Exists { if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", h.TextPath) log.Println("Serving", h.TextPath)
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
@ -99,8 +102,8 @@ func handlerText(w http.ResponseWriter, rq *http.Request) {
// handlerBinary serves binary part of the hypha. // handlerBinary serves binary part of the hypha.
func handlerBinary(w http.ResponseWriter, rq *http.Request) { func handlerBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
hyphaName := HyphaNameFromRq(rq, "binary") hyphaName := util.HyphaNameFromRq(rq, "binary")
if h := hyphae.ByName(hyphaName); h.Exists { if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", h.BinaryPath) log.Println("Serving", h.BinaryPath)
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath))) w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath)))
@ -110,9 +113,9 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
// handlerHypha is the main hypha action that displays the hypha and the binary upload form along with some navigation. // handlerHypha is the main hypha action that displays the hypha and the binary upload form along with some navigation.
func handlerHypha(w http.ResponseWriter, rq *http.Request) { func handlerHypha(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) util.PrepareRq(rq)
var ( var (
hyphaName = HyphaNameFromRq(rq, "page", "hypha") hyphaName = util.HyphaNameFromRq(rq, "page", "hypha")
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
contents string contents string
openGraph string openGraph string
@ -122,9 +125,10 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) {
fileContentsT, errT := ioutil.ReadFile(h.TextPath) fileContentsT, errT := ioutil.ReadFile(h.TextPath)
_, errB := os.Stat(h.BinaryPath) _, errB := os.Stat(h.BinaryPath)
if errT == nil { if errT == nil {
md := markup.Doc(hyphaName, string(fileContentsT)) ctx, _ := mycocontext.ContextFromStringInput(hyphaName, string(fileContentsT))
contents = md.AsHTML() ast := mycomarkup.BlockTree(ctx)
openGraph = md.OpenGraphHTML() contents = mycomarkup.BlocksToHTML(ctx, ast)
openGraph = mycomarkup.OpenGraphHTML(ctx, ast)
} }
if !os.IsNotExist(errB) { if !os.IsNotExist(errB) {
contents = views.AttachmentHTML(h) + contents contents = views.AttachmentHTML(h) + contents

92
web/stuff.go Normal file
View File

@ -0,0 +1,92 @@
package web
// stuff.go is used for meta stuff about the wiki or all hyphae at once.
import (
"github.com/bouncepaw/mycorrhiza/cfg"
"io"
"log"
"math/rand"
"net/http"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
func initStuff() {
http.HandleFunc("/list/", handlerList)
http.HandleFunc("/reindex/", handlerReindex)
http.HandleFunc("/update-header-links/", handlerUpdateHeaderLinks)
http.HandleFunc("/random/", handlerRandom)
http.HandleFunc("/about/", handlerAbout)
}
// handlerList shows a list of all hyphae in the wiki in random order.
func handlerList(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
util.HTTP200Page(w, views.BaseHTML("List of pages", views.HyphaListHTML(), user.FromRequest(rq)))
}
// handlerReindex reindexes all hyphae by checking the wiki storage directory anew.
func handlerReindex(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
if ok := user.CanProceed(rq, "reindex"); !ok {
httpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be an admin to reindex hyphae.")
log.Println("Rejected", rq.URL)
return
}
hyphae.ResetCount()
log.Println("Wiki storage directory is", cfg.WikiDir)
log.Println("Start indexing hyphae...")
hyphae.Index(cfg.WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae")
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
// handlerUpdateHeaderLinks updates header links by reading the configured hypha, if there is any, or resorting to default values.
//
// See https://mycorrhiza.lesarbr.es/hypha/configuration/header
func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
if ok := user.CanProceed(rq, "update-header-links"); !ok {
httpErr(w, http.StatusForbidden, cfg.HomeHypha, "Not enough rights", "You must be a moderator to update header links.")
log.Println("Rejected", rq.URL)
return
}
shroom.SetHeaderLinks()
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
// handlerRandom redirects to a random hypha.
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
var (
randomHyphaName string
amountOfHyphae = hyphae.Count()
)
if amountOfHyphae == 0 {
httpErr(w, http.StatusNotFound, cfg.HomeHypha, "There are no hyphae",
"It is impossible to display a random hypha because the wiki does not contain any hyphae")
return
}
i := rand.Intn(amountOfHyphae)
for h := range hyphae.YieldExistingHyphae() {
if i == 0 {
randomHyphaName = h.Name
}
i--
}
http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther)
}
// handlerAbout shows a summary of wiki's software.
func handlerAbout(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err := io.WriteString(w, views.BaseHTML("About "+cfg.WikiName, views.AboutHTML(), user.FromRequest(rq)))
if err != nil {
log.Println(err)
}
}

130
web/web.go Normal file
View File

@ -0,0 +1,130 @@
// Package web contains web handlers and initialization stuff.
//
// It exports just one function: Init. Call it if you want to have web capabilities.
package web
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strings"
"github.com/bouncepaw/mycorrhiza/assets"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
// httpErr is used by many handlers to signal errors in a compact way.
func httpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
log.Println(errMsg, "for", name)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(status)
fmt.Fprint(
w,
views.BaseHTML(
title,
fmt.Sprintf(
`<main class="main-width"><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
errMsg,
name,
),
user.EmptyUser(),
),
)
}
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
if _, err := os.Stat(cfg.WikiDir + "/assets/common.css"); err == nil {
http.ServeFile(w, rq, cfg.WikiDir+"/assets/common.css")
} else {
w.Header().Set("Content-Type", "text/css;charset=utf-8")
w.Write([]byte(assets.DefaultCSS()))
}
if bytes, err := ioutil.ReadFile(cfg.WikiDir + "/assets/custom.css"); err == nil {
w.Write(bytes)
}
}
func handlerToolbar(w http.ResponseWriter, rq *http.Request) {
util.PrepareRq(rq)
w.Header().Set("Content-Type", "text/javascript;charset=utf-8")
w.Write([]byte(assets.ToolbarJS()))
}
// handlerIcon serves the requested icon. All icons are distributed as part of the Mycorrhiza binary.
//
// See assets/assets/icon/ for icons themselves, see assets/assets.qtpl for their sources.
func handlerIcon(w http.ResponseWriter, rq *http.Request) {
iconName := strings.TrimPrefix(rq.URL.Path, "/assets/icon/")
if iconName == "https" {
iconName = "http"
}
w.Header().Set("Content-Type", "image/svg+xml")
icon := func() string {
switch iconName {
case "gemini":
return assets.IconGemini()
case "mailto":
return assets.IconMailto()
case "gopher":
return assets.IconGopher()
case "feed":
return assets.IconFeed()
default:
return assets.IconHTTP()
}
}()
_, err := io.WriteString(w, icon)
if err != nil {
log.Println(err)
}
}
func handlerUserList(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(views.BaseHTML("User list", views.UserListHTML(), user.FromRequest(rq))))
}
func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte(
`User-agent: *
Allow: /page/
Allow: /recent-changes
Disallow: /
Crawl-delay: 5`))
}
func Init() {
initAdmin()
initReaders()
initMutators()
initAuth()
initHistory()
initStuff()
http.HandleFunc("/user-list/", handlerUserList)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(cfg.WikiDir+"/static"))))
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
http.ServeFile(w, rq, cfg.WikiDir+"/static/favicon.ico")
})
http.HandleFunc("/assets/common.css", handlerStyle)
http.HandleFunc("/assets/toolbar.js", handlerToolbar)
http.HandleFunc("/assets/icon/", handlerIcon)
http.HandleFunc("/robots.txt", handlerRobotsTxt)
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
addr, _ := url.Parse("/hypha/" + cfg.HomeHypha) // Let's pray it never fails
rq.URL = addr
handlerHypha(w, rq)
})
}