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
hyphae/*.gog
metarrhiza
example-wiki
# go editors and IDEA folders
.idea/
# VScode and IDEA folders
.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
./mycorrhiza metarrhiza
./mycorrhiza ${WIKI}
config_run: build
./mycorrhiza -config-path "assets/config.ini" metarrhiza
./mycorrhiza -config-path "assets/config.ini" ${WIKI}
devconfig_run: build
./mycorrhiza -config-path "assets/devconfig.ini" metarrhiza
./mycorrhiza -config-path "assets/devconfig.ini" ${WIKI}
build:
go generate

View File

@ -1,18 +1,10 @@
# 🍄 MycorrhizaWiki 1.1
# 🍄 MycorrhizaWiki 1.2
A wiki engine.
[Main wiki](https://mycorrhiza.lesarbr.es)
## Building
Also see [detailed instructions](https://mycorrhiza.lesarbr.es/hypha/guide/deployment) on 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.
```
See [the guide](https://mycorrhiza.lesarbr.es/hypha/guide/deployment) on the wiki.
## Installing

View File

@ -113,16 +113,18 @@ func StreamDefaultCSS(qw422016 *qt422016.Writer) {
qw422016.N().S(`
`)
//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; }
.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_small { font-size: 1.5rem; }
.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__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 { 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; }
.hypha-tabs { padding: 0; margin: 0; }
@ -157,6 +159,12 @@ header { width: 100%; margin-bottom: 1rem; }
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 */
@media screen and (min-width: 801px) {
.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; }
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 { padding-left: 16px; } */
.wikilink_gopher::before { content: url("/static/icon/gopher"); }
.wikilink_http::before { content: url("/static/icon/http"); }
.wikilink_https::before { content: url("/static/icon/http"); }
/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */
.wikilink_gemini::before { content: url("/static/icon/gemini"); }
.wikilink_mailto::before { content: url("/static/icon/mailto"); }
.wikilink_gopher::before { content: url("/assets/icon/gopher"); }
.wikilink_http::before { content: url("/assets/icon/http"); }
.wikilink_https::before { content: url("/assets/icon/http"); }
/* .wikilink_https { background: transparent url("/assets/icon/http") center left no-repeat; } */
.wikilink_gemini::before { content: url("/assets/icon/gemini"); }
.wikilink_mailto::before { content: url("/assets/icon/mailto"); }
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; }
@ -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; }
article p { 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;}
.codeblock code {padding:0; font-size:15px;}
.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 */
.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-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; }
.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__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 */
/* 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 .codeblock,
.transclusion,
@ -336,18 +353,17 @@ table { background-color: #eee; }
.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 { background-color: #eee; }
.layout-card__title { border-bottom: 1px solid #eee; }
/* Other stuff */
html { background-color: #ddd;
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: #bbb; }
html { background-color: #eee;
}
header { background-color: #eee; }
.header-links__link { color: black; }
.header-links__link:hover { background-color: #eee; }
.header-links__link:hover { background-color: #ddd; }
main { background-color: white; }
blockquote { border-left: 4px black solid; }
blockquote { border-left: 2px #999 solid; }
.wikilink_new {color:#a55858;}
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
.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; }
.layout-card__title, .hypha-tabs__tab_active { background-color: #343434; }
blockquote { border-left: 4px #ddd solid; }
.transclusion .transclusion__link { color: #ddd; }
input[type="text"], input[type="password"],
::-webkit-file-upload-button,
.btn,
article code,
article .codeblock,
.transclusion,
@ -388,6 +407,11 @@ article .codeblock,
.upload-amnt,
textarea,
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 .codeblock { background-color: #454545; }
mark { background: rgba(130, 80, 30, 5); color: inherit; }
@ -484,7 +508,7 @@ const wrapBold = selectionWrapper(2, '**'),
//line assets/assets.qtpl:14
qw422016.N().S(`'),
wrapHighlighted = selectionWrapper(2, '!!'),
wrapLifted = selectionWrapper(1, '^'),
wrapLifted = selectionWrapper(2, '^^'),
wrapLowered = selectionWrapper(2, ',,'),
wrapStrikethrough = selectionWrapper(2, '~~'),
wrapLink = selectionWrapper(2, '[[', ']]')
@ -529,6 +553,11 @@ function insertDate() {
textInserter(date)()
}
function insertTimeUTC() {
let time = new Date().toISOString().substring(11, 19) + " UTC"
textInserter(time)()
}
function insertUserlink() {
const userlink = document.querySelector('.header-links__entry_user a')
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; }
.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_small { font-size: 1.5rem; }
.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__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 { 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; }
.hypha-tabs { padding: 0; margin: 0; }
@ -42,6 +44,12 @@ header { width: 100%; margin-bottom: 1rem; }
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 */
@media screen and (min-width: 801px) {
.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; }
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 { padding-left: 16px; } */
.wikilink_gopher::before { content: url("/static/icon/gopher"); }
.wikilink_http::before { content: url("/static/icon/http"); }
.wikilink_https::before { content: url("/static/icon/http"); }
/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */
.wikilink_gemini::before { content: url("/static/icon/gemini"); }
.wikilink_mailto::before { content: url("/static/icon/mailto"); }
.wikilink_gopher::before { content: url("/assets/icon/gopher"); }
.wikilink_http::before { content: url("/assets/icon/http"); }
.wikilink_https::before { content: url("/assets/icon/http"); }
/* .wikilink_https { background: transparent url("/assets/icon/http") center left no-repeat; } */
.wikilink_gemini::before { content: url("/assets/icon/gemini"); }
.wikilink_mailto::before { content: url("/assets/icon/mailto"); }
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; }
@ -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; }
article p { 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;}
.codeblock code {padding:0; font-size:15px;}
.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 */
.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-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; }
.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__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__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__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 */
/* 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 .codeblock,
.transclusion,
@ -221,18 +238,17 @@ table { background-color: #eee; }
.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 { background-color: #eee; }
.layout-card__title { border-bottom: 1px solid #eee; }
/* Other stuff */
html { background-color: #ddd;
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: #bbb; }
html { background-color: #eee;
}
header { background-color: #eee; }
.header-links__link { color: black; }
.header-links__link:hover { background-color: #eee; }
.header-links__link:hover { background-color: #ddd; }
main { background-color: white; }
blockquote { border-left: 4px black solid; }
blockquote { border-left: 2px #999 solid; }
.wikilink_new {color:#a55858;}
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
.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; }
.layout-card__title, .hypha-tabs__tab_active { background-color: #343434; }
blockquote { border-left: 4px #ddd solid; }
.transclusion .transclusion__link { color: #ddd; }
input[type="text"], input[type="password"],
::-webkit-file-upload-button,
.btn,
article code,
article .codeblock,
.transclusion,
@ -273,6 +292,11 @@ article .codeblock,
.upload-amnt,
textarea,
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 .codeblock { background-color: #454545; }
mark { background: rgba(130, 80, 30, 5); color: inherit; }

View File

@ -2,7 +2,7 @@ WikiName = Mycorrhiza (dev)
NaviTitleIcon = 🧑‍💻
[Hyphae]
HomeHypha = home
HomeHypha = mycorrhiza_wiki
UserHypha = u
HeaderLinksHypha = header-links
@ -17,3 +17,8 @@ FixedAuthCredentialsPath = mycocredentials.json
UseRegistration = true
RegistrationCredentialsPath = mycoregistration.json
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, '//'),
wrapMonospace = selectionWrapper(1, '`'),
wrapHighlighted = selectionWrapper(2, '!!'),
wrapLifted = selectionWrapper(1, '^'),
wrapLifted = selectionWrapper(2, '^^'),
wrapLowered = selectionWrapper(2, ',,'),
wrapStrikethrough = selectionWrapper(2, '~~'),
wrapLink = selectionWrapper(2, '[[', ']]')
@ -63,6 +63,11 @@ function insertDate() {
textInserter(date)()
}
function insertTimeUTC() {
let time = new Date().toISOString().substring(11, 19) + " UTC"
textInserter(time)()
}
function insertUserlink() {
const userlink = document.querySelector('.header-links__entry_user a')
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
import (
"errors"
"fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"path/filepath"
"strings"
"github.com/adrg/xdg"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/mitchellh/go-homedir"
)
@ -17,9 +18,20 @@ var paths struct {
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 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.
func CalculatePaths() error {
@ -49,7 +61,7 @@ func tokenStoragePath() (string, error) {
if err != nil {
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 dir, nil
@ -57,7 +69,7 @@ func tokenStoragePath() (string, error) {
func registrationCredentialsPath() (string, error) {
var err error
path := util.RegistrationCredentialsPath
path := cfg.RegistrationCredentialsPath
if len(path) == 0 {
path, err = xdg.DataFile("mycorrhiza/registration.json")
@ -81,7 +93,7 @@ func registrationCredentialsPath() (string, error) {
func fixedCredentialsPath() (string, error) {
var err error
path := util.FixedCredentialsPath
path := cfg.FixedAuthCredentialsPath
if len(path) > 0 {
path, err = homedir.Expand(path)

43
flag.go
View File

@ -3,30 +3,38 @@ package main
import (
"flag"
"fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"log"
"os"
"path/filepath"
"github.com/bouncepaw/mycorrhiza/assets"
"github.com/bouncepaw/mycorrhiza/util"
)
// CLI options are read and parsed here.
var printExampleConfig bool
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.Usage = func() {
fmt.Fprintf(
flag.CommandLine.Output(),
assets.HelpMessage(),
os.Args[0],
)
flag.PrintDefaults()
}
flag.Usage = printHelp
}
// 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() {
flag.Parse()
@ -40,18 +48,9 @@ func parseCliArgs() {
log.Fatal("Error: pass a wiki directory")
}
var err error
WikiDir, err = filepath.Abs(args[0])
util.WikiDir = WikiDir
wikiDir, err := filepath.Abs(args[0])
cfg.WikiDir = wikiDir
if err != nil {
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
// 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 (
"crypto/tls"
"crypto/x509/pkix"
"io"
"io/ioutil"
"log"
"path/filepath"
"strings"
"time"
"git.sr.ht/~adnano/go-gemini"
"git.sr.ht/~adnano/go-gemini/certificate"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
)
func geminiHomeHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
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.
Visit home hypha:
=> /hypha/` + util.HomePage))
=> /hypha/`+cfg.HomeHypha)
}
func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
@ -37,21 +44,20 @@ func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
if h.Exists {
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
if errT == nil {
md := markup.Doc(hyphaName, string(fileContentsT))
contents = md.AsGemtext()
contents = string(fileContentsT)
}
}
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() {
if util.GeminiCertPath == "" {
if cfg.GeminiCertificatePath == "" {
return
}
certPath, err := filepath.Abs(util.GeminiCertPath)
certPath, err := filepath.Abs(cfg.GeminiCertificatePath)
if err != nil {
log.Fatal(err)
}
@ -82,3 +88,15 @@ func handleGemini() {
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 (
git.sr.ht/~adnano/go-gemini v0.1.13
github.com/adrg/xdg v0.2.2
github.com/bouncepaw/mycomarkup v0.4.5
github.com/go-ini/ini v1.62.0
github.com/gorilla/feeds v1.1.1
github.com/kr/pretty v0.2.1 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/smartystreets/goconvey v1.6.4 // indirect
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
)

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/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY=
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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-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-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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
import (
"bytes"
"fmt"
"html"
"log"
"os/exec"
"regexp"
@ -10,6 +12,7 @@ import (
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/util"
)
@ -19,20 +22,18 @@ var gitpath string
var renameMsgPattern = regexp.MustCompile(`^Rename (.*) to .*`)
// Start finds git and initializes git credentials.
func Start(wikiDir string) {
func Start() {
path, err := exec.LookPath("git")
if err != nil {
log.Fatal("Cound not find the git executable. Check your $PATH.")
} else {
log.Println("Git path is", path)
log.Fatal("Could not find the git executable. Check your $PATH.")
}
gitpath = path
_, err = gitsh("config", "user.name", "wikimind")
_, err = silentGitsh("config", "user.name", "wikimind")
if err != nil {
log.Fatal(err)
}
_, err = gitsh("config", "user.email", "wikimind@mycorrhiza")
_, err = silentGitsh("config", "user.email", "wikimind@mycorrhiza")
if err != nil {
log.Fatal(err)
}
@ -44,9 +45,26 @@ type Revision struct {
Username string
Time time.Time
Message string
filesAffectedBuf []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
func (rev *Revision) hyphaeAffected() (hyphae []string) {
if nil != rev.hyphaeAffectedBuf {
@ -54,8 +72,6 @@ func (rev *Revision) hyphaeAffected() (hyphae []string) {
}
hyphae = make([]string, 0)
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 = make(map[string]bool)
isNewName = func(hyphaName string) bool {
@ -65,11 +81,9 @@ func (rev *Revision) hyphaeAffected() (hyphae []string) {
set[hyphaName] = true
return true
}
filesAffected = rev.filesAffected()
)
if err != nil {
return hyphae
}
for _, filename := range strings.Split(out.String(), "\n") {
for _, filename := range filesAffected {
if strings.IndexRune(filename, '.') >= 0 {
dotPos := strings.LastIndexByte(filename, '.')
hyphaName := string([]byte(filename)[0:dotPos]) // is it safe?
@ -94,15 +108,44 @@ func (rev Revision) HyphaeLinksHTML() (html string) {
if i > 0 {
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
}
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(
`<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.
@ -113,11 +156,11 @@ func (rev *Revision) bestLink() string {
)
switch {
case renameRes != nil:
return "/page/" + renameRes[1]
return "/hypha/" + renameRes[1]
case len(revs) == 0:
return ""
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) {
fmt.Printf("$ %v\n", args)
cmd := exec.Command(gitpath, args...)
cmd.Dir = util.WikiDir
cmd.Dir = cfg.WikiDir
b, err := cmd.CombinedOutput()
if err != nil {
@ -135,6 +178,15 @@ func gitsh(args ...string) (out bytes.Buffer, err error) {
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.
func unixTimestampAsTime(ts string) *time.Time {
i, err := strconv.ParseInt(ts, 10, 64)

View File

@ -1,28 +1,29 @@
// information.go
// Things related to gathering existing information.
package history
// information.go
// Things related to gathering existing information.
import (
"fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"log"
"regexp"
"strconv"
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/gorilla/feeds"
)
func recentChangesFeed() *feeds.Feed {
feed := &feeds.Feed{
Title: "Recent changes",
Link: &feeds.Link{Href: util.URL},
Link: &feeds.Link{Href: cfg.URL},
Description: "List of 30 recent changes on the wiki",
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
Updated: time.Now(),
}
var (
out, err = gitsh(
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
"--max-count=30",
@ -34,6 +35,7 @@ func recentChangesFeed() *feeds.Feed {
revs = append(revs, parseRevisionLine(line))
}
}
log.Printf("Found %d recent changes", len(revs))
for _, rev := range revs {
feed.Add(&feeds.Item{
Title: rev.Message,
@ -42,7 +44,7 @@ func recentChangesFeed() *feeds.Feed {
Description: rev.descriptionForFeed(),
Created: rev.Time,
Updated: rev.Time,
Link: &feeds.Link{Href: util.URL + rev.bestLink()},
Link: &feeds.Link{Href: cfg.URL + rev.bestLink()},
})
}
return feed
@ -62,7 +64,7 @@ func RecentChangesJSON() (string, error) {
func RecentChanges(n int) []Revision {
var (
out, err = gitsh(
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
"--max-count="+strconv.Itoa(n),
@ -74,6 +76,7 @@ func RecentChanges(n int) []Revision {
revs = append(revs, parseRevisionLine(line))
}
}
log.Printf("Found %d recent changes", len(revs))
return revs
}
@ -86,7 +89,7 @@ func FileChanged(path string) bool {
// Revisions returns slice of revisions for the given hypha name.
func Revisions(hyphaName string) ([]Revision, error) {
var (
out, err = gitsh(
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
// Hash, author email, author time, commit msg separated by tab
"--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
}
@ -137,7 +141,7 @@ func (rev *Revision) asHistoryEntry(hyphaName string) (html string) {
author := ""
if rev.Username != "anon" {
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(`
<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) {
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
}
// 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) {
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
}

View File

@ -1,7 +1,7 @@
// history/operations.go
// Things related to writing history.
package history
// history/operations.go
// Things related to writing history.
import (
"fmt"
"os"
@ -84,7 +84,10 @@ func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp {
func (hop *HistoryOp) WithFilesRenamed(pairs map[string]string) *HistoryOp {
for from, to := range pairs {
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 {
hop.Errs = append(hop.Errs, err)
}

View File

@ -27,6 +27,8 @@ func Index(path string) {
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.

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
import (
@ -8,7 +8,7 @@ import (
)
// HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"'&%{}]+`)
type Hypha struct {
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=views
//go:generate qtc -dir=tree
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
package main
import (
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"strings"
"github.com/bouncepaw/mycorrhiza/assets"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/history"
"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"
"github.com/bouncepaw/mycorrhiza/web"
"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() {
parseCliArgs()
// It is ok if the path is ""
util.ReadConfigFile(util.ConfigFilePath)
cfg.ReadConfigFile()
if err := files.CalculatePaths(); err != nil {
log.Fatal(err)
}
log.Println("Running MycorrhizaWiki")
if err := os.Chdir(WikiDir); err != nil {
log.Println("Running MycorrhizaWiki 1.2.0 indev")
if err := os.Chdir(cfg.WikiDir); err != nil {
log.Fatal(err)
}
log.Println("Wiki storage directory is", WikiDir)
hyphae.Index(WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae")
log.Println("Wiki storage directory is", cfg.WikiDir)
// Initialize user database
// Init the subsystems:
hyphae.Index(cfg.WikiDir)
user.InitUserDatabase()
history.Start(WikiDir)
history.Start()
shroom.SetHeaderLinks()
// Network:
go handleGemini()
// See http_admin.go for /admin, /admin/*
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))
web.Init()
log.Fatal(http.ListenAndServe("0.0.0.0:"+cfg.HTTPPort, 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"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
"github.com/bouncepaw/mycomarkup/globals"
)
func init() {
markup.HyphaExists = func(hyphaName string) bool {
globals.HyphaExists = func(hyphaName string) bool {
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 {
rawText, err = FetchTextPart(h)
if h.BinaryPath != "" {
@ -24,15 +24,9 @@ func init() {
}
return
}
markup.HyphaIterate = func(λ func(string)) {
globals.HyphaIterate = func(λ func(string)) {
for h := range hyphae.YieldExistingHyphae() {
λ(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 (
"errors"
"fmt"
"github.com/bouncepaw/mycorrhiza/cfg"
"io/ioutil"
"log"
"mime/multipart"
@ -13,7 +14,6 @@ import (
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/mimetype"
"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) {
@ -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
func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) {
var (
fullPath = filepath.Join(util.WikiDir, h.Name+ext)
fullPath = filepath.Join(cfg.WikiDir, h.Name+ext)
originalFullPath = &h.TextPath
)
if hop.Type == history.TypeEditBinary {

View File

@ -4,9 +4,8 @@ import (
"io/ioutil"
"os"
"github.com/bouncepaw/mycorrhiza/cfg"
"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.
@ -24,15 +23,15 @@ func FetchTextPart(h *hyphae.Hypha) (string, error) {
}
func SetHeaderLinks() {
if userLinksHypha := hyphae.ByName(util.HeaderLinksHypha); !userLinksHypha.Exists {
util.SetDefaultHeaderLinks()
if userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha); !userLinksHypha.Exists {
cfg.SetDefaultHeaderLinks()
} else {
contents, err := ioutil.ReadFile(userLinksHypha.TextPath)
if err != nil || len(contents) == 0 {
util.SetDefaultHeaderLinks()
cfg.SetDefaultHeaderLinks()
} else {
text := string(contents)
util.ParseHeaderLinks(text, markup.Rocketlink)
cfg.ParseHeaderLinks(text)
}
}
}

View File

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

View File

@ -2,6 +2,7 @@ package user
import (
"errors"
"github.com/bouncepaw/mycorrhiza/cfg"
"log"
"net/http"
"strconv"
@ -39,8 +40,8 @@ func Register(username, password string) error {
username = util.CanonicalName(username)
log.Println("Attempt to register user", username)
switch {
case CountRegistered() >= util.LimitRegistration && util.LimitRegistration > 0:
i := strconv.Itoa(util.LimitRegistration)
case CountRegistered() >= cfg.LimitRegistration && cfg.LimitRegistration > 0:
i := strconv.Itoa(cfg.LimitRegistration)
log.Println("Limit reached: " + i)
return errors.New("Reached the limit of registered users: " + i)
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 (
"crypto/rand"
"encoding/hex"
"log"
"net/http"
"regexp"
"strings"
"unicode"
"github.com/bouncepaw/mycorrhiza/cfg"
)
// TODO: make names match to fields of config file
var (
SiteName string
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
)
func PrepareRq(rq *http.Request) {
log.Println(rq.RequestURI)
rq.URL.Path = strings.TrimSuffix(rq.URL.Path, "/")
}
// LettersNumbersOnly keeps letters and numbers only in the given 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.
func ShorterPath(path string) string {
if strings.HasPrefix(path, WikiDir) {
tmp := strings.TrimPrefix(path, WikiDir)
if strings.HasPrefix(path, cfg.WikiDir) {
tmp := strings.TrimPrefix(path, cfg.WikiDir)
if tmp == "" {
return ""
}
@ -103,9 +88,9 @@ func CanonicalName(name string) string {
}
// 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.
func IsCanonicalName(name string) bool {
@ -113,5 +98,17 @@ func IsCanonicalName(name 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 "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% func RegisterHTML(rq *http.Request) %}
<div class="layout">
<main class="main-width">
<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">
<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>
<br>
@ -20,16 +20,16 @@
<input type="password" required name="password">
<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>
<input class="modal__action modal__submit" type="submit">
<a class="modal__action modal__cancel" href="/">Cancel</a>
<input class="btn" type="submit" value="Register">
<a class="btn btn_weak" href="/{%s rq.URL.RawQuery %}">Cancel</a>
</fieldset>
</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><a href="{%s rq.URL.RawQuery %}">← Go back</a></p>
<p><a href="/{%s rq.URL.RawQuery %}">← Go back</a></p>
{% else %}
<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 %}
</section>
</main>
@ -43,8 +43,7 @@
{% if user.AuthUsed %}
<form class="modal" method="post" action="/login-data" id="login-form" enctype="multipart/form-data" autocomplete="on">
<fieldset class="modal__fieldset">
<legend class="modal__title">Log in to {%s util.SiteName %}</legend>
<p>Use the data you were given by an administrator.</p>
<legend class="modal__title">Log in to {%s cfg.WikiName %}</legend>
<label for="login-form__username">Username</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
@ -53,8 +52,8 @@
<br>
<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>
<input class="modal__action modal__submit" type="submit">
<a class="modal__action modal__cancel" href="/">Cancel</a>
<input class="btn" type="submit" value="Log in">
<a class="btn btn_weak" href="/">Cancel</a>
</fieldset>
</form>
{% else %}

View File

@ -11,7 +11,7 @@ import "net/http"
import "github.com/bouncepaw/mycorrhiza/user"
//line views/auth.qtpl:3
import "github.com/bouncepaw/mycorrhiza/util"
import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/auth.qtpl:5
import (
@ -35,7 +35,7 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
<section>
`)
//line views/auth.qtpl:9
if util.UseRegistration {
if cfg.UseRegistration {
//line views/auth.qtpl:9
qw422016.N().S(`
<form class="modal" method="post" action="/register?`)
@ -46,7 +46,7 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
<fieldset class="modal__fieldset">
<legend class="modal__title">Register to `)
//line views/auth.qtpl:12
qw422016.E().S(util.SiteName)
qw422016.E().S(cfg.WikiName)
//line views/auth.qtpl:12
qw422016.N().S(`</legend>
@ -59,17 +59,21 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
<input type="password" required name="password">
<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>
<input class="modal__action modal__submit" type="submit">
<a class="modal__action modal__cancel" href="/">Cancel</a>
<input class="btn" type="submit" value="Register">
<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>
</form>
`)
//line views/auth.qtpl:27
} else if util.UseFixedAuth {
} else if cfg.UseFixedAuth {
//line views/auth.qtpl:27
qw422016.N().S(`
<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
qw422016.E().S(rq.URL.RawQuery)
//line views/auth.qtpl:29
@ -80,7 +84,7 @@ func StreamRegisterHTML(qw422016 *qt422016.Writer, rq *http.Request) {
//line views/auth.qtpl:30
qw422016.N().S(`
<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
qw422016.E().S(rq.URL.RawQuery)
//line views/auth.qtpl:32
@ -139,10 +143,9 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
<fieldset class="modal__fieldset">
<legend class="modal__title">Log in to `)
//line views/auth.qtpl:46
qw422016.E().S(util.SiteName)
qw422016.E().S(cfg.WikiName)
//line views/auth.qtpl:46
qw422016.N().S(`</legend>
<p>Use the data you were given by an administrator.</p>
<label for="login-form__username">Username</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
@ -151,182 +154,182 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
<br>
<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>
<input class="modal__action modal__submit" type="submit">
<a class="modal__action modal__cancel" href="/">Cancel</a>
<input class="btn" type="submit" value="Log in">
<a class="btn btn_weak" href="/">Cancel</a>
</fieldset>
</form>
`)
//line views/auth.qtpl:60
//line views/auth.qtpl:59
} else {
//line views/auth.qtpl:60
//line views/auth.qtpl:59
qw422016.N().S(`
<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>
`)
//line views/auth.qtpl:63
//line views/auth.qtpl:62
}
//line views/auth.qtpl:63
//line views/auth.qtpl:62
qw422016.N().S(`
</section>
</main>
</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) {
//line views/auth.qtpl:67
//line views/auth.qtpl:66
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:67
//line views/auth.qtpl:66
StreamLoginHTML(qw422016)
//line views/auth.qtpl:67
//line views/auth.qtpl:66
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 {
//line views/auth.qtpl:67
//line views/auth.qtpl:66
qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:67
//line views/auth.qtpl:66
WriteLoginHTML(qb422016)
//line views/auth.qtpl:67
//line views/auth.qtpl:66
qs422016 := string(qb422016.B)
//line views/auth.qtpl:67
//line views/auth.qtpl:66
qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:67
//line views/auth.qtpl:66
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) {
//line views/auth.qtpl:69
//line views/auth.qtpl:68
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
`)
//line views/auth.qtpl:73
//line views/auth.qtpl:72
switch err {
//line views/auth.qtpl:74
//line views/auth.qtpl:73
case "unknown username":
//line views/auth.qtpl:74
//line views/auth.qtpl:73
qw422016.N().S(`
<p class="error">Unknown username.</p>
`)
//line views/auth.qtpl:76
//line views/auth.qtpl:75
case "wrong password":
//line views/auth.qtpl:76
//line views/auth.qtpl:75
qw422016.N().S(`
<p class="error">Wrong password.</p>
`)
//line views/auth.qtpl:78
//line views/auth.qtpl:77
default:
//line views/auth.qtpl:78
//line views/auth.qtpl:77
qw422016.N().S(`
<p class="error">`)
//line views/auth.qtpl:79
//line views/auth.qtpl:78
qw422016.E().S(err)
//line views/auth.qtpl:79
//line views/auth.qtpl:78
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(`
<p><a href="/login"> Try again</a></p>
</section>
</main>
</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) {
//line views/auth.qtpl:85
//line views/auth.qtpl:84
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:85
//line views/auth.qtpl:84
StreamLoginErrorHTML(qw422016, err)
//line views/auth.qtpl:85
//line views/auth.qtpl:84
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 {
//line views/auth.qtpl:85
//line views/auth.qtpl:84
qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:85
//line views/auth.qtpl:84
WriteLoginErrorHTML(qb422016, err)
//line views/auth.qtpl:85
//line views/auth.qtpl:84
qs422016 := string(qb422016.B)
//line views/auth.qtpl:85
//line views/auth.qtpl:84
qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:85
//line views/auth.qtpl:84
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) {
//line views/auth.qtpl:87
//line views/auth.qtpl:86
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
`)
//line views/auth.qtpl:91
//line views/auth.qtpl:90
if can {
//line views/auth.qtpl:91
//line views/auth.qtpl:90
qw422016.N().S(`
<h1>Log out?</h1>
<p><a href="/logout-confirm"><strong>Confirm</strong></a></p>
<p><a href="/">Cancel</a></p>
`)
//line views/auth.qtpl:95
//line views/auth.qtpl:94
} else {
//line views/auth.qtpl:95
//line views/auth.qtpl:94
qw422016.N().S(`
<p>You cannot log out because you are not logged in.</p>
<p><a href="/login">Login</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(`
</section>
</main>
</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) {
//line views/auth.qtpl:103
//line views/auth.qtpl:102
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:103
//line views/auth.qtpl:102
StreamLogoutHTML(qw422016, can)
//line views/auth.qtpl:103
//line views/auth.qtpl:102
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 {
//line views/auth.qtpl:103
//line views/auth.qtpl:102
qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:103
//line views/auth.qtpl:102
WriteLogoutHTML(qb422016, can)
//line views/auth.qtpl:103
//line views/auth.qtpl:102
qs422016 := string(qb422016.B)
//line views/auth.qtpl:103
//line views/auth.qtpl:102
qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:103
//line views/auth.qtpl:102
return qs422016
//line views/auth.qtpl:103
//line views/auth.qtpl:102
}

View File

@ -1,5 +1,6 @@
{% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
@ -44,7 +45,7 @@ if err != nil {
recent changes
</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 %}
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__hash">{%s rev.Hash %}</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 %}
{% func HistoryHTML(rq *http.Request, hyphaName, list string) %}

View File

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

View File

@ -1,8 +1,47 @@
{% import "path/filepath" %}
{% import "strings" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% 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) %}
{% code
var (
@ -12,8 +51,8 @@
%}
<h1 class="navi-title">
{% stripspace %}
<a href="/hypha/{%s util.HomePage %}">
{%-s= util.SiteNavIcon -%}
<a href="/hypha/{%s cfg.HomeHypha %}">
{%-s= cfg.NaviTitleIcon -%}
<span aria-hidden="true" class="navi-title__colon">:</span>
</a>

View File

@ -10,224 +10,325 @@ import "path/filepath"
//line views/hypha.qtpl:2
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"
//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"
//line views/hypha.qtpl:6
//line views/hypha.qtpl:9
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/hypha.qtpl:6
//line views/hypha.qtpl:9
var (
_ = qtio422016.Copy
_ = 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) {
//line views/hypha.qtpl:6
//line views/hypha.qtpl:45
qw422016.N().S(`
`)
//line views/hypha.qtpl:8
//line views/hypha.qtpl:47
var (
prevAcc = "/hypha/"
parts = strings.Split(h.Name, "/")
)
//line views/hypha.qtpl:12
//line views/hypha.qtpl:51
qw422016.N().S(`
<h1 class="navi-title">
`)
//line views/hypha.qtpl:14
//line views/hypha.qtpl:53
qw422016.N().S(`<a href="/hypha/`)
//line views/hypha.qtpl:15
qw422016.E().S(util.HomePage)
//line views/hypha.qtpl:15
//line views/hypha.qtpl:54
qw422016.E().S(cfg.HomeHypha)
//line views/hypha.qtpl:54
qw422016.N().S(`">`)
//line views/hypha.qtpl:16
qw422016.N().S(util.SiteNavIcon)
//line views/hypha.qtpl:16
//line views/hypha.qtpl:55
qw422016.N().S(cfg.NaviTitleIcon)
//line views/hypha.qtpl:55
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 {
//line views/hypha.qtpl:21
//line views/hypha.qtpl:60
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>`)
//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="`)
//line views/hypha.qtpl:25
//line views/hypha.qtpl:64
qw422016.E().S(prevAcc + part)
//line views/hypha.qtpl:25
//line views/hypha.qtpl:64
qw422016.N().S(`" rel="`)
//line views/hypha.qtpl:25
//line views/hypha.qtpl:64
if i == len(parts)-1 {
//line views/hypha.qtpl:25
//line views/hypha.qtpl:64
qw422016.N().S(`bookmark`)
//line views/hypha.qtpl:25
//line views/hypha.qtpl:64
} else {
//line views/hypha.qtpl:25
//line views/hypha.qtpl:64
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(`">`)
//line views/hypha.qtpl:26
//line views/hypha.qtpl:65
qw422016.N().S(util.BeautifulName(part))
//line views/hypha.qtpl:26
//line views/hypha.qtpl:65
qw422016.N().S(`</a>`)
//line views/hypha.qtpl:28
//line views/hypha.qtpl:67
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(`
</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) {
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
StreamNaviTitleHTML(qw422016, h)
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
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 {
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
WriteNaviTitleHTML(qb422016, h)
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
qs422016 := string(qb422016.B)
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:32
//line views/hypha.qtpl:71
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) {
//line views/hypha.qtpl:34
//line views/hypha.qtpl:73
qw422016.N().S(`
`)
//line views/hypha.qtpl:35
//line views/hypha.qtpl:74
switch filepath.Ext(h.BinaryPath) {
//line views/hypha.qtpl:37
//line views/hypha.qtpl:76
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
//line views/hypha.qtpl:37
//line views/hypha.qtpl:76
qw422016.N().S(`
<div class="binary-container binary-container_with-img">
<a href="/binary/`)
//line views/hypha.qtpl:39
//line views/hypha.qtpl:78
qw422016.N().S(h.Name)
//line views/hypha.qtpl:39
//line views/hypha.qtpl:78
qw422016.N().S(`"><img src="/binary/`)
//line views/hypha.qtpl:39
//line views/hypha.qtpl:78
qw422016.N().S(h.Name)
//line views/hypha.qtpl:39
//line views/hypha.qtpl:78
qw422016.N().S(`"/></a>
</div>
`)
//line views/hypha.qtpl:42
//line views/hypha.qtpl:81
case ".ogg", ".webm", ".mp4":
//line views/hypha.qtpl:42
//line views/hypha.qtpl:81
qw422016.N().S(`
<div class="binary-container binary-container_with-video">
<video controls>
<source src="/binary/`)
//line views/hypha.qtpl:45
//line views/hypha.qtpl:84
qw422016.N().S(h.Name)
//line views/hypha.qtpl:45
//line views/hypha.qtpl:84
qw422016.N().S(`"/>
<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)
//line views/hypha.qtpl:46
//line views/hypha.qtpl:85
qw422016.N().S(`">Download video</a></p>
</video>
</div>
`)
//line views/hypha.qtpl:50
//line views/hypha.qtpl:89
case ".mp3":
//line views/hypha.qtpl:50
//line views/hypha.qtpl:89
qw422016.N().S(`
<div class="binary-container binary-container_with-audio">
<audio controls>
<source src="/binary/`)
//line views/hypha.qtpl:53
//line views/hypha.qtpl:92
qw422016.N().S(h.Name)
//line views/hypha.qtpl:53
//line views/hypha.qtpl:92
qw422016.N().S(`"/>
<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)
//line views/hypha.qtpl:54
//line views/hypha.qtpl:93
qw422016.N().S(`">Download audio</a></p>
</audio>
</div>
`)
//line views/hypha.qtpl:58
//line views/hypha.qtpl:97
default:
//line views/hypha.qtpl:58
//line views/hypha.qtpl:97
qw422016.N().S(`
<div class="binary-container binary-container_with-nothing">
<p><a href="/binary/`)
//line views/hypha.qtpl:60
//line views/hypha.qtpl:99
qw422016.N().S(h.Name)
//line views/hypha.qtpl:60
//line views/hypha.qtpl:99
qw422016.N().S(`">Download media</a></p>
</div>
`)
//line views/hypha.qtpl:62
//line views/hypha.qtpl:101
}
//line views/hypha.qtpl:62
//line views/hypha.qtpl:101
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) {
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
StreamAttachmentHTML(qw422016, h)
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
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 {
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
WriteAttachmentHTML(qb422016, h)
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
qs422016 := string(qb422016.B)
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
return qs422016
//line views/hypha.qtpl:63
//line views/hypha.qtpl:102
}

View File

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

View File

@ -306,7 +306,7 @@ func modalBegin(path, hyphaName, formAttrs, legend string) string {
func streammodalEnd(qw422016 *qt422016.Writer, hyphaName string, shouldFocusOnConfirm bool) {
//line views/modal.qtpl:56
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
if shouldFocusOnConfirm {
//line views/modal.qtpl:57
@ -319,7 +319,7 @@ func streammodalEnd(qw422016 *qt422016.Writer, hyphaName string, shouldFocusOnCo
//line views/modal.qtpl:58
qw422016.E().S(hyphaName)
//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>
</form>
</main>

View File

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

View File

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

View File

@ -3,6 +3,7 @@
{% import "path" %}
{% import "os" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/mimetype" %}
{% import "github.com/bouncepaw/mycorrhiza/tree" %}
@ -46,12 +47,14 @@
{% if u.CanProceed("upload-binary") %}
<form action="/upload-binary/{%s h.Name %}"
method="post" enctype="multipart/form-data"
class="modal amnt-menu-block">
<fieldset class="modal__fieldset upload-binary">
class="upload-binary modal amnt-menu-block">
<fieldset class="modal__fieldset">
<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>
<input type="file" class="upload-binary__input" name="binary">
<input type="submit" class="modal__action modal__submit">
<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">
</fieldset>
</form>
{% endif %}
@ -61,7 +64,7 @@
<fieldset class="modal__fieldset">
<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>
<input type="submit" class="modal__action modal__submit">
<input type="submit" class="btn" value="Unattach">
</fieldset>
</form>
{% endif %}
@ -75,26 +78,17 @@ If `contents` == "", a helpful message is shown instead.
{% func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) %}
{% code
relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name)
u := user.FromRequest(rq)
%}
{%= NavHTML(rq, h.Name, "page") %}
<div class="layout">
<main class="main-width">
<article>
{%s= NaviTitleHTML(h) %}
{% if contents == "" %}
<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 %}
{% if h.Exists %}
{%s= contents %}
{% else %}
{%= nonExistentHyphaNotice(h, u) %}
{% endif %}
</article>
<section class="prevnext">
@ -109,6 +103,7 @@ If `contents` == "", a helpful message is shown instead.
</main>
{%= RelativeHyphaeHTML(relatives) %}
</div>
{%= viewScripts() %}
{% endfunc %}
{% func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) %}
@ -127,4 +122,11 @@ If `contents` == "", a helpful message is shown instead.
</main>
{%= RelativeHyphaeHTML(relatives) %}
</div>
{%= viewScripts() %}
{% 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"
//line views/readers.qtpl:6
import "github.com/bouncepaw/mycorrhiza/hyphae"
import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/readers.qtpl:7
import "github.com/bouncepaw/mycorrhiza/mimetype"
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/readers.qtpl:8
import "github.com/bouncepaw/mycorrhiza/tree"
import "github.com/bouncepaw/mycorrhiza/mimetype"
//line views/readers.qtpl:9
import "github.com/bouncepaw/mycorrhiza/user"
import "github.com/bouncepaw/mycorrhiza/tree"
//line views/readers.qtpl:10
import "github.com/bouncepaw/mycorrhiza/user"
//line views/readers.qtpl:11
import "github.com/bouncepaw/mycorrhiza/util"
//line views/readers.qtpl:12
//line views/readers.qtpl:13
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/readers.qtpl:12
//line views/readers.qtpl:13
var (
_ = qtio422016.Copy
_ = 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) {
//line views/readers.qtpl:12
//line views/readers.qtpl:13
qw422016.N().S(`
`)
//line views/readers.qtpl:13
//line views/readers.qtpl:14
StreamNavHTML(qw422016, rq, h.Name, "attachment")
//line views/readers.qtpl:13
//line views/readers.qtpl:14
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<h1>Attachment of `)
//line views/readers.qtpl:16
//line views/readers.qtpl:17
qw422016.E().S(util.BeautifulName(h.Name))
//line views/readers.qtpl:16
//line views/readers.qtpl:17
qw422016.N().S(`</h1>
`)
//line views/readers.qtpl:17
//line views/readers.qtpl:18
if h.BinaryPath == "" {
//line views/readers.qtpl:17
//line views/readers.qtpl:18
qw422016.N().S(`
<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 {
//line views/readers.qtpl:19
//line views/readers.qtpl:20
qw422016.N().S(`
<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(`
<section class="amnt-grid">
`)
//line views/readers.qtpl:25
//line views/readers.qtpl:26
if h.BinaryPath != "" {
//line views/readers.qtpl:25
//line views/readers.qtpl:26
qw422016.N().S(`
`)
//line views/readers.qtpl:27
//line views/readers.qtpl:28
mime := mimetype.FromExtension(path.Ext(h.BinaryPath))
fileinfo, err := os.Stat(h.BinaryPath)
//line views/readers.qtpl:28
//line views/readers.qtpl:29
qw422016.N().S(`
`)
//line views/readers.qtpl:29
//line views/readers.qtpl:30
if err == nil {
//line views/readers.qtpl:29
//line views/readers.qtpl:30
qw422016.N().S(`
<fieldset class="amnt-menu-block">
<legend class="modal__title modal__title_small">Stat</legend>
<p class="modal__confirmation-msg"><b>File size:</b> `)
//line views/readers.qtpl:32
//line views/readers.qtpl:33
qw422016.N().DL(fileinfo.Size())
//line views/readers.qtpl:32
//line views/readers.qtpl:33
qw422016.N().S(` bytes</p>
<p><b>MIME type:</b> `)
//line views/readers.qtpl:33
//line views/readers.qtpl:34
qw422016.E().S(mime)
//line views/readers.qtpl:33
//line views/readers.qtpl:34
qw422016.N().S(`</p>
</fieldset>
`)
//line views/readers.qtpl:35
//line views/readers.qtpl:36
}
//line views/readers.qtpl:35
//line views/readers.qtpl:36
qw422016.N().S(`
`)
//line views/readers.qtpl:37
//line views/readers.qtpl:38
if strings.HasPrefix(mime, "image/") {
//line views/readers.qtpl:37
//line views/readers.qtpl:38
qw422016.N().S(`
<fieldset class="amnt-menu-block">
<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>
<pre class="codebleck"><code>img { `)
//line views/readers.qtpl:41
//line views/readers.qtpl:42
qw422016.E().S(h.Name)
//line views/readers.qtpl:41
//line views/readers.qtpl:42
qw422016.N().S(` }</code></pre>
</fieldset>
`)
//line views/readers.qtpl:43
//line views/readers.qtpl:44
}
//line views/readers.qtpl:43
//line views/readers.qtpl:44
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(`
`)
//line views/readers.qtpl:46
//line views/readers.qtpl:47
if u.CanProceed("upload-binary") {
//line views/readers.qtpl:46
//line views/readers.qtpl:47
qw422016.N().S(`
<form action="/upload-binary/`)
//line views/readers.qtpl:47
//line views/readers.qtpl:48
qw422016.E().S(h.Name)
//line views/readers.qtpl:47
//line views/readers.qtpl:48
qw422016.N().S(`"
method="post" enctype="multipart/form-data"
class="modal amnt-menu-block">
<fieldset class="modal__fieldset upload-binary">
class="upload-binary modal amnt-menu-block">
<fieldset class="modal__fieldset">
<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>
<input type="file" class="upload-binary__input" name="binary">
<input type="submit" class="modal__action modal__submit">
<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">
</fieldset>
</form>
`)
//line views/readers.qtpl:57
//line views/readers.qtpl:60
}
//line views/readers.qtpl:57
//line views/readers.qtpl:60
qw422016.N().S(`
`)
//line views/readers.qtpl:59
//line views/readers.qtpl:62
if h.BinaryPath != "" && u.CanProceed("unattach-confirm") {
//line views/readers.qtpl:59
//line views/readers.qtpl:62
qw422016.N().S(`
<form action="/unattach-confirm/`)
//line views/readers.qtpl:60
//line views/readers.qtpl:63
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">
<fieldset class="modal__fieldset">
<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>
<input type="submit" class="modal__action modal__submit">
<input type="submit" class="btn" value="Unattach">
</fieldset>
</form>
`)
//line views/readers.qtpl:67
//line views/readers.qtpl:70
}
//line views/readers.qtpl:67
//line views/readers.qtpl:70
qw422016.N().S(`
</section>
</main>
</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) {
//line views/readers.qtpl:72
//line views/readers.qtpl:75
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:72
//line views/readers.qtpl:75
StreamAttachmentMenuHTML(qw422016, rq, h, u)
//line views/readers.qtpl:72
//line views/readers.qtpl:75
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 {
//line views/readers.qtpl:72
//line views/readers.qtpl:75
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:72
//line views/readers.qtpl:75
WriteAttachmentMenuHTML(qb422016, rq, h, u)
//line views/readers.qtpl:72
//line views/readers.qtpl:75
qs422016 := string(qb422016.B)
//line views/readers.qtpl:72
//line views/readers.qtpl:75
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:72
//line views/readers.qtpl:75
return qs422016
//line views/readers.qtpl:72
//line views/readers.qtpl:75
}
// 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) {
//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
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")
//line views/readers.qtpl:79
//line views/readers.qtpl:83
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<article>
`)
//line views/readers.qtpl:83
//line views/readers.qtpl:87
qw422016.N().S(NaviTitleHTML(h))
//line views/readers.qtpl:83
//line views/readers.qtpl:87
qw422016.N().S(`
`)
//line views/readers.qtpl:84
if contents == "" {
//line views/readers.qtpl:84
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
//line views/readers.qtpl:88
if h.Exists {
//line views/readers.qtpl:88
qw422016.N().S(`
`)
//line views/readers.qtpl:97
//line views/readers.qtpl:89
qw422016.N().S(contents)
//line views/readers.qtpl:97
//line views/readers.qtpl:89
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(`
</article>
<section class="prevnext">
`)
//line views/readers.qtpl:101
//line views/readers.qtpl:95
if prevHyphaName != "" {
//line views/readers.qtpl:101
//line views/readers.qtpl:95
qw422016.N().S(`
<a class="prevnext__el prevnext__prev" href="/hypha/`)
//line views/readers.qtpl:102
//line views/readers.qtpl:96
qw422016.E().S(prevHyphaName)
//line views/readers.qtpl:102
//line views/readers.qtpl:96
qw422016.N().S(`" rel="prev">← `)
//line views/readers.qtpl:102
//line views/readers.qtpl:96
qw422016.E().S(util.BeautifulName(path.Base(prevHyphaName)))
//line views/readers.qtpl:102
//line views/readers.qtpl:96
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(`
`)
//line views/readers.qtpl:104
//line views/readers.qtpl:98
if nextHyphaName != "" {
//line views/readers.qtpl:104
//line views/readers.qtpl:98
qw422016.N().S(`
<a class="prevnext__el prevnext__next" href="/hypha/`)
//line views/readers.qtpl:105
//line views/readers.qtpl:99
qw422016.E().S(nextHyphaName)
//line views/readers.qtpl:105
//line views/readers.qtpl:99
qw422016.N().S(`" rel="next">`)
//line views/readers.qtpl:105
//line views/readers.qtpl:99
qw422016.E().S(util.BeautifulName(path.Base(nextHyphaName)))
//line views/readers.qtpl:105
//line views/readers.qtpl:99
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(`
</section>
`)
//line views/readers.qtpl:108
//line views/readers.qtpl:102
StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/readers.qtpl:108
//line views/readers.qtpl:102
qw422016.N().S(`
</main>
`)
//line views/readers.qtpl:110
//line views/readers.qtpl:104
StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/readers.qtpl:110
//line views/readers.qtpl:104
qw422016.N().S(`
</div>
`)
//line views/readers.qtpl:112
}
//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
//line views/readers.qtpl:106
streamviewScripts(qw422016)
//line views/readers.qtpl:106
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)
//line views/readers.qtpl:117
//line views/readers.qtpl:112
qw422016.N().S(`
`)
//line views/readers.qtpl:118
//line views/readers.qtpl:113
StreamNavHTML(qw422016, rq, h.Name, "revision", revHash)
//line views/readers.qtpl:118
//line views/readers.qtpl:113
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<article>
<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))
//line views/readers.qtpl:123
//line views/readers.qtpl:118
qw422016.N().S(`
`)
//line views/readers.qtpl:124
//line views/readers.qtpl:119
qw422016.N().S(contents)
//line views/readers.qtpl:124
//line views/readers.qtpl:119
qw422016.N().S(`
</article>
`)
//line views/readers.qtpl:126
//line views/readers.qtpl:121
StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/readers.qtpl:126
//line views/readers.qtpl:121
qw422016.N().S(`
</main>
`)
//line views/readers.qtpl:128
//line views/readers.qtpl:123
StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/readers.qtpl:128
//line views/readers.qtpl:123
qw422016.N().S(`
</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) {
//line views/readers.qtpl:130
//line views/readers.qtpl:126
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:130
//line views/readers.qtpl:126
StreamRevisionHTML(qw422016, rq, h, contents, revHash)
//line views/readers.qtpl:130
//line views/readers.qtpl:126
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 {
//line views/readers.qtpl:130
//line views/readers.qtpl:126
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:130
//line views/readers.qtpl:126
WriteRevisionHTML(qb422016, rq, h, contents, revHash)
//line views/readers.qtpl:130
//line views/readers.qtpl:126
qs422016 := string(qb422016.B)
//line views/readers.qtpl:130
//line views/readers.qtpl:126
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:130
//line views/readers.qtpl:126
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 "github.com/bouncepaw/mycorrhiza/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
@ -9,7 +10,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
{% for _, el := range headElements %}{%s= el %}{% endfor %}
</head>
@ -17,7 +18,7 @@
<header>
<nav class="header-links main-width">
<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>
{%- endfor -%}
{%s= UserMenuHTML(u) %}
@ -25,6 +26,7 @@
</nav>
</header>
{%s= body %}
{%= omnipresentScripts() %}
</body>
</html>
{% endfunc %}
@ -53,19 +55,19 @@ for u := range user.YieldUsers() {
<section>
<h2>Admins</h2>
<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>
</section>
<section>
<h2>Moderators</h2>
<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>
</section>
<section>
<h2>Editors</h2>
<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>
</section>
</main>
@ -95,16 +97,16 @@ for u := range user.YieldUsers() {
<div class="layout">
<main class="main-width">
<section>
<h1>About {%s util.SiteName %}</h1>
<h1>About {%s cfg.WikiName %}</h1>
<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 -%}
<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") -%}
{%- if i > 0 -%}<span aria-hidden="true">, </span>
{%- 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 -%}
<li>This wiki does not use authorization</li>
{%- endif -%}
@ -151,3 +153,9 @@ for u := range user.YieldUsers() {
</main>
</div>
{% 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"
//line views/stuff.qtpl:2
import "github.com/bouncepaw/mycorrhiza/hyphae"
import "github.com/bouncepaw/mycorrhiza/cfg"
//line views/stuff.qtpl:3
import "github.com/bouncepaw/mycorrhiza/user"
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/stuff.qtpl:4
import "github.com/bouncepaw/mycorrhiza/user"
//line views/stuff.qtpl:5
import "github.com/bouncepaw/mycorrhiza/util"
//line views/stuff.qtpl:6
//line views/stuff.qtpl:7
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/stuff.qtpl:6
//line views/stuff.qtpl:7
var (
_ = qtio422016.Copy
_ = 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) {
//line views/stuff.qtpl:6
//line views/stuff.qtpl:7
qw422016.N().S(`
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>`)
//line views/stuff.qtpl:13
//line views/stuff.qtpl:14
qw422016.E().S(title)
//line views/stuff.qtpl:13
//line views/stuff.qtpl:14
qw422016.N().S(`</title>
`)
//line views/stuff.qtpl:14
//line views/stuff.qtpl:15
for _, el := range headElements {
//line views/stuff.qtpl:14
//line views/stuff.qtpl:15
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(`
</head>
<body>
@ -59,76 +62,81 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User,
<nav class="header-links main-width">
<ul class="header-links__list">
`)
//line views/stuff.qtpl:20
for _, link := range util.HeaderLinks {
//line views/stuff.qtpl:20
//line views/stuff.qtpl:21
for _, link := range cfg.HeaderLinks {
//line views/stuff.qtpl:21
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)
//line views/stuff.qtpl:21
//line views/stuff.qtpl:22
qw422016.N().S(`">`)
//line views/stuff.qtpl:21
//line views/stuff.qtpl:22
qw422016.E().S(link.Display)
//line views/stuff.qtpl:21
//line views/stuff.qtpl:22
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(` `)
//line views/stuff.qtpl:23
//line views/stuff.qtpl:24
qw422016.N().S(UserMenuHTML(u))
//line views/stuff.qtpl:23
//line views/stuff.qtpl:24
qw422016.N().S(`
</ul>
</nav>
</header>
`)
//line views/stuff.qtpl:27
//line views/stuff.qtpl:28
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(`
</body>
</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) {
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
StreamBaseHTML(qw422016, title, body, u, headElements...)
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
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 {
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
WriteBaseHTML(qb422016, title, body, u, headElements...)
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:30
//line views/stuff.qtpl:32
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) {
//line views/stuff.qtpl:32
//line views/stuff.qtpl:34
qw422016.N().S(`
<div class="layout">
<main class="main-width user-list">
<h1>List of users</h1>
`)
//line views/stuff.qtpl:37
//line views/stuff.qtpl:39
var (
admins = 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(`
<section>
<h2>Admins</h2>
<ol>`)
//line views/stuff.qtpl:55
//line views/stuff.qtpl:57
for _, name := range admins {
//line views/stuff.qtpl:55
//line views/stuff.qtpl:57
qw422016.N().S(`
<li><a href="/page/`)
//line views/stuff.qtpl:56
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:56
//line views/stuff.qtpl:58
qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:58
qw422016.N().S(`/`)
//line views/stuff.qtpl:56
//line views/stuff.qtpl:58
qw422016.E().S(name)
//line views/stuff.qtpl:56
//line views/stuff.qtpl:58
qw422016.N().S(`">`)
//line views/stuff.qtpl:56
//line views/stuff.qtpl:58
qw422016.E().S(name)
//line views/stuff.qtpl:56
//line views/stuff.qtpl:58
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>
</section>
<section>
<h2>Moderators</h2>
<ol>`)
//line views/stuff.qtpl:61
//line views/stuff.qtpl:63
for _, name := range moderators {
//line views/stuff.qtpl:61
//line views/stuff.qtpl:63
qw422016.N().S(`
<li><a href="/page/`)
//line views/stuff.qtpl:62
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:62
//line views/stuff.qtpl:64
qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:64
qw422016.N().S(`/`)
//line views/stuff.qtpl:62
//line views/stuff.qtpl:64
qw422016.E().S(name)
//line views/stuff.qtpl:62
//line views/stuff.qtpl:64
qw422016.N().S(`">`)
//line views/stuff.qtpl:62
//line views/stuff.qtpl:64
qw422016.E().S(name)
//line views/stuff.qtpl:62
//line views/stuff.qtpl:64
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>
</section>
<section>
<h2>Editors</h2>
<ol>`)
//line views/stuff.qtpl:67
//line views/stuff.qtpl:69
for _, name := range editors {
//line views/stuff.qtpl:67
//line views/stuff.qtpl:69
qw422016.N().S(`
<li><a href="/page/`)
//line views/stuff.qtpl:68
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:68
//line views/stuff.qtpl:70
qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:70
qw422016.N().S(`/`)
//line views/stuff.qtpl:68
//line views/stuff.qtpl:70
qw422016.E().S(name)
//line views/stuff.qtpl:68
//line views/stuff.qtpl:70
qw422016.N().S(`">`)
//line views/stuff.qtpl:68
//line views/stuff.qtpl:70
qw422016.E().S(name)
//line views/stuff.qtpl:68
//line views/stuff.qtpl:70
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>
</section>
</main>
</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) {
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
StreamUserListHTML(qw422016)
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
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 {
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
WriteUserListHTML(qb422016)
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:73
//line views/stuff.qtpl:75
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) {
//line views/stuff.qtpl:75
//line views/stuff.qtpl:77
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<h1>List of hyphae</h1>
<p>This wiki has `)
//line views/stuff.qtpl:79
//line views/stuff.qtpl:81
qw422016.N().D(hyphae.Count())
//line views/stuff.qtpl:79
//line views/stuff.qtpl:81
qw422016.N().S(` hyphae.</p>
<ul class="hypha-list">
`)
//line views/stuff.qtpl:81
//line views/stuff.qtpl:83
for h := range hyphae.YieldExistingHyphae() {
//line views/stuff.qtpl:81
//line views/stuff.qtpl:83
qw422016.N().S(`
<li class="hypha-list__entry">
<a class="hypha-list__link" href="/hypha/`)
//line views/stuff.qtpl:83
//line views/stuff.qtpl:85
qw422016.E().S(h.Name)
//line views/stuff.qtpl:83
//line views/stuff.qtpl:85
qw422016.N().S(`">`)
//line views/stuff.qtpl:83
//line views/stuff.qtpl:85
qw422016.E().S(util.BeautifulName(h.Name))
//line views/stuff.qtpl:83
//line views/stuff.qtpl:85
qw422016.N().S(`</a>
`)
//line views/stuff.qtpl:84
//line views/stuff.qtpl:86
if h.BinaryPath != "" {
//line views/stuff.qtpl:84
//line views/stuff.qtpl:86
qw422016.N().S(`
<span class="hypha-list__amnt-type">`)
//line views/stuff.qtpl:85
//line views/stuff.qtpl:87
qw422016.E().S(filepath.Ext(h.BinaryPath)[1:])
//line views/stuff.qtpl:85
//line views/stuff.qtpl:87
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(`
</li>
`)
//line views/stuff.qtpl:88
//line views/stuff.qtpl:90
}
//line views/stuff.qtpl:88
//line views/stuff.qtpl:90
qw422016.N().S(`
</ul>
</main>
</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) {
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
StreamHyphaListHTML(qw422016)
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
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 {
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
WriteHyphaListHTML(qb422016)
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:92
//line views/stuff.qtpl:94
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) {
//line views/stuff.qtpl:94
//line views/stuff.qtpl:96
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
<h1>About `)
//line views/stuff.qtpl:98
qw422016.E().S(util.SiteName)
//line views/stuff.qtpl:98
//line views/stuff.qtpl:100
qw422016.E().S(cfg.WikiName)
//line views/stuff.qtpl:100
qw422016.N().S(`</h1>
<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 {
//line views/stuff.qtpl:101
//line views/stuff.qtpl:103
qw422016.N().S(` <li><b>User count:</b> `)
//line views/stuff.qtpl:102
//line views/stuff.qtpl:104
qw422016.N().D(user.Count())
//line views/stuff.qtpl:102
//line views/stuff.qtpl:104
qw422016.N().S(`</li>
<li><b>Home page:</b> <a href="/">`)
//line views/stuff.qtpl:103
qw422016.E().S(util.HomePage)
//line views/stuff.qtpl:103
//line views/stuff.qtpl:105
qw422016.E().S(cfg.HomeHypha)
//line views/stuff.qtpl:105
qw422016.N().S(`</a></li>
<li><b>Administrators:</b>`)
//line views/stuff.qtpl:104
//line views/stuff.qtpl:106
for i, username := range user.ListUsersWithGroup("admin") {
//line views/stuff.qtpl:105
//line views/stuff.qtpl:107
if i > 0 {
//line views/stuff.qtpl:105
//line views/stuff.qtpl:107
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/`)
//line views/stuff.qtpl:107
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:107
//line views/stuff.qtpl:109
qw422016.E().S(cfg.UserHypha)
//line views/stuff.qtpl:109
qw422016.N().S(`/`)
//line views/stuff.qtpl:107
//line views/stuff.qtpl:109
qw422016.E().S(username)
//line views/stuff.qtpl:107
//line views/stuff.qtpl:109
qw422016.N().S(`">`)
//line views/stuff.qtpl:107
//line views/stuff.qtpl:109
qw422016.E().S(username)
//line views/stuff.qtpl:107
//line views/stuff.qtpl:109
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>
`)
//line views/stuff.qtpl:108
//line views/stuff.qtpl:110
} else {
//line views/stuff.qtpl:108
//line views/stuff.qtpl:110
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>
<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p>
</section>
</main>
</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) {
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
StreamAboutHTML(qw422016)
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
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 {
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
WriteAboutHTML(qb422016)
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:116
//line views/stuff.qtpl:118
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) {
//line views/stuff.qtpl:118
//line views/stuff.qtpl:120
qw422016.N().S(`
<div class="layout">
<main class="main-width">
@ -478,31 +486,80 @@ func StreamAdminPanelHTML(qw422016 *qt422016.Writer) {
</main>
</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) {
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
StreamAdminPanelHTML(qw422016)
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
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 {
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
WriteAdminPanelHTML(qb422016)
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:153
//line views/stuff.qtpl:155
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 (
"io"
"log"
"net/http"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"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() {
if user.AuthUsed {
http.HandleFunc("/admin", handlerAdmin)
@ -18,26 +20,32 @@ func initAdmin() {
}
}
// handlerAdmin provides the admin panel.
func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
util.PrepareRq(rq)
if user.CanProceed(rq, "admin") {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
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) {
log.Println(rq.URL)
util.PrepareRq(rq)
if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" {
log.Fatal("An admin commanded the wiki to shutdown")
}
}
// handlerAdminReindexUsers reinitialises the user system.
func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
util.PrepareRq(rq)
if user.CanProceed(rq, "admin") && rq.Method == "POST" {
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 (
"github.com/bouncepaw/mycorrhiza/cfg"
"io"
"log"
"net/http"
@ -10,23 +11,29 @@ import (
"github.com/bouncepaw/mycorrhiza/views"
)
func init() {
http.HandleFunc("/register", handlerRegister)
func initAuth() {
if !user.AuthUsed {
return
}
if cfg.UseRegistration {
http.HandleFunc("/register", handlerRegister)
}
http.HandleFunc("/login", handlerLogin)
http.HandleFunc("/login-data", handlerLoginData)
http.HandleFunc("/logout", handlerLogout)
http.HandleFunc("/logout-confirm", handlerLogoutConfirm)
}
// handlerRegister both displays the register form (GET) and registers users (POST).
func handlerRegister(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
if !util.UseRegistration {
util.PrepareRq(rq)
if !cfg.UseRegistration {
w.WriteHeader(http.StatusForbidden)
}
if rq.Method == http.MethodGet {
io.WriteString(
w,
base(
views.BaseHTML(
"Register",
views.RegisterHTML(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) {
var (
u = user.FromRequest(rq)
@ -60,35 +68,42 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) {
log.Println("Unknown user tries to log out")
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) {
user.LogoutFromRequest(w, rq)
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
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)
}
}
// handlerLogin shows the login form.
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")
if user.AuthUsed {
w.WriteHeader(http.StatusOK)
} else {
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 (
"fmt"
@ -13,7 +13,7 @@ import (
"github.com/bouncepaw/mycorrhiza/views"
)
func init() {
func initHistory() {
http.HandleFunc("/history/", handlerHistory)
http.HandleFunc("/recent-changes/", handlerRecentChanges)
http.HandleFunc("/recent-changes-rss", handlerRecentChangesRSS)
@ -21,10 +21,10 @@ func init() {
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) {
log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "history")
util.PrepareRq(rq)
hyphaName := util.HyphaNameFromRq(rq, "history")
var list string
// 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)
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) {
log.Println(rq.URL)
util.PrepareRq(rq)
var (
noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
n, err = strconv.Atoi(noPrefix)
)
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 {
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) {
log.Println(rq.URL)
util.PrepareRq(rq)
if content, err := f(); err != nil {
w.Header().Set("Content-Type", "text/plain;charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)

View File

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

View File

@ -1,7 +1,8 @@
package main
package web
import (
"fmt"
"github.com/bouncepaw/mycomarkup/mycocontext"
"io/ioutil"
"log"
"net/http"
@ -11,14 +12,15 @@ import (
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
"github.com/bouncepaw/mycomarkup"
)
func init() {
func initReaders() {
http.HandleFunc("/page/", handlerHypha)
http.HandleFunc("/hypha/", handlerHypha)
http.HandleFunc("/text/", handlerText)
@ -29,9 +31,9 @@ func init() {
}
func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
util.PrepareRq(rq)
var (
hyphaName = HyphaNameFromRq(rq, "attachment")
hyphaName = util.HyphaNameFromRq(rq, "attachment")
h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq)
)
@ -43,7 +45,7 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
}
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
util.PrepareRq(rq)
var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/")
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
func handlerRevision(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
util.PrepareRq(rq)
var (
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
@ -73,7 +75,8 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
u = user.FromRequest(rq)
)
if err == nil {
contents = markup.Doc(hyphaName, textContents).AsHTML()
ctx, _ := mycocontext.ContextFromStringInput(hyphaName, textContents)
contents = mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))
}
page := views.RevisionHTML(
rq,
@ -83,13 +86,13 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
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.
func handlerText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "text")
util.PrepareRq(rq)
hyphaName := util.HyphaNameFromRq(rq, "text")
if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", h.TextPath)
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.
func handlerBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "binary")
util.PrepareRq(rq)
hyphaName := util.HyphaNameFromRq(rq, "binary")
if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", 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.
func handlerHypha(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
util.PrepareRq(rq)
var (
hyphaName = HyphaNameFromRq(rq, "page", "hypha")
hyphaName = util.HyphaNameFromRq(rq, "page", "hypha")
h = hyphae.ByName(hyphaName)
contents string
openGraph string
@ -122,9 +125,10 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) {
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
_, errB := os.Stat(h.BinaryPath)
if errT == nil {
md := markup.Doc(hyphaName, string(fileContentsT))
contents = md.AsHTML()
openGraph = md.OpenGraphHTML()
ctx, _ := mycocontext.ContextFromStringInput(hyphaName, string(fileContentsT))
ast := mycomarkup.BlockTree(ctx)
contents = mycomarkup.BlocksToHTML(ctx, ast)
openGraph = mycomarkup.OpenGraphHTML(ctx, ast)
}
if !os.IsNotExist(errB) {
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)
})
}