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 %[2]s`, util.UserHypha, rev.Username)
+ by %[2]s`, cfg.UserHypha, rev.Username)
}
return fmt.Sprintf(`
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.
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.