diff --git a/.gitignore b/.gitignore index f6d437f..160d81e 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -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/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..e2c07cf --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..304a7ba --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..467eff0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/mycorrhiza.iml b/.idea/mycorrhiza.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/mycorrhiza.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..3a1beee --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Makefile b/Makefile index 2bacbea..59b3044 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 06aba96..dc15a79 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/assets/assets.qtpl.go b/assets/assets.qtpl.go index 484496c..a77f6f9 100644 --- a/assets/assets.qtpl.go +++ b/assets/assets.qtpl.go @@ -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/ diff --git a/assets/default.css b/assets/default.css index c3905b7..4679850 100644 --- a/assets/default.css +++ b/assets/default.css @@ -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; } diff --git a/assets/devconfig.ini b/assets/devconfig.ini index 4a26789..dba810d 100644 --- a/assets/devconfig.ini +++ b/assets/devconfig.ini @@ -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 \ No newline at end of file diff --git a/assets/toolbar.js b/assets/toolbar.js index 50062e5..affed6b 100644 --- a/assets/toolbar.js +++ b/assets/toolbar.js @@ -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/ diff --git a/cfg/config.go b/cfg/config.go new file mode 100644 index 0000000..3b86425 --- /dev/null +++ b/cfg/config.go @@ -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 + } +} diff --git a/cfg/header_links.go b/cfg/header_links.go new file mode 100644 index 0000000..b6add82 --- /dev/null +++ b/cfg/header_links.go @@ -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 .... + Href string + // Display is what is shown when the link is rendered. It goes here. + Display string +} diff --git a/files/Structure.md b/files/Structure.md new file mode 100644 index 0000000..63b563d --- /dev/null +++ b/files/Structure.md @@ -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. \ No newline at end of file diff --git a/files/files.go b/files/files.go index 5f03a10..740041b 100644 --- a/files/files.go +++ b/files/files.go @@ -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) diff --git a/flag.go b/flag.go index 6cb3fef..19c69d6 100644 --- a/flag.go +++ b/flag.go @@ -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) } diff --git a/gemini.go b/gemini.go index b86c3a4..ba50649 100644 --- a/gemini.go +++ b/gemini.go @@ -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 "" +} diff --git a/go.mod b/go.mod index 7e4d01e..e3fe75c 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 16305f9..3c1dd9e 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/history/history.go b/history/history.go index a1e897b..4283721 100644 --- a/history/history.go +++ b/history/history.go @@ -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 += `` } - html += fmt.Sprintf(`%[1]s`, hyphaName) + html += fmt.Sprintf(`%[1]s`, 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( `

%s

-

Hyphae affected: %s

`, rev.Message, rev.HyphaeLinksHTML()) +

Hyphae affected: %s

+
%s
`, 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) diff --git a/history/information.go b/history/information.go index cd22f0c..5c39cbf 100644 --- a/history/information.go +++ b/history/information.go @@ -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(` - by
  • 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 -} diff --git a/main.go b/main.go index 295c974..33e17f9 100644 --- a/main.go +++ b/main.go @@ -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( - `

    %s. Go back to the hypha.

    `, - 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)) } diff --git a/markup/hr.go b/markup/hr.go deleted file mode 100644 index 278138a..0000000 --- a/markup/hr.go +++ /dev/null @@ -1,34 +0,0 @@ -package markup - -import ( - "unicode" -) - -// MatchesHorizontalLine checks if the string can be interpreted as suitable for rendering as
    . -// -// The rule is: if there are more than 4 characters "-" in the string, then make it a horizontal line. -// Otherwise it is a paragraph (

    ). -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 -} diff --git a/markup/img.go b/markup/img.go deleted file mode 100644 index b1a59c1..0000000 --- a/markup/img.go +++ /dev/null @@ -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 += `

  • ") - } -} - -// 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() -} diff --git a/markup/mycomarkup.go b/markup/mycomarkup.go deleted file mode 100644 index 2fadd4e..0000000 --- a/markup/mycomarkup.go +++ /dev/null @@ -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(``, 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{} -} diff --git a/markup/outlink.go b/markup/outlink.go deleted file mode 100644 index 0f4203a..0000000 --- a/markup/outlink.go +++ /dev/null @@ -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, "`) - -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 - } - } -} diff --git a/markup/paragraph.go b/markup/paragraph.go deleted file mode 100644 index a576fee..0000000 --- a/markup/paragraph.go +++ /dev/null @@ -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("", 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(`%s`, 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() -} diff --git a/markup/parser.go b/markup/parser.go deleted file mode 100644 index db715f1..0000000 --- a/markup/parser.go +++ /dev/null @@ -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 += "Unknown element." - } - } - } - return html -} diff --git a/markup/table.go b/markup/table.go deleted file mode 100644 index a4dca62..0000000 --- a/markup/table.go +++ /dev/null @@ -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("%s", t.caption) - } - if len(t.rows) > 0 && t.rows[0].looksLikeThead() { - html += fmt.Sprintf("%s", t.rows[0].asHtml(t.hyphaName)) - t.rows = t.rows[1:] - } - html += "\n\n" - for _, tr := range t.rows { - html += tr.asHtml(t.hyphaName) - } - return fmt.Sprintf(`%s
    `, 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("%s\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\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 += `
    ` - } - 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" - } -} diff --git a/markup/testdata/test.myco b/markup/testdata/test.myco deleted file mode 100644 index fd5880f..0000000 --- a/markup/testdata/test.myco +++ /dev/null @@ -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 -} -} - diff --git a/markup/utils.go b/markup/utils.go deleted file mode 100644 index 420697a..0000000 --- a/markup/utils.go +++ /dev/null @@ -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), " ", "_")) -} diff --git a/markup/xclusion.go b/markup/xclusion.go deleted file mode 100644 index a5df6c8..0000000 --- a/markup/xclusion.go +++ /dev/null @@ -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 := `
    - %s -
    %s
    -
    ` - tmptFailed := `
    -

    Hypha %s does not exist

    -
    ` - 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 -} diff --git a/metarrhiza b/metarrhiza deleted file mode 160000 index 515634c..0000000 --- a/metarrhiza +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 515634c88a51a616f486dfefc2a7b9e6ca692689 diff --git a/name.go b/name.go deleted file mode 100644 index 7da76ba..0000000 --- a/name.go +++ /dev/null @@ -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 "" -} diff --git a/shroom/init.go b/shroom/init.go index 96a6195..003f8f5 100644 --- a/shroom/init.go +++ b/shroom/init.go @@ -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" - } } diff --git a/shroom/upload.go b/shroom/upload.go index c72ec9a..92fb51d 100644 --- a/shroom/upload.go +++ b/shroom/upload.go @@ -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 { diff --git a/shroom/view.go b/shroom/view.go index 05becb9..ce7a8f4 100644 --- a/shroom/view.go +++ b/shroom/view.go @@ -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) } } } diff --git a/user/files.go b/user/files.go index 93b8b78..eb11039 100644 --- a/user/files.go +++ b/user/files.go @@ -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 diff --git a/user/net.go b/user/net.go index 88ef769..3703087 100644 --- a/user/net.go +++ b/user/net.go @@ -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): diff --git a/util/config.go b/util/config.go deleted file mode 100644 index 6b1212e..0000000 --- a/util/config.go +++ /dev/null @@ -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) -} diff --git a/util/header_links.go b/util/header_links.go deleted file mode 100644 index c574768..0000000 --- a/util/header_links.go +++ /dev/null @@ -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 diff --git a/util/util.go b/util/util.go index 5a685ca..4117b63 100644 --- a/util/util.go +++ b/util/util.go @@ -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 } diff --git a/views/auth.qtpl b/views/auth.qtpl index 2cf72ae..6aa4e47 100644 --- a/views/auth.qtpl +++ b/views/auth.qtpl @@ -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) %}
    - {% if util.UseRegistration %} + {% if cfg.UseRegistration %} - {% elseif util.UseFixedAuth %} + {% elseif cfg.UseFixedAuth %}

    Administrators have forbidden registration for this wiki. Administrators can make an account for you by hand; contact them.

    -

    ← Go back

    +

    ← Go back

    {% else %}

    Administrators of this wiki have not configured any authorization method. You can make edits anonymously.

    -

    ← Go back

    +

    ← Go back

    {% endif %}
    @@ -43,8 +43,7 @@ {% if user.AuthUsed %} {% else %} diff --git a/views/auth.qtpl.go b/views/auth.qtpl.go index 02b7743..b1120cd 100644 --- a/views/auth.qtpl.go +++ b/views/auth.qtpl.go @@ -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) {
    `) //line views/auth.qtpl:9 - if util.UseRegistration { + if cfg.UseRegistration { //line views/auth.qtpl:9 qw422016.N().S(` `) //line views/auth.qtpl:27 - } else if util.UseFixedAuth { + } else if cfg.UseFixedAuth { //line views/auth.qtpl:27 qw422016.N().S(`

    Administrators have forbidden registration for this wiki. Administrators can make an account for you by hand; contact them.

    -

    Administrators of this wiki have not configured any authorization method. You can make edits anonymously.

    -

    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(` -

    Use the data you were given by an administrator.


    @@ -151,182 +154,182 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {

    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.

    - -
    Cancel + + Cancel `) -//line views/auth.qtpl:60 +//line views/auth.qtpl:59 } else { -//line views/auth.qtpl:60 +//line views/auth.qtpl:59 qw422016.N().S(`

    Administrators of this wiki have not configured any authorization method. You can make edits anonymously.

    ← Go home

    `) -//line views/auth.qtpl:63 +//line views/auth.qtpl:62 } -//line views/auth.qtpl:63 +//line views/auth.qtpl:62 qw422016.N().S(`
    `) -//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(`
    `) -//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(`

    Unknown username.

    `) -//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(`

    Wrong password.

    `) -//line views/auth.qtpl:78 +//line views/auth.qtpl:77 default: -//line views/auth.qtpl:78 +//line views/auth.qtpl:77 qw422016.N().S(`

    `) -//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(`

    `) -//line views/auth.qtpl:80 +//line views/auth.qtpl:79 } -//line views/auth.qtpl:80 +//line views/auth.qtpl:79 qw422016.N().S(`

    ← Try again

    `) -//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(`
    `) -//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(`

    Log out?

    Confirm

    Cancel

    `) -//line views/auth.qtpl:95 +//line views/auth.qtpl:94 } else { -//line views/auth.qtpl:95 +//line views/auth.qtpl:94 qw422016.N().S(`

    You cannot log out because you are not logged in.

    Login

    ← Home

    `) -//line views/auth.qtpl:99 +//line views/auth.qtpl:98 } -//line views/auth.qtpl:99 +//line views/auth.qtpl:98 qw422016.N().S(`
    `) -//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 } diff --git a/views/history.qtpl b/views/history.qtpl index 3432fd8..7049261 100644 --- a/views/history.qtpl +++ b/views/history.qtpl @@ -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 -

    Subscribe via RSS, Atom or JSON feed.

    +

    Subscribe via RSS, Atom or JSON feed.

    {% comment %} Here I am, willing to add some accessibility using ARIA. Turns out, @@ -76,7 +77,7 @@ if err != nil {
  • {%s rev.Hash %}
  • {%s= rev.HyphaeLinksHTML() %}
  • -
  • {%s rev.Message %} {% if rev.Username != "anon" %}{% endif %}
  • +
  • {%s rev.Message %} {% if rev.Username != "anon" %}{% endif %}
  • {% endfunc %} {% func HistoryHTML(rq *http.Request, hyphaName, list string) %} diff --git a/views/history.qtpl.go b/views/history.qtpl.go index 470c411..0a0462b 100644 --- a/views/history.qtpl.go +++ b/views/history.qtpl.go @@ -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(`

    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(`

    `)
    -//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(`
    `) -//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(`
    @@ -111,268 +114,268 @@ func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) { -

    Subscribe via RSS, Atom or JSON feed.

    +

    Subscribe via RSS, Atom or JSON feed.

    `) -//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(`
    `) -//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(`

    Could not find any recent changes.

    `) -//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(`
      `) -//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(`
    `) -//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(`
    `) -//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(`
  • `) -//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(`
  • `) -//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(`
  • `) -//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(``) -//line views/history.qtpl:79 +//line views/history.qtpl:80 } -//line views/history.qtpl:79 +//line views/history.qtpl:80 qw422016.N().S(`
  • `) -//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(`

    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(`

    `) -//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(`
    `) -//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 } diff --git a/views/hypha.qtpl b/views/hypha.qtpl index d758cb0..a814216 100644 --- a/views/hypha.qtpl +++ b/views/hypha.qtpl @@ -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) %} +
    +

    This hypha does not exist

    + {% if user.AuthUsed && u.Group == "anon" %} +

    You are not authorized to create new hyphae. Here is what you can do:

    + + {% else %} + +
    +
    +

    šŸ“ Write a text

    +

    Write a note, a diary, an article, a story or anything textual using Mycomarkup. Full history of edits to the document will be saved.

    +

    Make sure to follow this wiki's writing conventions if there are any.

    + Create +
    + +
    +

    šŸ–¼ Upload a media

    +

    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.

    +
    + + + + +
    +
    +
    + {% endif %} +
    +{% endfunc %} + {% func NaviTitleHTML(h *hyphae.Hypha) %} {% code var ( @@ -12,8 +51,8 @@ %}

    {% stripspace %} - - {%-s= util.SiteNavIcon -%} + + {%-s= cfg.NaviTitleIcon -%} diff --git a/views/hypha.qtpl.go b/views/hypha.qtpl.go index 6d6dee0..a942fc8 100644 --- a/views/hypha.qtpl.go +++ b/views/hypha.qtpl.go @@ -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(` +
    +

    This hypha does not exist

    + `) +//line views/hypha.qtpl:12 + if user.AuthUsed && u.Group == "anon" { +//line views/hypha.qtpl:12 + qw422016.N().S(` +

    You are not authorized to create new hyphae. Here is what you can do:

    + + `) +//line views/hypha.qtpl:18 + } else { +//line views/hypha.qtpl:18 + qw422016.N().S(` + +
    +
    +

    šŸ“ Write a text

    +

    Write a note, a diary, an article, a story or anything textual using Mycomarkup. Full history of edits to the document will be saved.

    +

    Make sure to follow this wiki's writing conventions if there are any.

    + Create +
    + +
    +

    šŸ–¼ Upload a media

    +

    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.

    +
    + + + + +
    +
    +
    + `) +//line views/hypha.qtpl:41 + } +//line views/hypha.qtpl:41 + qw422016.N().S(` +
    +`) +//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(`

    `) -//line views/hypha.qtpl:14 +//line views/hypha.qtpl:53 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(``) -//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(``) -//line views/hypha.qtpl:23 +//line views/hypha.qtpl:62 } -//line views/hypha.qtpl:23 +//line views/hypha.qtpl:62 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(``) -//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(`

    `) -//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(`
    `) -//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(`
    `) -//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(`
    `) -//line views/hypha.qtpl:58 +//line views/hypha.qtpl:97 default: -//line views/hypha.qtpl:58 +//line views/hypha.qtpl:97 qw422016.N().S(` `) -//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 } diff --git a/views/modal.qtpl b/views/modal.qtpl index bb1a559..45c9dd3 100644 --- a/views/modal.qtpl +++ b/views/modal.qtpl @@ -54,8 +54,8 @@ {% endfunc %} {% func modalEnd(hyphaName string, shouldFocusOnConfirm bool) %} - - Cancel + + Cancel diff --git a/views/modal.qtpl.go b/views/modal.qtpl.go index 3efb837..5dfba56 100644 --- a/views/modal.qtpl.go +++ b/views/modal.qtpl.go @@ -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(` - Cancel + qw422016.N().S(`" class="btn btn_weak">Cancel diff --git a/views/mutators.qtpl b/views/mutators.qtpl index 20cdbf4..5b1c321 100644 --- a/views/mutators.qtpl +++ b/views/mutators.qtpl @@ -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()", "//Italic//"}, {"highlighted", "wrapHighlighted()", "!!Highlight!!"}, {"monospace", "wrapMonospace()", "`Monospace`"}, - {"lifted", "wrapLifted()", "^Lifted^"}, + {"lifted", "wrapLifted()", "^^Lifted^^"}, {"lowered", "wrapLowered()", ",,Lowered,,"}, {"strikethrough", "wrapStrikethrough()", "~~Strikethrough~~"}, {"rocket", "insertRocket()", "=> rocketlink"}, @@ -46,6 +48,7 @@ display string }{ {"date", "insertDate()", "Insert current date"}, + {"time", "insertTimeUTC()", "Insert current time"}, } %} `) -//line views/mutators.qtpl:38 +//line views/mutators.qtpl:40 } -//line views/mutators.qtpl:38 +//line views/mutators.qtpl:40 qw422016.N().S(`

    Learn more about mycomarkup

    Actions

    `) -//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(` `) -//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(`
    - + `) -//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(`

    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(`

    `) -//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(`

    Cancel
    `) -//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(`
    `) -//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(`

    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)

    `) -//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(`

    Cancel

    Note that the hypha is not saved yet. You can preview the changes ↓

    `) -//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(`
    `) -//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(`
    `) -//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(` + +`) +//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 } diff --git a/views/nav.qtpl b/views/nav.qtpl index 8e1ed51..eaad97e 100644 --- a/views/nav.qtpl +++ b/views/nav.qtpl @@ -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 %} -
  • - {% if u.Group == "anon" %} - Login - {% else %} - {%s u.Name %} - {% endif %} -
  • +
  • + {% if u.Group == "anon" %} + Login + {% else %} + {%s util.BeautifulName(u.Name) %} + {% endif %} +
  • +{% endif %} +{% if user.AuthUsed && cfg.UseRegistration && u.Group == "anon" %} +
  • + Register +
  • {% endif %} {% endfunc %} diff --git a/views/nav.qtpl.go b/views/nav.qtpl.go index c3b14ec..1011440 100644 --- a/views/nav.qtpl.go +++ b/views/nav.qtpl.go @@ -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