1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1 +1,2 @@ | ||||
| mycorrhiza | ||||
| hyphae/*.gog | ||||
|   | ||||
							
								
								
									
										5
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						| @@ -1,9 +1,12 @@ | ||||
| run: build | ||||
| 	./mycorrhiza metarrhiza | ||||
|  | ||||
| run_with_fixed_auth: build | ||||
| auth_run: build | ||||
| 	./mycorrhiza -auth-method fixed metarrhiza | ||||
|  | ||||
| gemini_run: build | ||||
| 	./mycorrhiza -gemini-cert-path "." metarrhiza | ||||
|  | ||||
| build: | ||||
| 	go generate | ||||
| 	go build . | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # 🍄 MycorrhizaWiki 0.12 | ||||
| # 🍄 MycorrhizaWiki 0.13 | ||||
| A wiki engine. | ||||
|  | ||||
| [Main wiki](https://mycorrhiza.lesarbr.es) | ||||
| @@ -25,6 +25,8 @@ Options: | ||||
|         What auth method to use. Variants: "none", "fixed" (default "none") | ||||
|   -fixed-credentials-path string | ||||
|         Used when -auth-method=fixed. Path to file with user credentials. (default "mycocredentials.json") | ||||
|   -gemini-cert-path string | ||||
|         Directory where you store Gemini certificates. Leave empty if you don't want to use Gemini. | ||||
|   -header-links-hypha string | ||||
|         Optional hypha that overrides the header links | ||||
|   -home string | ||||
| @@ -57,6 +59,7 @@ Options: | ||||
| * Hyphae can be renamed (recursive renaming of subhyphae is also supported) | ||||
| * Light on resources | ||||
| * Authorization with pre-set credentials | ||||
| * Basic Gemini protocol support | ||||
|  | ||||
| ## Contributing | ||||
| Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. You can also sponsor on [boosty](https://boosty.to/bouncepaw). Feel free to open an issue or contact directly. | ||||
|   | ||||
| @@ -1,58 +1,124 @@ | ||||
| // Code generated by qtc from "asset.qtpl". DO NOT EDIT. | ||||
| // Code generated by qtc from "assets.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
| 
 | ||||
| //line templates/asset.qtpl:1 | ||||
| package templates | ||||
| //line assets/assets.qtpl:1 | ||||
| package assets | ||||
| 
 | ||||
| //line templates/asset.qtpl:1 | ||||
| //line assets/assets.qtpl:1 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
| 
 | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
| 
 | ||||
| //line templates/asset.qtpl:1 | ||||
| //line assets/assets.qtpl:1 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
| 
 | ||||
| //line templates/asset.qtpl:1 | ||||
| //line assets/assets.qtpl:1 | ||||
| func StreamDefaultCSS(qw422016 *qt422016.Writer) { | ||||
| //line templates/asset.qtpl:1 | ||||
| //line assets/assets.qtpl:1 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:2 | ||||
| 	qw422016.N().S(`/* Layout stuff */ | ||||
| @media screen and (min-width: 800px) { | ||||
| 	main { padding:1rem 2rem; margin: 0 auto; width: 800px; } | ||||
| 	.hypha-tabs { padding: 1rem 2rem; margin: 0 auto; width: 800px; } | ||||
| 	header { margin: 0 auto; width: 800px; } | ||||
| //line assets/assets.qtpl:2 | ||||
| 	qw422016.N().S(`.amnt-grid { display: grid; grid-template-columns: 1fr 1fr; } | ||||
| .upload-binary__input { display: block; margin: .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; } | ||||
| .hypha-list__link { text-decoration: none; display: inline-block; padding: .25rem; } | ||||
| .hypha-list__link:hover { text-decoration: underline; } | ||||
| .hypha-list__amnt-type { font-size: smaller; color: #999; } | ||||
| 
 | ||||
| /* General element positions, from small to big */ | ||||
| /* Phones and whatnot */ | ||||
| .layout { display: grid; row-gap: 1rem; } | ||||
| header { width: 100%; margin-bottom: 1rem; } | ||||
| .header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; } | ||||
| .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__link { display: inline-block; padding: .25rem; text-decoration: none; } | ||||
| 
 | ||||
| .hypha-tabs { padding: 0; margin: 0; } | ||||
| .hypha-tabs__tab { margin-right: .5rem; padding: 0; } | ||||
| .hypha-tabs__link { display: inline-block; padding: .25rem; text-decoration: none; } | ||||
| .hypha-tabs__selection { display: inline-block; padding: .25rem; font-weight: bold; } | ||||
| 
 | ||||
| .layout-card li { list-style-type: none; } | ||||
| .backlinks__list { padding: 0; margin: 0; } | ||||
| .backlinks__link { text-decoration: none; display: block; padding: .25rem; padding-left: 1.25rem; } | ||||
| 
 | ||||
| @media screen and (max-width: 800px) { | ||||
| 	.amnt-grid { grid-template-columns: 1fr; } | ||||
| 	.layout { grid-template-column: auto; grid-template-row: auto auto auto; } | ||||
| 	.main-width { width: 100%; } | ||||
| 	main { padding: 1rem; margin: 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; } | ||||
| 	main { border-radius: .25rem; } | ||||
| 	.layout-card { width: 800px; margin: 0 auto; } | ||||
| 
 | ||||
| 	.header-links { padding: 0; } | ||||
| 	.header-links__entry { margin-right: 1.5rem; } | ||||
| 	.header-links__entry_user { margin: 0 2rem 0 auto; } | ||||
| 	.header-links__entry:nth-of-type(1), | ||||
| 	.hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; } | ||||
| 	.hypha-tabs__tab { margin-right: 1.5rem; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); border-bottom: 2px #ddd solid; padding: 0 .5rem; } | ||||
| 
 | ||||
| 	.hypha-tabs { padding: 0; } | ||||
| 	.hypha-tabs__tab { border-radius: .25rem .25rem 0 0; margin-right: 0; } | ||||
| 	.hypha-tabs__selection, .hypha-tabs__link { padding: .25rem .5rem; } | ||||
| 
 | ||||
| 	.header-links__entry:nth-of-type(1), .hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; } | ||||
| } | ||||
| @media screen and (max-width: 800px) { | ||||
| 	main { padding: 1rem; margin: 0; width: 100%; } | ||||
| 	.hypha-tabs{ padding: 1rem; margin: 0; width: 100%; } | ||||
| 	.hypha-tabs__tab { box-shadow: none; margin-right: .5rem; padding: .25rem .5rem; } | ||||
| 	header { width: 100%; } | ||||
| 	.header-links__entry { margin-right: .5rem; } | ||||
| 
 | ||||
| /* Wide enough to fit two columns ok */ | ||||
| @media screen and (min-width: 1100px) { | ||||
| 	.layout { display: grid; grid-template-columns: auto 1fr; column-gap: 1rem; margin: 0 1rem; row-gap: 1rem; } | ||||
| 	.main-width { margin: 0; } | ||||
| 	main { grid-column: 1 / span 1; grid-row: 1 / span 2; } | ||||
| 	.relative-hyphae { grid-column: 2 / span 1; grid-row: 1 / span 1; } | ||||
| 	.layout-card { width: 100%; } | ||||
| } | ||||
| 
 | ||||
| @media screen and (min-width: 1250px) { | ||||
| 	.layout { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); } | ||||
| 	.layout-card { max-width: 16rem; } | ||||
| 	.main-width { margin: 0 auto; } | ||||
| 	.backlinks { grid-column: 1 / span 1; margin-right: 0; } | ||||
| 	main { grid-column: 2 / span 1; } | ||||
| 	.relative-hyphae { grid-column: 3 / span 1; margin-left: 0; } | ||||
| 
 | ||||
| 	.backlinks__title { text-align: right; } | ||||
| 	.backlinks__link { text-align: right; padding-right: 1.25rem; padding-left: .25rem; } | ||||
| } | ||||
| 
 | ||||
| *, *::before, *::after {box-sizing: border-box;} | ||||
| html { height:100%; padding:0; } | ||||
| body {height:100%; margin:0; font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;} | ||||
| main {border-radius: 0 0 .25rem .25rem; } | ||||
| body {height:100%; margin:0; } | ||||
| body, input { font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;} | ||||
| main > form {margin-bottom:1rem;} | ||||
| textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;} | ||||
| .edit_no-preview {height:100%;} | ||||
| .edit_with-preview .edit-form textarea { min-height: 500px; } | ||||
| 
 | ||||
| .edit { min-height: 80vh; } | ||||
| .edit__title { margin-top: 0; } | ||||
| .edit__preview { border: 2px dashed #ddd; } | ||||
| .edit-form {height:90%;} | ||||
| .edit-form textarea {width:100%;height:90%;} | ||||
| .edit-form {height:70vh;} | ||||
| .edit-form textarea {width:100%;height:95%;} | ||||
| .edit-form__save { font-weight: bold; } | ||||
| 
 | ||||
| .icon {margin-right: .25rem; vertical-align: bottom; } | ||||
| 
 | ||||
| main h1:not(.navi-title) {font-size:1.7rem;} | ||||
| @@ -67,7 +133,10 @@ blockquote { margin-left: 0; padding-left: 1rem; } | ||||
| .wikilink_mailto::before { content: url("/static/icon/mailto"); } | ||||
| 
 | ||||
| article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; } | ||||
| article h1, article h2, article h3, article h4, article h5, article h6 { margin: 1.5rem 0 0 0; } | ||||
| main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; } | ||||
| .heading__link { text-decoration: none; display: inline-block; } | ||||
| .heading__link::after { width: 1rem; content: "§"; color: transparent; } | ||||
| .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%; } | ||||
| @@ -85,6 +154,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25 | ||||
| .binary-container_with-video video, | ||||
| .binary-container_with-audio audio {width: 100%} | ||||
| 
 | ||||
| .subhyphae__title { padding-bottom: .5rem; clear: both; } | ||||
| .navi-title { padding-bottom: .5rem; margin: .25rem 0; } | ||||
| .navi-title a {text-decoration:none; } | ||||
| .navi-title__separator { margin: 0 .25rem; } | ||||
| @@ -101,19 +171,11 @@ figcaption { padding-bottom: .5rem; } | ||||
| 
 | ||||
| #new-name {width:100%;} | ||||
| 
 | ||||
| header { margin-bottom: .5rem; } | ||||
| .header-links__entry_user { font-style:italic; } | ||||
| .header-links__link { text-decoration: none; display: block; width: 100%; height: 100%; padding: .25rem; } | ||||
| .hypha-tabs { padding: 0; } | ||||
| .header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; } | ||||
| .header-links__entry, .hypha-tabs__tab { list-style-type: none; } | ||||
| .hypha-tabs__tab a { text-decoration: none; } | ||||
| .hypha-tabs__tab_active { font-weight: bold; } | ||||
| 
 | ||||
| .rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; } | ||||
| .rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; border-radius: .25rem; } | ||||
| .rc-entry__time { font-style: italic; } | ||||
| .rc-entry__hash { font-style: italic; text-align: right; } | ||||
| .rc-entry__links { grid-column: 1 / span 2; } | ||||
| .rc-entry__links, .rc-entry__msg { grid-column: 1 / span 2; } | ||||
| .rc-entry__author { font-style: italic; } | ||||
| 
 | ||||
| .prevnext__el { display: block-inline; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; } | ||||
| @@ -132,6 +194,22 @@ table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; } | ||||
| td { padding: .25rem; } | ||||
| caption { caption-side: top; font-size: small; } | ||||
| 
 | ||||
| .subhyphae__list, .subhyphae__list ul { display: flex; padding: 0; margin: 0; flex-wrap: wrap; } | ||||
| .subhyphae__entry { list-style-type: none; border: 1px solid #999; padding: 0; margin: .125rem; border-radius: .25rem; } | ||||
| .subhyphae__link { display: block; padding: .25rem; text-decoration: none; } | ||||
| .subhyphae__link:hover { background: #eee; } | ||||
| 
 | ||||
| .navitree { padding: 0; margin: 0; } | ||||
| .navitree__entry { } | ||||
| .navitree > .navitree__entry > a::before { display: inline-block; width: .5rem; color: #999; margin: 0 .25rem; } | ||||
| .navitree > .navitree__entry_infertile > a::before { content: " "} /* nbsp, careful */ | ||||
| .navitree > .navitree__sibling_fertile > a::before { content: "▸"} | ||||
| .navitree__trunk { border-left: 1px #999 solid; } | ||||
| .navitree__link { text-decoration: none; display: block; padding: .25rem; } | ||||
| .navitree__entry_this > span { display: block; padding: .25rem; font-weight: bold; } | ||||
| .navitree__entry_this > span::before { content: " "; display: inline-block; width: 1rem; } | ||||
| 
 | ||||
| 
 | ||||
| /* Color stuff */ | ||||
| /* Lighter stuff #eee */ | ||||
| article code, | ||||
| @@ -142,11 +220,24 @@ article .codeblock, | ||||
| .prevnext__el, | ||||
| table { background-color: #eee; } | ||||
| 
 | ||||
| .hypha-tabs__tab { background-color: #eee; } | ||||
| .hypha-tabs__tab a { color: black; } | ||||
| .hypha-tabs__tab_active { border-bottom: 2px white solid; background: white; } | ||||
| 
 | ||||
| @media screen and (max-width: 800px) { | ||||
| 	.hypha-tabs { background-color: white; } | ||||
| 	.hypha-tabs__tab { box-shadow: none; } | ||||
| 	.hypha-tabs, | ||||
| 	.hypha-tabs__tab { background-color: white; } | ||||
| } | ||||
| 
 | ||||
| @media screen and (min-width: 801px) { | ||||
| 	.hypha-tabs__tab { border: 1px #ddd solid; } | ||||
| 	.hypha-tabs__tab_active { border-bottom: 1px white solid; } | ||||
| } | ||||
| 
 | ||||
| .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; } | ||||
| 
 | ||||
| /* Other stuff */ | ||||
| html { background-color: #ddd;  | ||||
| background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); | ||||
| @@ -154,11 +245,7 @@ background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox= | ||||
| header { background-color: #bbb; } | ||||
| .header-links__link { color: black; } | ||||
| .header-links__link:hover { background-color: #eee; } | ||||
| 
 | ||||
| main, .hypha-tabs__tab { background-color: white; } | ||||
| .hypha-tabs__tab { clip-path: inset(-20px -20px 0 -20px); } | ||||
| .hypha-tabs__tab a { color: black; } | ||||
| .hypha-tabs__tab_active { border-bottom: 2px white solid; } | ||||
| main { background-color: white; } | ||||
| 
 | ||||
| blockquote { border-left: 4px black solid; } | ||||
| .wikilink_new {color:#a55858;} | ||||
| @@ -169,21 +256,24 @@ blockquote { border-left: 4px black solid; } | ||||
| .upload-amnt { border: #eee 1px solid; } | ||||
| td { border: #ddd 1px solid; } | ||||
| 
 | ||||
| .navitree__link:hover, .backlinks__link:hover { background-color: #eee; } | ||||
| 
 | ||||
| /* Dark theme! */ | ||||
| @media (prefers-color-scheme: dark) { | ||||
| html { background: #222; color: #ddd; } | ||||
| main,  article, .hypha-tabs__tab, header { background-color: #343434; color: #ddd; } | ||||
| main,  article, .hypha-tabs__tab, header, .layout-card { background-color: #343434; color: #ddd; } | ||||
| 
 | ||||
| a, .wikilink_external { color: #f1fa8c; } | ||||
| a:visited, .wikilink_external:visited { color: #ffb86c; } | ||||
| .wikilink_new, .wikilink_new:visited { color: #dd4444; } | ||||
| .navitree__link:hover, .backlinks__link:hover { background-color: #444; } | ||||
| 
 | ||||
| .header-links__link, .header-links__link:visited, | ||||
| .prevnext__el, .prevnext__el:visited { color: #ddd; } | ||||
| .header-links__link:hover { background-color: #444; } | ||||
| 
 | ||||
| .hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; } | ||||
| .hypha-tabs__tab_active { background-color: #343434; } | ||||
| .layout-card__title, .hypha-tabs__tab_active { background-color: #343434; } | ||||
| 
 | ||||
| blockquote { border-left: 4px #ddd solid; } | ||||
| 
 | ||||
| @@ -206,169 +296,170 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; } | ||||
| } | ||||
| } | ||||
| 
 | ||||
| .backlinks { display: none; } | ||||
| `) | ||||
| //line templates/asset.qtpl:2 | ||||
| //line assets/assets.qtpl:2 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| func WriteDefaultCSS(qq422016 qtio422016.Writer) { | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	StreamDefaultCSS(qw422016) | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| func DefaultCSS() string { | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	WriteDefaultCSS(qb422016) | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| 	return qs422016 | ||||
| //line templates/asset.qtpl:3 | ||||
| //line assets/assets.qtpl:3 | ||||
| } | ||||
| 
 | ||||
| // Next three are from https://remixicon.com/ | ||||
| 
 | ||||
| //line templates/asset.qtpl:6 | ||||
| //line assets/assets.qtpl:6 | ||||
| func StreamIconHTTP(qw422016 *qt422016.Writer) { | ||||
| //line templates/asset.qtpl:6 | ||||
| //line assets/assets.qtpl:6 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:7 | ||||
| //line assets/assets.qtpl:7 | ||||
| 	qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"/></svg> | ||||
| `) | ||||
| //line templates/asset.qtpl:7 | ||||
| //line assets/assets.qtpl:7 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| func WriteIconHTTP(qq422016 qtio422016.Writer) { | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	StreamIconHTTP(qw422016) | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| func IconHTTP() string { | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	WriteIconHTTP(qb422016) | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| 	return qs422016 | ||||
| //line templates/asset.qtpl:8 | ||||
| //line assets/assets.qtpl:8 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:10 | ||||
| //line assets/assets.qtpl:10 | ||||
| func StreamIconGemini(qw422016 *qt422016.Writer) { | ||||
| //line templates/asset.qtpl:10 | ||||
| //line assets/assets.qtpl:10 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:11 | ||||
| //line assets/assets.qtpl:11 | ||||
| 	qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M15.502 20A6.523 6.523 0 0 1 12 23.502 6.523 6.523 0 0 1 8.498 20h2.26c.326.489.747.912 1.242 1.243.495-.33.916-.754 1.243-1.243h2.259zM18 14.805l2 2.268V19H4v-1.927l2-2.268V9c0-3.483 2.504-6.447 6-7.545C15.496 2.553 18 5.517 18 9v5.805zM17.27 17L16 15.56V9c0-2.318-1.57-4.43-4-5.42C9.57 4.57 8 6.681 8 9v6.56L6.73 17h10.54zM12 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg> | ||||
| `) | ||||
| //line templates/asset.qtpl:11 | ||||
| //line assets/assets.qtpl:11 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| func WriteIconGemini(qq422016 qtio422016.Writer) { | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	StreamIconGemini(qw422016) | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| func IconGemini() string { | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	WriteIconGemini(qb422016) | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| 	return qs422016 | ||||
| //line templates/asset.qtpl:12 | ||||
| //line assets/assets.qtpl:12 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:14 | ||||
| //line assets/assets.qtpl:14 | ||||
| func StreamIconMailto(qw422016 *qt422016.Writer) { | ||||
| //line templates/asset.qtpl:14 | ||||
| //line assets/assets.qtpl:14 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:15 | ||||
| //line assets/assets.qtpl:15 | ||||
| 	qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm17 4.238l-7.928 7.1L4 7.216V19h16V7.238zM4.511 5l7.55 6.662L19.502 5H4.511z"/></svg> | ||||
| `) | ||||
| //line templates/asset.qtpl:15 | ||||
| //line assets/assets.qtpl:15 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| func WriteIconMailto(qq422016 qtio422016.Writer) { | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	StreamIconMailto(qw422016) | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| func IconMailto() string { | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	WriteIconMailto(qb422016) | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| 	return qs422016 | ||||
| //line templates/asset.qtpl:16 | ||||
| //line assets/assets.qtpl:16 | ||||
| } | ||||
| 
 | ||||
| // This is a modified version of https://www.svgrepo.com/svg/232085/rat | ||||
| 
 | ||||
| //line templates/asset.qtpl:19 | ||||
| //line assets/assets.qtpl:19 | ||||
| func StreamIconGopher(qw422016 *qt422016.Writer) { | ||||
| //line templates/asset.qtpl:19 | ||||
| //line assets/assets.qtpl:19 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:20 | ||||
| //line assets/assets.qtpl:20 | ||||
| 	qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16"> | ||||
| <path fill="#999" d="M447.238,204.944v-70.459c0-8.836-7.164-16-16-16c-34.051,0-64.414,21.118-75.079,55.286 | ||||
| C226.094,41.594,0,133.882,0,319.435c0,0.071,0.01,0.14,0.011,0.21c0.116,44.591,36.423,80.833,81.04,80.833h171.203 | ||||
| @@ -381,34 +472,34 @@ c55.425-8.382,107.014,29.269,115.759,84.394H295.484z"/> | ||||
| <circle fill="#999" cx="415.238" cy="260.05" r="21.166"/> | ||||
| </svg> | ||||
| `) | ||||
| //line templates/asset.qtpl:20 | ||||
| //line assets/assets.qtpl:20 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| func WriteIconGopher(qq422016 qtio422016.Writer) { | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	StreamIconGopher(qw422016) | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| } | ||||
| 
 | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| func IconGopher() string { | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	WriteIconGopher(qb422016) | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| 	return qs422016 | ||||
| //line templates/asset.qtpl:21 | ||||
| //line assets/assets.qtpl:21 | ||||
| } | ||||
| @@ -1,33 +1,99 @@ | ||||
| /* Layout stuff */ | ||||
| @media screen and (min-width: 800px) { | ||||
| 	main { padding:1rem 2rem; margin: 0 auto; width: 800px; } | ||||
| 	.hypha-tabs { padding: 1rem 2rem; margin: 0 auto; width: 800px; } | ||||
| 	header { margin: 0 auto; width: 800px; } | ||||
| .amnt-grid { display: grid; grid-template-columns: 1fr 1fr; } | ||||
| .upload-binary__input { display: block; margin: .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; } | ||||
| .hypha-list__link { text-decoration: none; display: inline-block; padding: .25rem; } | ||||
| .hypha-list__link:hover { text-decoration: underline; } | ||||
| .hypha-list__amnt-type { font-size: smaller; color: #999; } | ||||
| 
 | ||||
| /* General element positions, from small to big */ | ||||
| /* Phones and whatnot */ | ||||
| .layout { display: grid; row-gap: 1rem; } | ||||
| header { width: 100%; margin-bottom: 1rem; } | ||||
| .header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; } | ||||
| .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__link { display: inline-block; padding: .25rem; text-decoration: none; } | ||||
| 
 | ||||
| .hypha-tabs { padding: 0; margin: 0; } | ||||
| .hypha-tabs__tab { margin-right: .5rem; padding: 0; } | ||||
| .hypha-tabs__link { display: inline-block; padding: .25rem; text-decoration: none; } | ||||
| .hypha-tabs__selection { display: inline-block; padding: .25rem; font-weight: bold; } | ||||
| 
 | ||||
| .layout-card li { list-style-type: none; } | ||||
| .backlinks__list { padding: 0; margin: 0; } | ||||
| .backlinks__link { text-decoration: none; display: block; padding: .25rem; padding-left: 1.25rem; } | ||||
| 
 | ||||
| @media screen and (max-width: 800px) { | ||||
| 	.amnt-grid { grid-template-columns: 1fr; } | ||||
| 	.layout { grid-template-column: auto; grid-template-row: auto auto auto; } | ||||
| 	.main-width { width: 100%; } | ||||
| 	main { padding: 1rem; margin: 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; } | ||||
| 	main { border-radius: .25rem; } | ||||
| 	.layout-card { width: 800px; margin: 0 auto; } | ||||
| 
 | ||||
| 	.header-links { padding: 0; } | ||||
| 	.header-links__entry { margin-right: 1.5rem; } | ||||
| 	.header-links__entry_user { margin: 0 2rem 0 auto; } | ||||
| 	.header-links__entry:nth-of-type(1), | ||||
| 	.hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; } | ||||
| 	.hypha-tabs__tab { margin-right: 1.5rem; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); border-bottom: 2px #ddd solid; padding: 0 .5rem; } | ||||
| 
 | ||||
| 	.hypha-tabs { padding: 0; } | ||||
| 	.hypha-tabs__tab { border-radius: .25rem .25rem 0 0; margin-right: 0; } | ||||
| 	.hypha-tabs__selection, .hypha-tabs__link { padding: .25rem .5rem; } | ||||
| 
 | ||||
| 	.header-links__entry:nth-of-type(1), .hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; } | ||||
| } | ||||
| @media screen and (max-width: 800px) { | ||||
| 	main { padding: 1rem; margin: 0; width: 100%; } | ||||
| 	.hypha-tabs{ padding: 1rem; margin: 0; width: 100%; } | ||||
| 	.hypha-tabs__tab { box-shadow: none; margin-right: .5rem; padding: .25rem .5rem; } | ||||
| 	header { width: 100%; } | ||||
| 	.header-links__entry { margin-right: .5rem; } | ||||
| 
 | ||||
| /* Wide enough to fit two columns ok */ | ||||
| @media screen and (min-width: 1100px) { | ||||
| 	.layout { display: grid; grid-template-columns: auto 1fr; column-gap: 1rem; margin: 0 1rem; row-gap: 1rem; } | ||||
| 	.main-width { margin: 0; } | ||||
| 	main { grid-column: 1 / span 1; grid-row: 1 / span 2; } | ||||
| 	.relative-hyphae { grid-column: 2 / span 1; grid-row: 1 / span 1; } | ||||
| 	.layout-card { width: 100%; } | ||||
| } | ||||
| 
 | ||||
| @media screen and (min-width: 1250px) { | ||||
| 	.layout { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); } | ||||
| 	.layout-card { max-width: 16rem; } | ||||
| 	.main-width { margin: 0 auto; } | ||||
| 	.backlinks { grid-column: 1 / span 1; margin-right: 0; } | ||||
| 	main { grid-column: 2 / span 1; } | ||||
| 	.relative-hyphae { grid-column: 3 / span 1; margin-left: 0; } | ||||
| 
 | ||||
| 	.backlinks__title { text-align: right; } | ||||
| 	.backlinks__link { text-align: right; padding-right: 1.25rem; padding-left: .25rem; } | ||||
| } | ||||
| 
 | ||||
| *, *::before, *::after {box-sizing: border-box;} | ||||
| html { height:100%; padding:0; } | ||||
| body {height:100%; margin:0; font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;} | ||||
| main {border-radius: 0 0 .25rem .25rem; } | ||||
| body {height:100%; margin:0; } | ||||
| body, input { font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;} | ||||
| main > form {margin-bottom:1rem;} | ||||
| textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;} | ||||
| .edit_no-preview {height:100%;} | ||||
| .edit_with-preview .edit-form textarea { min-height: 500px; } | ||||
| 
 | ||||
| .edit { min-height: 80vh; } | ||||
| .edit__title { margin-top: 0; } | ||||
| .edit__preview { border: 2px dashed #ddd; } | ||||
| .edit-form {height:90%;} | ||||
| .edit-form textarea {width:100%;height:90%;} | ||||
| .edit-form {height:70vh;} | ||||
| .edit-form textarea {width:100%;height:95%;} | ||||
| .edit-form__save { font-weight: bold; } | ||||
| 
 | ||||
| .icon {margin-right: .25rem; vertical-align: bottom; } | ||||
| 
 | ||||
| main h1:not(.navi-title) {font-size:1.7rem;} | ||||
| @@ -42,7 +108,10 @@ blockquote { margin-left: 0; padding-left: 1rem; } | ||||
| .wikilink_mailto::before { content: url("/static/icon/mailto"); } | ||||
| 
 | ||||
| article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; } | ||||
| article h1, article h2, article h3, article h4, article h5, article h6 { margin: 1.5rem 0 0 0; } | ||||
| main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; } | ||||
| .heading__link { text-decoration: none; display: inline-block; } | ||||
| .heading__link::after { width: 1rem; content: "§"; color: transparent; } | ||||
| .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%; } | ||||
| @@ -60,6 +129,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25 | ||||
| .binary-container_with-video video, | ||||
| .binary-container_with-audio audio {width: 100%} | ||||
| 
 | ||||
| .subhyphae__title { padding-bottom: .5rem; clear: both; } | ||||
| .navi-title { padding-bottom: .5rem; margin: .25rem 0; } | ||||
| .navi-title a {text-decoration:none; } | ||||
| .navi-title__separator { margin: 0 .25rem; } | ||||
| @@ -76,19 +146,11 @@ figcaption { padding-bottom: .5rem; } | ||||
| 
 | ||||
| #new-name {width:100%;} | ||||
| 
 | ||||
| header { margin-bottom: .5rem; } | ||||
| .header-links__entry_user { font-style:italic; } | ||||
| .header-links__link { text-decoration: none; display: block; width: 100%; height: 100%; padding: .25rem; } | ||||
| .hypha-tabs { padding: 0; } | ||||
| .header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; } | ||||
| .header-links__entry, .hypha-tabs__tab { list-style-type: none; } | ||||
| .hypha-tabs__tab a { text-decoration: none; } | ||||
| .hypha-tabs__tab_active { font-weight: bold; } | ||||
| 
 | ||||
| .rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; } | ||||
| .rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; border-radius: .25rem; } | ||||
| .rc-entry__time { font-style: italic; } | ||||
| .rc-entry__hash { font-style: italic; text-align: right; } | ||||
| .rc-entry__links { grid-column: 1 / span 2; } | ||||
| .rc-entry__links, .rc-entry__msg { grid-column: 1 / span 2; } | ||||
| .rc-entry__author { font-style: italic; } | ||||
| 
 | ||||
| .prevnext__el { display: block-inline; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; } | ||||
| @@ -107,6 +169,22 @@ table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; } | ||||
| td { padding: .25rem; } | ||||
| caption { caption-side: top; font-size: small; } | ||||
| 
 | ||||
| .subhyphae__list, .subhyphae__list ul { display: flex; padding: 0; margin: 0; flex-wrap: wrap; } | ||||
| .subhyphae__entry { list-style-type: none; border: 1px solid #999; padding: 0; margin: .125rem; border-radius: .25rem; } | ||||
| .subhyphae__link { display: block; padding: .25rem; text-decoration: none; } | ||||
| .subhyphae__link:hover { background: #eee; } | ||||
| 
 | ||||
| .navitree { padding: 0; margin: 0; } | ||||
| .navitree__entry { } | ||||
| .navitree > .navitree__entry > a::before { display: inline-block; width: .5rem; color: #999; margin: 0 .25rem; } | ||||
| .navitree > .navitree__entry_infertile > a::before { content: " "} /* nbsp, careful */ | ||||
| .navitree > .navitree__sibling_fertile > a::before { content: "▸"} | ||||
| .navitree__trunk { border-left: 1px #999 solid; } | ||||
| .navitree__link { text-decoration: none; display: block; padding: .25rem; } | ||||
| .navitree__entry_this > span { display: block; padding: .25rem; font-weight: bold; } | ||||
| .navitree__entry_this > span::before { content: " "; display: inline-block; width: 1rem; } | ||||
| 
 | ||||
| 
 | ||||
| /* Color stuff */ | ||||
| /* Lighter stuff #eee */ | ||||
| article code, | ||||
| @@ -117,11 +195,24 @@ article .codeblock, | ||||
| .prevnext__el, | ||||
| table { background-color: #eee; } | ||||
| 
 | ||||
| .hypha-tabs__tab { background-color: #eee; } | ||||
| .hypha-tabs__tab a { color: black; } | ||||
| .hypha-tabs__tab_active { border-bottom: 2px white solid; background: white; } | ||||
| 
 | ||||
| @media screen and (max-width: 800px) { | ||||
| 	.hypha-tabs { background-color: white; } | ||||
| 	.hypha-tabs__tab { box-shadow: none; } | ||||
| 	.hypha-tabs, | ||||
| 	.hypha-tabs__tab { background-color: white; } | ||||
| } | ||||
| 
 | ||||
| @media screen and (min-width: 801px) { | ||||
| 	.hypha-tabs__tab { border: 1px #ddd solid; } | ||||
| 	.hypha-tabs__tab_active { border-bottom: 1px white solid; } | ||||
| } | ||||
| 
 | ||||
| .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; } | ||||
| 
 | ||||
| /* Other stuff */ | ||||
| html { background-color: #ddd;  | ||||
| background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); | ||||
| @@ -129,11 +220,7 @@ background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox= | ||||
| header { background-color: #bbb; } | ||||
| .header-links__link { color: black; } | ||||
| .header-links__link:hover { background-color: #eee; } | ||||
| 
 | ||||
| main, .hypha-tabs__tab { background-color: white; } | ||||
| .hypha-tabs__tab { clip-path: inset(-20px -20px 0 -20px); } | ||||
| .hypha-tabs__tab a { color: black; } | ||||
| .hypha-tabs__tab_active { border-bottom: 2px white solid; } | ||||
| main { background-color: white; } | ||||
| 
 | ||||
| blockquote { border-left: 4px black solid; } | ||||
| .wikilink_new {color:#a55858;} | ||||
| @@ -144,21 +231,24 @@ blockquote { border-left: 4px black solid; } | ||||
| .upload-amnt { border: #eee 1px solid; } | ||||
| td { border: #ddd 1px solid; } | ||||
| 
 | ||||
| .navitree__link:hover, .backlinks__link:hover { background-color: #eee; } | ||||
| 
 | ||||
| /* Dark theme! */ | ||||
| @media (prefers-color-scheme: dark) { | ||||
| html { background: #222; color: #ddd; } | ||||
| main,  article, .hypha-tabs__tab, header { background-color: #343434; color: #ddd; } | ||||
| main,  article, .hypha-tabs__tab, header, .layout-card { background-color: #343434; color: #ddd; } | ||||
| 
 | ||||
| a, .wikilink_external { color: #f1fa8c; } | ||||
| a:visited, .wikilink_external:visited { color: #ffb86c; } | ||||
| .wikilink_new, .wikilink_new:visited { color: #dd4444; } | ||||
| .navitree__link:hover, .backlinks__link:hover { background-color: #444; } | ||||
| 
 | ||||
| .header-links__link, .header-links__link:visited, | ||||
| .prevnext__el, .prevnext__el:visited { color: #ddd; } | ||||
| .header-links__link:hover { background-color: #444; } | ||||
| 
 | ||||
| .hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; } | ||||
| .hypha-tabs__tab_active { background-color: #343434; } | ||||
| .layout-card__title, .hypha-tabs__tab_active { background-color: #343434; } | ||||
| 
 | ||||
| blockquote { border-left: 4px #ddd solid; } | ||||
| 
 | ||||
| @@ -181,3 +271,4 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; } | ||||
| } | ||||
| } | ||||
| 
 | ||||
| .backlinks { display: none; } | ||||
| Before Width: | Height: | Size: 473 B After Width: | Height: | Size: 473 B | 
| Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 951 B | 
| Before Width: | Height: | Size: 627 B After Width: | Height: | Size: 627 B | 
| Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 261 B | 
							
								
								
									
										7
									
								
								flag.go
									
									
									
									
									
								
							
							
						
						| @@ -19,6 +19,7 @@ func init() { | ||||
| 	flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"") | ||||
| 	flag.StringVar(&util.FixedCredentialsPath, "fixed-credentials-path", "mycocredentials.json", "Used when -auth-method=fixed. Path to file with user credentials.") | ||||
| 	flag.StringVar(&util.HeaderLinksHypha, "header-links-hypha", "", "Optional hypha that overrides the header links") | ||||
| 	flag.StringVar(&util.GeminiCertPath, "gemini-cert-path", "", "Directory where you store Gemini certificates. Leave empty if you don't want to use Gemini.") | ||||
| } | ||||
|  | ||||
| // Do the things related to cli args and die maybe | ||||
| @@ -41,9 +42,9 @@ func parseCliArgs() { | ||||
| 		util.URL = "http://0.0.0.0:" + util.ServerPort | ||||
| 	} | ||||
|  | ||||
| 	util.HomePage = CanonicalName(util.HomePage) | ||||
| 	util.UserHypha = CanonicalName(util.UserHypha) | ||||
| 	util.HeaderLinksHypha = CanonicalName(util.HeaderLinksHypha) | ||||
| 	util.HomePage = util.CanonicalName(util.HomePage) | ||||
| 	util.UserHypha = util.CanonicalName(util.UserHypha) | ||||
| 	util.HeaderLinksHypha = util.CanonicalName(util.HeaderLinksHypha) | ||||
|  | ||||
| 	switch util.AuthMethod { | ||||
| 	case "none": | ||||
|   | ||||
							
								
								
									
										84
									
								
								gemini.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,84 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509/pkix" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.sr.ht/~adnano/go-gemini" | ||||
| 	"git.sr.ht/~adnano/go-gemini/certificate" | ||||
|  | ||||
| 	"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 | ||||
|  | ||||
| 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)) | ||||
| } | ||||
|  | ||||
| func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName = geminiHyphaNameFromRq(rq, "page", "hypha") | ||||
| 		h         = hyphae.ByName(hyphaName) | ||||
| 		hasAmnt   = h.Exists && h.BinaryPath != "" | ||||
| 		contents  string | ||||
| 	) | ||||
| 	if h.Exists { | ||||
| 		fileContentsT, errT := ioutil.ReadFile(h.TextPath) | ||||
| 		if errT == nil { | ||||
| 			md := markup.Doc(hyphaName, string(fileContentsT)) | ||||
| 			contents = md.AsGemtext() | ||||
| 		} | ||||
| 	} | ||||
| 	if hasAmnt { | ||||
| 		w.Write([]byte("This hypha has an attachment\n")) | ||||
| 	} | ||||
| 	w.Write([]byte(contents)) | ||||
| } | ||||
|  | ||||
| func handleGemini() { | ||||
| 	if util.GeminiCertPath == "" { | ||||
| 		return | ||||
| 	} | ||||
| 	certPath, err := filepath.Abs(util.GeminiCertPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	var server gemini.Server | ||||
| 	server.ReadTimeout = 30 * time.Second | ||||
| 	server.WriteTimeout = 1 * time.Minute | ||||
| 	if err := server.Certificates.Load(certPath); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	server.CreateCertificate = func(hostname string) (tls.Certificate, error) { | ||||
| 		return certificate.Create(certificate.CreateOptions{ | ||||
| 			Subject: pkix.Name{ | ||||
| 				CommonName: hostname, | ||||
| 			}, | ||||
| 			DNSNames: []string{hostname}, | ||||
| 			Duration: 365 * 24 * time.Hour, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	var mux gemini.ServeMux | ||||
| 	mux.HandleFunc("/", geminiHomeHypha) | ||||
| 	mux.HandleFunc("/hypha/", geminiHypha) | ||||
| 	mux.HandleFunc("/page/", geminiHypha) | ||||
|  | ||||
| 	server.Handle("localhost", &mux) | ||||
| 	if err := server.ListenAndServe(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						| @@ -3,8 +3,10 @@ module github.com/bouncepaw/mycorrhiza | ||||
| go 1.14 | ||||
|  | ||||
| require ( | ||||
| 	git.sr.ht/~adnano/go-gemini v0.1.13 | ||||
| 	github.com/adrg/xdg v0.2.2 | ||||
| 	github.com/gorilla/feeds v1.1.1 | ||||
| 	github.com/kr/pretty v0.2.1 // indirect | ||||
| 	github.com/valyala/quicktemplate v1.6.3 | ||||
| 	tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						| @@ -1,3 +1,5 @@ | ||||
| git.sr.ht/~adnano/go-gemini v0.1.13 h1:vzKkkVrOzMpfJ1AAeE/PChg0Rw5Zf+9HrnwsgVxXUT4= | ||||
| git.sr.ht/~adnano/go-gemini v0.1.13/go.mod h1:If1VxEWcZDrRt5FeAFnGTcM2Ud1E3BXs3VJ5rnZWKq0= | ||||
| 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= | ||||
| @@ -33,3 +35,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512 h1:reGEt1vmGompn/6FitHdBatILTsK9CYnQOCw3weoW/s= | ||||
| tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512/go.mod h1:gqBK7AJ5wPR1bpFOuPmlQObYxwXrFdZmNb2vdzquqoA= | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| @@ -77,8 +76,8 @@ func (rev Revision) TimeString() string { | ||||
| 	return rev.Time.Format(time.RFC822) | ||||
| } | ||||
|  | ||||
| // HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string. | ||||
| func (rev Revision) HyphaeLinks() (html string) { | ||||
| // HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string. | ||||
| func (rev Revision) HyphaeLinksHTML() (html string) { | ||||
| 	hyphae := rev.hyphaeAffected() | ||||
| 	for i, hyphaName := range hyphae { | ||||
| 		if i > 0 { | ||||
| @@ -92,7 +91,7 @@ func (rev Revision) HyphaeLinks() (html string) { | ||||
| func (rev *Revision) descriptionForFeed() (html string) { | ||||
| 	return fmt.Sprintf( | ||||
| 		`<p>%s</p> | ||||
| <p><b>Hyphae affected:</b> %s</p>`, rev.Message, rev.HyphaeLinks()) | ||||
| <p><b>Hyphae affected:</b> %s</p>`, rev.Message, rev.HyphaeLinksHTML()) | ||||
| } | ||||
|  | ||||
| // Try and guess what link is the most important by looking at the message. | ||||
| @@ -111,23 +110,6 @@ func (rev *Revision) bestLink() string { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (rev Revision) RecentChangesEntry() (html string) { | ||||
| 	if user.AuthUsed && rev.Username != "anon" { | ||||
| 		return fmt.Sprintf(` | ||||
| <li class="rc-entry__time"><time>%[1]s</time></li> | ||||
| <li class="rc-entry__hash">%[2]s</li> | ||||
| <li class="rc-entry__links">%[5]s</li> | ||||
| <li class="rc-entry__msg">%[6]s <span class="rc-entry__author">by <a href="/page/%[3]s/%[4]s" rel="author">%[4]s</a></span></li> | ||||
| `, rev.TimeString(), rev.Hash, util.UserHypha, rev.Username, rev.HyphaeLinks(), rev.Message) | ||||
| 	} | ||||
| 	return fmt.Sprintf(` | ||||
| <li class="rc-entry__time"><time>%[1]s</time></li> | ||||
| <li class="rc-entry__hash">%[2]s</li> | ||||
| <li class="rc-entry__links">%[3]s</li> | ||||
| <li class="rc-entry__msg">%[4]s</li> | ||||
| `, rev.TimeString(), rev.Hash, rev.HyphaeLinks(), rev.Message) | ||||
| } | ||||
|  | ||||
| // Path to git executable. Set at init() | ||||
| var gitpath string | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,6 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/templates" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/gorilla/feeds" | ||||
| ) | ||||
| @@ -61,7 +60,7 @@ func RecentChangesJSON() (string, error) { | ||||
| 	return recentChangesFeed().ToJSON() | ||||
| } | ||||
|  | ||||
| func RecentChanges(n int) string { | ||||
| func RecentChanges(n int) []Revision { | ||||
| 	var ( | ||||
| 		out, err = gitsh( | ||||
| 			"log", "--oneline", "--no-merges", | ||||
| @@ -75,11 +74,7 @@ func RecentChanges(n int) string { | ||||
| 			revs = append(revs, parseRevisionLine(line)) | ||||
| 		} | ||||
| 	} | ||||
| 	entries := make([]string, len(revs)) | ||||
| 	for i, rev := range revs { | ||||
| 		entries[i] = rev.RecentChangesEntry() | ||||
| 	} | ||||
| 	return templates.RecentChangesHTML(entries, n) | ||||
| 	return revs | ||||
| } | ||||
|  | ||||
| // FileChanged tells you if the file has been changed. | ||||
| @@ -177,6 +172,6 @@ func parseRevisionLine(line string) Revision { | ||||
|  | ||||
| // See how the file with `filepath` looked at commit with `hash`. | ||||
| func FileAtRevision(filepath, hash string) (string, error) { | ||||
| 	out, err := gitsh("show", hash+":"+filepath) | ||||
| 	out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, util.WikiDir+"/")) | ||||
| 	return out.String(), err | ||||
| } | ||||
|   | ||||
| @@ -59,12 +59,16 @@ func (hop *HistoryOp) gitop(args ...string) *HistoryOp { | ||||
| 	return hop | ||||
| } | ||||
|  | ||||
| // WithError appends the `err` to the list of errors. | ||||
| func (hop *HistoryOp) WithError(err error) *HistoryOp { | ||||
| // WithErr appends the `err` to the list of errors. | ||||
| func (hop *HistoryOp) WithErr(err error) *HistoryOp { | ||||
| 	hop.Errs = append(hop.Errs, err) | ||||
| 	return hop | ||||
| } | ||||
|  | ||||
| func (hop *HistoryOp) WithErrAbort(err error) *HistoryOp { | ||||
| 	return hop.WithErr(err).Abort() | ||||
| } | ||||
|  | ||||
| // WithFilesRemoved git-rm-s all passed `paths`. Paths can be rooted or not. Paths that are empty strings are ignored. | ||||
| func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp { | ||||
| 	args := []string{"rm", "--quiet", "--"} | ||||
| @@ -134,3 +138,11 @@ func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp { | ||||
| 	} | ||||
| 	return hop | ||||
| } | ||||
|  | ||||
| func (hop *HistoryOp) HasErrors() bool { | ||||
| 	return len(hop.Errs) > 0 | ||||
| } | ||||
|  | ||||
| func (hop *HistoryOp) FirstErrorText() string { | ||||
| 	return hop.Errs[0].Error() | ||||
| } | ||||
|   | ||||
							
								
								
									
										33
									
								
								http_admin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/views" | ||||
| ) | ||||
|  | ||||
| // This is not init(), because user.AuthUsed is not set at init-stage. | ||||
| func initAdmin() { | ||||
| 	if user.AuthUsed { | ||||
| 		http.HandleFunc("/admin", handlerAdmin) | ||||
| 		http.HandleFunc("/admin/shutdown", handlerAdminShutdown) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handlerAdmin(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	if user.CanProceed(rq, "admin") { | ||||
| 		w.Header().Set("Content-Type", "text/html;charset=utf-8") | ||||
| 		w.WriteHeader(http.StatusOK) | ||||
| 		w.Write([]byte(base("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq)))) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" { | ||||
| 		log.Fatal("An admin commanded the wiki to shutdown") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										11
									
								
								http_auth.go
									
									
									
									
									
								
							
							
						
						| @@ -4,8 +4,9 @@ import ( | ||||
| 	"log" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/templates" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/bouncepaw/mycorrhiza/views" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @@ -28,7 +29,7 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) { | ||||
| 		log.Println("Unknown user tries to log out") | ||||
| 		w.WriteHeader(http.StatusForbidden) | ||||
| 	} | ||||
| 	w.Write([]byte(base("Logout?", templates.LogoutHTML(can), u))) | ||||
| 	w.Write([]byte(base("Logout?", views.LogoutHTML(can), u))) | ||||
| } | ||||
|  | ||||
| func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) { | ||||
| @@ -39,12 +40,12 @@ func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) { | ||||
| func handlerLoginData(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		username = CanonicalName(rq.PostFormValue("username")) | ||||
| 		username = util.CanonicalName(rq.PostFormValue("username")) | ||||
| 		password = rq.PostFormValue("password") | ||||
| 		err      = user.LoginDataHTTP(w, rq, username, password) | ||||
| 	) | ||||
| 	if err != "" { | ||||
| 		w.Write([]byte(base(err, templates.LoginErrorHTML(err), user.EmptyUser()))) | ||||
| 		w.Write([]byte(base(err, views.LoginErrorHTML(err), user.EmptyUser()))) | ||||
| 	} else { | ||||
| 		http.Redirect(w, rq, "/", http.StatusSeeOther) | ||||
| 	} | ||||
| @@ -58,5 +59,5 @@ func handlerLogin(w http.ResponseWriter, rq *http.Request) { | ||||
| 	} else { | ||||
| 		w.WriteHeader(http.StatusForbidden) | ||||
| 	} | ||||
| 	w.Write([]byte(base("Login", templates.LoginHTML(), user.EmptyUser()))) | ||||
| 	w.Write([]byte(base("Login", views.LoginHTML(), user.EmptyUser()))) | ||||
| } | ||||
|   | ||||
| @@ -8,9 +8,9 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/templates" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/bouncepaw/mycorrhiza/views" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @@ -35,7 +35,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println("Found", len(revs), "revisions for", hyphaName) | ||||
|  | ||||
| 	util.HTTP200Page(w, | ||||
| 		base(hyphaName, templates.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq))) | ||||
| 		base(hyphaName, views.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq))) | ||||
| } | ||||
|  | ||||
| // Recent changes | ||||
| @@ -46,7 +46,7 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { | ||||
| 		n, err   = strconv.Atoi(noPrefix) | ||||
| 	) | ||||
| 	if err == nil && n < 101 { | ||||
| 		util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n), user.FromRequest(rq))) | ||||
| 		util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", views.RecentChangesHTML(n), user.FromRequest(rq))) | ||||
| 	} else { | ||||
| 		http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										352
									
								
								http_mutators.go
									
									
									
									
									
								
							
							
						
						| @@ -5,10 +5,13 @@ import ( | ||||
| 	"log" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/markup" | ||||
| 	"github.com/bouncepaw/mycorrhiza/templates" | ||||
| 	"github.com/bouncepaw/mycorrhiza/shroom" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/bouncepaw/mycorrhiza/views" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @@ -25,182 +28,140 @@ func init() { | ||||
| 	http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm) | ||||
| } | ||||
|  | ||||
| func handlerUnattachAsk(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "unattach-ask") | ||||
| 		hd, isOld = HyphaStorage[hyphaName] | ||||
| 		hasAmnt   = hd != nil && hd.binaryPath != "" | ||||
| 	) | ||||
| 	if !hasAmnt { | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach") | ||||
| 		log.Println("Rejected (no amnt):", rq.URL) | ||||
| 		return | ||||
| 	} else if ok := user.CanProceed(rq, "unattach-confirm"); !ok { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments") | ||||
| 		log.Println("Rejected (no rights):", rq.URL) | ||||
| 		return | ||||
| 	} | ||||
| 	util.HTTP200Page(w, base("Unattach "+hyphaName+"?", templates.UnattachAskHTML(rq, hyphaName, isOld), user.FromRequest(rq))) | ||||
| } | ||||
|  | ||||
| func handlerUnattachConfirm(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName        = HyphaNameFromRq(rq, "unattach-confirm") | ||||
| 		hyphaData, isOld = HyphaStorage[hyphaName] | ||||
| 		hasAmnt          = hyphaData != nil && hyphaData.binaryPath != "" | ||||
| 		u                = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if !u.CanProceed("unattach-confirm") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments") | ||||
| 		log.Println("Rejected (no rights):", rq.URL) | ||||
| 		return | ||||
| 	} | ||||
| 	if !hasAmnt { | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach") | ||||
| 		log.Println("Rejected (no amnt):", rq.URL) | ||||
| 		return | ||||
| 	} else if !isOld { | ||||
| 		// The precondition is to have the hypha in the first place. | ||||
| 		HttpErr(w, http.StatusPreconditionFailed, hyphaName, | ||||
| 			"Error: no such hypha", | ||||
| 			"Could not unattach this hypha because it does not exist") | ||||
| 		return | ||||
| 	} | ||||
| 	if hop := hyphaData.UnattachHypha(hyphaName, u); len(hop.Errs) != 0 { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 			"Error: could not unattach hypha", | ||||
| 			fmt.Sprintf("Could not unattach this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs)) | ||||
| 		return | ||||
| 	} | ||||
| 	http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) | ||||
| } | ||||
|  | ||||
| func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "rename-ask") | ||||
| 		_, isOld  = HyphaStorage[hyphaName] | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if !u.CanProceed("rename-confirm") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.") | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 		return | ||||
| 	} | ||||
| 	util.HTTP200Page(w, base("Rename "+hyphaName+"?", templates.RenameAskHTML(rq, hyphaName, isOld), u)) | ||||
| } | ||||
|  | ||||
| func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName        = HyphaNameFromRq(rq, "rename-confirm") | ||||
| 		_, isOld         = HyphaStorage[hyphaName] | ||||
| 		newName          = CanonicalName(rq.PostFormValue("new-name")) | ||||
| 		_, newNameIsUsed = HyphaStorage[newName] | ||||
| 		recursive        = rq.PostFormValue("recursive") == "true" | ||||
| 		u                = user.FromRequest(rq) | ||||
| 	) | ||||
| 	switch { | ||||
| 	case !u.CanProceed("rename-confirm"): | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.") | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 	case newNameIsUsed: | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists", | ||||
| 			fmt.Sprintf("Hypha named <a href='/page/%s'>%s</a> already exists.", hyphaName, hyphaName)) | ||||
| 	case newName == "": | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no name", | ||||
| 			"No new name is given.") | ||||
| 	case !isOld: | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no such hypha", | ||||
| 			"Cannot rename a hypha that does not exist yet.") | ||||
| 	case !HyphaPattern.MatchString(newName): | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name", | ||||
| 			"Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>") | ||||
| 	default: | ||||
| 		if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 { | ||||
| 			HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 				"Error: could not rename hypha", | ||||
| 				fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs)) | ||||
| 		} else { | ||||
| 			http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther) | ||||
| func factoryHandlerAsker( | ||||
| 	actionPath string, | ||||
| 	asker func(*user.User, *hyphae.Hypha) (error, string), | ||||
| 	succTitleTemplate string, | ||||
| 	succPageTemplate func(*http.Request, string, bool) string, | ||||
| ) func(http.ResponseWriter, *http.Request) { | ||||
| 	return func(w http.ResponseWriter, rq *http.Request) { | ||||
| 		log.Println(rq.URL) | ||||
| 		var ( | ||||
| 			hyphaName = HyphaNameFromRq(rq, actionPath) | ||||
| 			h         = hyphae.ByName(hyphaName) | ||||
| 			u         = user.FromRequest(rq) | ||||
| 		) | ||||
| 		if err, errtitle := asker(u, h); err != nil { | ||||
| 			HttpErr( | ||||
| 				w, | ||||
| 				http.StatusInternalServerError, | ||||
| 				hyphaName, | ||||
| 				errtitle, | ||||
| 				err.Error()) | ||||
| 			return | ||||
| 		} | ||||
| 		util.HTTP200Page( | ||||
| 			w, | ||||
| 			base( | ||||
| 				fmt.Sprintf(succTitleTemplate, hyphaName), | ||||
| 				succPageTemplate(rq, hyphaName, h.Exists), | ||||
| 				u)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // handlerDeleteAsk shows a delete dialog. | ||||
| func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "delete-ask") | ||||
| 		_, isOld  = HyphaStorage[hyphaName] | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if !u.CanProceed("delete-ask") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.") | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 		return | ||||
| var handlerUnattachAsk = factoryHandlerAsker( | ||||
| 	"unattach-ask", | ||||
| 	shroom.CanUnattach, | ||||
| 	"Unattach %s?", | ||||
| 	views.UnattachAskHTML, | ||||
| ) | ||||
|  | ||||
| var handlerDeleteAsk = factoryHandlerAsker( | ||||
| 	"delete-ask", | ||||
| 	shroom.CanDelete, | ||||
| 	"Delete %s?", | ||||
| 	views.DeleteAskHTML, | ||||
| ) | ||||
|  | ||||
| var handlerRenameAsk = factoryHandlerAsker( | ||||
| 	"rename-ask", | ||||
| 	shroom.CanRename, | ||||
| 	"Rename %s?", | ||||
| 	views.RenameAskHTML, | ||||
| ) | ||||
|  | ||||
| func factoryHandlerConfirmer( | ||||
| 	actionPath string, | ||||
| 	confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string), | ||||
| ) func(http.ResponseWriter, *http.Request) { | ||||
| 	return func(w http.ResponseWriter, rq *http.Request) { | ||||
| 		log.Println(rq.URL) | ||||
| 		var ( | ||||
| 			hyphaName = HyphaNameFromRq(rq, actionPath) | ||||
| 			h         = hyphae.ByName(hyphaName) | ||||
| 			u         = user.FromRequest(rq) | ||||
| 		) | ||||
| 		if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() { | ||||
| 			HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 				errtitle, | ||||
| 				hop.FirstErrorText()) | ||||
| 			return | ||||
| 		} | ||||
| 		http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther) | ||||
| 	} | ||||
| 	util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(rq, hyphaName, isOld), u)) | ||||
| } | ||||
|  | ||||
| // handlerDeleteConfirm deletes a hypha for sure | ||||
| func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName        = HyphaNameFromRq(rq, "delete-confirm") | ||||
| 		hyphaData, isOld = HyphaStorage[hyphaName] | ||||
| 		u                = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if !u.CanProceed("delete-confirm") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.") | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 		return | ||||
| 	} | ||||
| 	if !isOld { | ||||
| 		// The precondition is to have the hypha in the first place. | ||||
| 		HttpErr(w, http.StatusPreconditionFailed, hyphaName, | ||||
| 			"Error: no such hypha", | ||||
| 			"Could not delete this hypha because it does not exist.") | ||||
| 		return | ||||
| 	} | ||||
| 	if hop := hyphaData.DeleteHypha(hyphaName, u); len(hop.Errs) != 0 { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 			"Error: could not delete hypha", | ||||
| 			fmt.Sprintf("Could not delete this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs)) | ||||
| 		return | ||||
| 	} | ||||
| 	http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) | ||||
| } | ||||
| var handlerUnattachConfirm = factoryHandlerConfirmer( | ||||
| 	"unattach-confirm", | ||||
| 	func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) { | ||||
| 		return shroom.UnattachHypha(u, h) | ||||
| 	}, | ||||
| ) | ||||
|  | ||||
| var handlerDeleteConfirm = factoryHandlerConfirmer( | ||||
| 	"delete-confirm", | ||||
| 	func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) { | ||||
| 		return shroom.DeleteHypha(u, h) | ||||
| 	}, | ||||
| ) | ||||
|  | ||||
| var handlerRenameConfirm = factoryHandlerConfirmer( | ||||
| 	"rename-confirm", | ||||
| 	func(oldHypha *hyphae.Hypha, u *user.User, rq *http.Request) (*history.HistoryOp, string) { | ||||
| 		var ( | ||||
| 			newName   = util.CanonicalName(rq.PostFormValue("new-name")) | ||||
| 			recursive = rq.PostFormValue("recursive") == "true" | ||||
| 			newHypha  = hyphae.ByName(newName) | ||||
| 		) | ||||
| 		return shroom.RenameHypha(oldHypha, newHypha, recursive, u) | ||||
| 	}, | ||||
| ) | ||||
|  | ||||
| // handlerEdit shows the edit form. It doesn't edit anything actually. | ||||
| func handlerEdit(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName        = HyphaNameFromRq(rq, "edit") | ||||
| 		hyphaData, isOld = HyphaStorage[hyphaName] | ||||
| 		warning          string | ||||
| 		textAreaFill     string | ||||
| 		err              error | ||||
| 		u                = user.FromRequest(rq) | ||||
| 		hyphaName    = HyphaNameFromRq(rq, "edit") | ||||
| 		h            = hyphae.ByName(hyphaName) | ||||
| 		warning      string | ||||
| 		textAreaFill string | ||||
| 		err          error | ||||
| 		u            = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if !u.CanProceed("edit") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.") | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 	if err, errtitle := shroom.CanEdit(u, h); err != nil { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 			errtitle, | ||||
| 			err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	if isOld { | ||||
| 		textAreaFill, err = FetchTextPart(hyphaData) | ||||
| 	if h.Exists { | ||||
| 		textAreaFill, err = shroom.FetchTextPart(h) | ||||
| 		if err != nil { | ||||
| 			log.Println(err) | ||||
| 			HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", "Could not fetch text data") | ||||
| 			HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 				"Error", | ||||
| 				"Could not fetch text data") | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		warning = `<p>You are creating a new hypha.</p>` | ||||
| 		warning = `<p class="warning warning_new-hypha">You are creating a new hypha.</p>` | ||||
| 	} | ||||
| 	util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning), u)) | ||||
| 	util.HTTP200Page( | ||||
| 		w, | ||||
| 		base( | ||||
| 			"Edit "+hyphaName, | ||||
| 			views.EditHTML(rq, hyphaName, textAreaFill, warning), | ||||
| 			u)) | ||||
| } | ||||
|  | ||||
| // handlerUploadText uploads a new text part for the hypha. | ||||
| @@ -208,62 +169,79 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "upload-text") | ||||
| 		h         = hyphae.ByName(hyphaName) | ||||
| 		textData  = rq.PostFormValue("text") | ||||
| 		action    = rq.PostFormValue("action") | ||||
| 		u         = user.FromRequest(rq) | ||||
| 		hop       *history.HistoryOp | ||||
| 		errtitle  string | ||||
| 	) | ||||
| 	if !u.CanProceed("upload-text") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.") | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 		return | ||||
| 	} | ||||
| 	if textData == "" { | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed") | ||||
| 		return | ||||
|  | ||||
| 	if action != "Preview" { | ||||
| 		hop, errtitle = shroom.UploadText(h, []byte(textData), u) | ||||
| 		if hop.HasErrors() { | ||||
| 			HttpErr(w, http.StatusForbidden, hyphaName, | ||||
| 				errtitle, | ||||
| 				hop.FirstErrorText()) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if action == "Preview" { | ||||
| 		util.HTTP200Page(w, base("Preview "+hyphaName, templates.PreviewHTML(rq, hyphaName, textData, "", markup.Doc(hyphaName, textData).AsHTML()), u)) | ||||
| 	} else if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) | ||||
| 		util.HTTP200Page( | ||||
| 			w, | ||||
| 			base( | ||||
| 				"Preview "+hyphaName, | ||||
| 				views.PreviewHTML( | ||||
| 					rq, | ||||
| 					hyphaName, | ||||
| 					textData, | ||||
| 					"", | ||||
| 					markup.Doc(hyphaName, textData).AsHTML()), | ||||
| 				u)) | ||||
| 	} else { | ||||
| 		http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) | ||||
| 		http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // handlerUploadBinary uploads a new binary part for the hypha. | ||||
| func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "upload-binary") | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if !u.CanProceed("upload-binary") { | ||||
| 		HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.") | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	rq.ParseMultipartForm(10 << 20) // Set upload limit | ||||
| 	file, handler, err := rq.FormFile("binary") | ||||
| 	if file != nil { | ||||
| 		defer file.Close() | ||||
| 	var ( | ||||
| 		hyphaName          = HyphaNameFromRq(rq, "upload-binary") | ||||
| 		h                  = hyphae.ByName(hyphaName) | ||||
| 		u                  = user.FromRequest(rq) | ||||
| 		file, handler, err = rq.FormFile("binary") | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 			"Error", | ||||
| 			err.Error()) | ||||
| 	} | ||||
| 	if err, errtitle := shroom.CanAttach(u, h); err != nil { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, | ||||
| 			errtitle, | ||||
| 			err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	// If file is not passed: | ||||
| 	if err != nil { | ||||
| 		HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// If file is passed: | ||||
| 	if file != nil { | ||||
| 		defer file.Close() | ||||
| 	} | ||||
| 	var ( | ||||
| 		mime = handler.Header.Get("Content-Type") | ||||
| 		hop  = UploadBinary(hyphaName, mime, file, u) | ||||
| 		mime          = handler.Header.Get("Content-Type") | ||||
| 		hop, errtitle = shroom.UploadBinary(h, mime, file, u) | ||||
| 	) | ||||
|  | ||||
| 	if len(hop.Errs) != 0 { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) | ||||
| 	if hop.HasErrors() { | ||||
| 		HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, hop.FirstErrorText()) | ||||
| 		return | ||||
| 	} | ||||
| 	http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) | ||||
| 	http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther) | ||||
| } | ||||
|   | ||||
| @@ -10,18 +10,35 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/markup" | ||||
| 	"github.com/bouncepaw/mycorrhiza/templates" | ||||
| 	"github.com/bouncepaw/mycorrhiza/tree" | ||||
| 	"github.com/bouncepaw/mycorrhiza/mimetype" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/bouncepaw/mycorrhiza/views" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	http.HandleFunc("/page/", handlerPage) | ||||
| 	http.HandleFunc("/page/", handlerHypha) | ||||
| 	http.HandleFunc("/hypha/", handlerHypha) | ||||
| 	http.HandleFunc("/text/", handlerText) | ||||
| 	http.HandleFunc("/binary/", handlerBinary) | ||||
| 	http.HandleFunc("/rev/", handlerRevision) | ||||
| 	http.HandleFunc("/attachment/", handlerAttachment) | ||||
| } | ||||
|  | ||||
| func handlerAttachment(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName = HyphaNameFromRq(rq, "attachment") | ||||
| 		h         = hyphae.ByName(hyphaName) | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	util.HTTP200Page(w, | ||||
| 		views.BaseHTML( | ||||
| 			fmt.Sprintf("Attachment of %s", util.BeautifulName(hyphaName)), | ||||
| 			views.AttachmentMenuHTML(rq, h, u), | ||||
| 			u)) | ||||
| } | ||||
|  | ||||
| // handlerRevision displays a specific revision of text part a page | ||||
| @@ -31,37 +48,34 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { | ||||
| 		shorterUrl        = strings.TrimPrefix(rq.URL.Path, "/rev/") | ||||
| 		firstSlashIndex   = strings.IndexRune(shorterUrl, '/') | ||||
| 		revHash           = shorterUrl[:firstSlashIndex] | ||||
| 		hyphaName         = CanonicalName(shorterUrl[firstSlashIndex+1:]) | ||||
| 		hyphaName         = util.CanonicalName(shorterUrl[firstSlashIndex+1:]) | ||||
| 		h                 = hyphae.ByName(hyphaName) | ||||
| 		contents          = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`) | ||||
| 		textPath          = hyphaName + ".myco" | ||||
| 		textContents, err = history.FileAtRevision(textPath, revHash) | ||||
| 		textContents, err = history.FileAtRevision(h.TextPath, revHash) | ||||
| 		u                 = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if err == nil { | ||||
| 		contents = markup.Doc(hyphaName, textContents).AsHTML() | ||||
| 	} | ||||
| 	treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith) | ||||
| 	page := templates.RevisionHTML( | ||||
| 	page := views.RevisionHTML( | ||||
| 		rq, | ||||
| 		hyphaName, | ||||
| 		naviTitle(hyphaName), | ||||
| 		h, | ||||
| 		contents, | ||||
| 		treeHTML, | ||||
| 		revHash, | ||||
| 	) | ||||
| 	w.Header().Set("Content-Type", "text/html;charset=utf-8") | ||||
| 	w.WriteHeader(http.StatusOK) | ||||
| 	w.Write([]byte(base(hyphaName, page, u))) | ||||
| 	w.Write([]byte(base(util.BeautifulName(hyphaName), page, u))) | ||||
| } | ||||
|  | ||||
| // handlerText serves raw source text of the hypha. | ||||
| func handlerText(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	hyphaName := HyphaNameFromRq(rq, "text") | ||||
| 	if data, ok := HyphaStorage[hyphaName]; ok { | ||||
| 		log.Println("Serving", data.textPath) | ||||
| 	if h := hyphae.ByName(hyphaName); h.Exists { | ||||
| 		log.Println("Serving", h.TextPath) | ||||
| 		w.Header().Set("Content-Type", "text/plain; charset=utf-8") | ||||
| 		http.ServeFile(w, rq, data.textPath) | ||||
| 		http.ServeFile(w, rq, h.TextPath) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -69,45 +83,39 @@ func handlerText(w http.ResponseWriter, rq *http.Request) { | ||||
| func handlerBinary(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	hyphaName := HyphaNameFromRq(rq, "binary") | ||||
| 	if data, ok := HyphaStorage[hyphaName]; ok { | ||||
| 		log.Println("Serving", data.binaryPath) | ||||
| 		w.Header().Set("Content-Type", ExtensionToMime(filepath.Ext(data.binaryPath))) | ||||
| 		http.ServeFile(w, rq, data.binaryPath) | ||||
| 	if h := hyphae.ByName(hyphaName); h.Exists { | ||||
| 		log.Println("Serving", h.BinaryPath) | ||||
| 		w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath))) | ||||
| 		http.ServeFile(w, rq, h.BinaryPath) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // handlerPage is the main hypha action that displays the hypha and the binary upload form along with some navigation. | ||||
| func handlerPage(w http.ResponseWriter, rq *http.Request) { | ||||
| // handlerHypha is the main hypha action that displays the hypha and the binary upload form along with some navigation. | ||||
| func handlerHypha(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		hyphaName         = HyphaNameFromRq(rq, "page") | ||||
| 		data, hyphaExists = HyphaStorage[hyphaName] | ||||
| 		hasAmnt           = hyphaExists && data.binaryPath != "" | ||||
| 		contents          string | ||||
| 		openGraph         string | ||||
| 		u                 = user.FromRequest(rq) | ||||
| 		hyphaName = HyphaNameFromRq(rq, "page", "hypha") | ||||
| 		h         = hyphae.ByName(hyphaName) | ||||
| 		contents  string | ||||
| 		openGraph string | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	if hyphaExists { | ||||
| 		fileContentsT, errT := ioutil.ReadFile(data.textPath) | ||||
| 		_, errB := os.Stat(data.binaryPath) | ||||
| 	if h.Exists { | ||||
| 		fileContentsT, errT := ioutil.ReadFile(h.TextPath) | ||||
| 		_, errB := os.Stat(h.BinaryPath) | ||||
| 		if errT == nil { | ||||
| 			md := markup.Doc(hyphaName, string(fileContentsT)) | ||||
| 			contents = md.AsHTML() | ||||
| 			openGraph = md.OpenGraphHTML() | ||||
| 		} | ||||
| 		if !os.IsNotExist(errB) { | ||||
| 			contents = binaryHtmlBlock(hyphaName, data) + contents | ||||
| 			contents = views.AttachmentHTML(h) + contents | ||||
| 		} | ||||
| 	} | ||||
| 	treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName, IterateHyphaNamesWith) | ||||
| 	util.HTTP200Page(w, | ||||
| 		templates.BaseHTML( | ||||
| 			hyphaName, | ||||
| 			templates.PageHTML(rq, hyphaName, | ||||
| 				naviTitle(hyphaName), | ||||
| 				contents, | ||||
| 				treeHTML, prevHypha, nextHypha, | ||||
| 				hasAmnt), | ||||
| 		views.BaseHTML( | ||||
| 			util.BeautifulName(hyphaName), | ||||
| 			views.HyphaHTML(rq, h, contents), | ||||
| 			u, | ||||
| 			openGraph)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										329
									
								
								hypha.go
									
									
									
									
									
								
							
							
						
						| @@ -1,329 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"mime/multipart" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/markup" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	markup.HyphaExists = func(hyphaName string) bool { | ||||
| 		_, hyphaExists := HyphaStorage[hyphaName] | ||||
| 		return hyphaExists | ||||
| 	} | ||||
| 	markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { | ||||
| 		if hyphaData, ok := HyphaStorage[hyphaName]; ok { | ||||
| 			rawText, err = FetchTextPart(hyphaData) | ||||
| 			if hyphaData.binaryPath != "" { | ||||
| 				binaryBlock = binaryHtmlBlock(hyphaName, hyphaData) | ||||
| 			} | ||||
| 		} else { | ||||
| 			err = errors.New("Hypha " + hyphaName + " does not exist") | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	markup.HyphaIterate = IterateHyphaNamesWith | ||||
| 	markup.HyphaImageForOG = func(hyphaName string) string { | ||||
| 		if hd, isOld := GetHyphaData(hyphaName); isOld && hd.binaryPath != "" { | ||||
| 			return util.URL + "/binary/" + hyphaName | ||||
| 		} | ||||
| 		return util.URL + "/favicon.ico" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist. | ||||
| func GetHyphaData(hyphaName string) (hyphaData *HyphaData, isOld bool) { | ||||
| 	hyphaData, isOld = HyphaStorage[hyphaName] | ||||
| 	if hyphaData == nil { | ||||
| 		hyphaData = &HyphaData{} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types. | ||||
| type HyphaData struct { | ||||
| 	textPath   string | ||||
| 	binaryPath string | ||||
| } | ||||
|  | ||||
| // uploadHelp is a helper function for UploadText and UploadBinary | ||||
| func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp { | ||||
| 	var ( | ||||
| 		hyphaData, isOld = GetHyphaData(hyphaName) | ||||
| 		fullPath         = filepath.Join(WikiDir, hyphaName+ext) | ||||
| 		originalFullPath = &hyphaData.textPath | ||||
| 	) | ||||
| 	if hop.Type == history.TypeEditBinary { | ||||
| 		originalFullPath = &hyphaData.binaryPath | ||||
| 	} | ||||
|  | ||||
| 	if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { | ||||
| 		return hop.WithError(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { | ||||
| 		return hop.WithError(err) | ||||
| 	} | ||||
|  | ||||
| 	if isOld && *originalFullPath != fullPath && *originalFullPath != "" { | ||||
| 		if err := history.Rename(*originalFullPath, fullPath); err != nil { | ||||
| 			return hop.WithError(err) | ||||
| 		} | ||||
| 		log.Println("Move", *originalFullPath, "to", fullPath) | ||||
| 	} | ||||
| 	// New hyphae must be added to the hypha storage | ||||
| 	if !isOld { | ||||
| 		HyphaStorage[hyphaName] = hyphaData | ||||
| 		hyphae.IncrementCount() | ||||
| 	} | ||||
| 	*originalFullPath = fullPath | ||||
| 	if isOld && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) { | ||||
| 		return hop.Abort() | ||||
| 	} | ||||
| 	return hop.WithFiles(fullPath). | ||||
| 		WithUser(u). | ||||
| 		Apply() | ||||
| } | ||||
|  | ||||
| // UploadText loads a new text part from `textData` for hypha `hyphaName`. | ||||
| func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp { | ||||
| 	return uploadHelp( | ||||
| 		history. | ||||
| 			Operation(history.TypeEditText). | ||||
| 			WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)), | ||||
| 		hyphaName, ".myco", []byte(textData), u) | ||||
| } | ||||
|  | ||||
| // UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`. | ||||
| func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *history.HistoryOp { | ||||
| 	var ( | ||||
| 		hop       = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mime)) | ||||
| 		data, err = ioutil.ReadAll(file) | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return hop.WithError(err).Apply() | ||||
| 	} | ||||
| 	return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u) | ||||
| } | ||||
|  | ||||
| // DeleteHypha deletes hypha and makes a history record about that. | ||||
| func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.HistoryOp { | ||||
| 	hop := history.Operation(history.TypeDeleteHypha). | ||||
| 		WithFilesRemoved(hd.textPath, hd.binaryPath). | ||||
| 		WithMsg(fmt.Sprintf("Delete ‘%s’", hyphaName)). | ||||
| 		WithUser(u). | ||||
| 		Apply() | ||||
| 	if len(hop.Errs) == 0 { | ||||
| 		delete(HyphaStorage, hyphaName) | ||||
| 		hyphae.DecrementCount() | ||||
| 	} | ||||
| 	return hop | ||||
| } | ||||
|  | ||||
| // UnattachHypha unattaches hypha and makes a history record about that. | ||||
| func (hd *HyphaData) UnattachHypha(hyphaName string, u *user.User) *history.HistoryOp { | ||||
| 	hop := history.Operation(history.TypeUnattachHypha). | ||||
| 		WithFilesRemoved(hd.binaryPath). | ||||
| 		WithMsg(fmt.Sprintf("Unattach ‘%s’", hyphaName)). | ||||
| 		WithUser(u). | ||||
| 		Apply() | ||||
| 	if len(hop.Errs) == 0 { | ||||
| 		hd, ok := HyphaStorage[hyphaName] | ||||
| 		if ok { | ||||
| 			if hd.binaryPath != "" { | ||||
| 				hd.binaryPath = "" | ||||
| 			} | ||||
| 			// If nothing is left of the hypha | ||||
| 			if hd.textPath == "" { | ||||
| 				delete(HyphaStorage, hyphaName) | ||||
| 				hyphae.DecrementCount() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return hop | ||||
| } | ||||
|  | ||||
| func findHyphaeToRename(hyphaName string, recursive bool) []string { | ||||
| 	hyphae := []string{hyphaName} | ||||
| 	if recursive { | ||||
| 		hyphae = append(hyphae, util.FindSubhyphae(hyphaName, IterateHyphaNamesWith)...) | ||||
| 	} | ||||
| 	return hyphae | ||||
| } | ||||
|  | ||||
| func renamingPairs(hyphaNames []string, replaceName func(string) string) (map[string]string, error) { | ||||
| 	renameMap := make(map[string]string) | ||||
| 	for _, hn := range hyphaNames { | ||||
| 		if hd, ok := HyphaStorage[hn]; ok { | ||||
| 			if _, nameUsed := HyphaStorage[replaceName(hn)]; nameUsed { | ||||
| 				return nil, errors.New("Hypha " + replaceName(hn) + " already exists") | ||||
| 			} | ||||
| 			if hd.textPath != "" { | ||||
| 				renameMap[hd.textPath] = replaceName(hd.textPath) | ||||
| 			} | ||||
| 			if hd.binaryPath != "" { | ||||
| 				renameMap[hd.binaryPath] = replaceName(hd.binaryPath) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return renameMap, nil | ||||
| } | ||||
|  | ||||
| // word Data is plural here | ||||
| func relocateHyphaData(hyphaNames []string, replaceName func(string) string) { | ||||
| 	for _, hyphaName := range hyphaNames { | ||||
| 		if hd, ok := HyphaStorage[hyphaName]; ok { | ||||
| 			hd.textPath = replaceName(hd.textPath) | ||||
| 			hd.binaryPath = replaceName(hd.binaryPath) | ||||
| 			HyphaStorage[replaceName(hyphaName)] = hd | ||||
| 			delete(HyphaStorage, hyphaName) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way. | ||||
| func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp { | ||||
| 	var ( | ||||
| 		re          = regexp.MustCompile(`(?i)` + hyphaName) | ||||
| 		replaceName = func(str string) string { | ||||
| 			return re.ReplaceAllString(CanonicalName(str), newName) | ||||
| 		} | ||||
| 		hyphaNames     = findHyphaeToRename(hyphaName, recursive) | ||||
| 		renameMap, err = renamingPairs(hyphaNames, replaceName) | ||||
| 		renameMsg      = "Rename ‘%s’ to ‘%s’" | ||||
| 		hop            = history.Operation(history.TypeRenameHypha) | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		hop.Errs = append(hop.Errs, err) | ||||
| 		return hop | ||||
| 	} | ||||
| 	if recursive { | ||||
| 		renameMsg += " recursively" | ||||
| 	} | ||||
| 	hop.WithFilesRenamed(renameMap). | ||||
| 		WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)). | ||||
| 		WithUser(u). | ||||
| 		Apply() | ||||
| 	if len(hop.Errs) == 0 { | ||||
| 		relocateHyphaData(hyphaNames, replaceName) | ||||
| 	} | ||||
| 	return hop | ||||
| } | ||||
|  | ||||
| // binaryHtmlBlock creates an html block for binary part of the hypha. | ||||
| func binaryHtmlBlock(hyphaName string, hd *HyphaData) string { | ||||
| 	switch filepath.Ext(hd.binaryPath) { | ||||
| 	case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico": | ||||
| 		return fmt.Sprintf(` | ||||
| 		<div class="binary-container binary-container_with-img"> | ||||
| 			<a href="/binary/%[1]s"><img src="/binary/%[1]s"/></a> | ||||
| 		</div>`, hyphaName) | ||||
| 	case ".ogg", ".webm", ".mp4": | ||||
| 		return fmt.Sprintf(` | ||||
| 		<div class="binary-container binary-container_with-video"> | ||||
| 			<video> | ||||
| 				<source src="/binary/%[1]s"/> | ||||
| 				<p>Your browser does not support video. See video's <a href="/binary/%[1]s">direct url</a></p> | ||||
| 			</video> | ||||
| 		`, hyphaName) | ||||
| 	case ".mp3": | ||||
| 		return fmt.Sprintf(` | ||||
| 		<div class="binary-container binary-container_with-audio"> | ||||
| 			<audio> | ||||
| 				<source src="/binary/%[1]s"/> | ||||
| 				<p>Your browser does not support audio. See audio's <a href="/binary/%[1]s">direct url</a></p> | ||||
| 			</audio> | ||||
| 		`, hyphaName) | ||||
| 	default: | ||||
| 		return fmt.Sprintf(` | ||||
| 		<div class="binary-container binary-container_with-nothing"> | ||||
| 			<p>This hypha's media cannot be rendered. <a href="/binary/%s">Download it</a></p> | ||||
| 		</div> | ||||
| 		`, hyphaName) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive. | ||||
| func Index(path string) { | ||||
| 	nodes, err := ioutil.ReadDir(path) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	for _, node := range nodes { | ||||
| 		// If this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an admnistrative importance! | ||||
| 		if node.IsDir() && isCanonicalName(node.Name()) && node.Name() != ".git" && node.Name() != "static" { | ||||
| 			Index(filepath.Join(path, node.Name())) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var ( | ||||
| 			hyphaPartPath           = filepath.Join(path, node.Name()) | ||||
| 			hyphaName, isText, skip = DataFromFilename(hyphaPartPath) | ||||
| 			hyphaData               *HyphaData | ||||
| 		) | ||||
| 		if !skip { | ||||
| 			// Reuse the entry for existing hyphae, create a new one for those that do not exist yet. | ||||
| 			if hd, ok := HyphaStorage[hyphaName]; ok { | ||||
| 				hyphaData = hd | ||||
| 			} else { | ||||
| 				hyphaData = &HyphaData{} | ||||
| 				HyphaStorage[hyphaName] = hyphaData | ||||
| 				hyphae.IncrementCount() | ||||
| 			} | ||||
| 			if isText { | ||||
| 				hyphaData.textPath = hyphaPartPath | ||||
| 			} else { | ||||
| 				// Notify the user about binary part collisions. It's a design decision to just use any of them, it's the user's fault that they have screwed up the folder structure, but the engine should at least let them know, right? | ||||
| 				if hyphaData.binaryPath != "" { | ||||
| 					log.Println("There is a file collision for binary part of a hypha:", hyphaData.binaryPath, "and", hyphaPartPath, "-- going on with the latter") | ||||
| 				} | ||||
| 				hyphaData.binaryPath = hyphaPartPath | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FetchTextPart tries to read text file in the `d`. If there is no file, empty string is returned. | ||||
| func FetchTextPart(d *HyphaData) (string, error) { | ||||
| 	if d.textPath == "" { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	_, err := os.Stat(d.textPath) | ||||
| 	if os.IsNotExist(err) { | ||||
| 		return "", nil | ||||
| 	} else if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	text, err := ioutil.ReadFile(d.textPath) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return string(text), nil | ||||
| } | ||||
|  | ||||
| func setHeaderLinks() { | ||||
| 	if userLinksHypha, ok := GetHyphaData(util.HeaderLinksHypha); !ok { | ||||
| 		util.SetDefaultHeaderLinks() | ||||
| 	} else { | ||||
| 		contents, err := ioutil.ReadFile(userLinksHypha.textPath) | ||||
| 		if err != nil || len(contents) == 0 { | ||||
| 			util.SetDefaultHeaderLinks() | ||||
| 		} else { | ||||
| 			text := string(contents) | ||||
| 			util.ParseHeaderLinks(text, markup.Rocketlink) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -10,21 +10,21 @@ var count = struct { | ||||
| 	sync.Mutex | ||||
| }{} | ||||
|  | ||||
| // Set the value of hyphae count to zero. | ||||
| // Set the value of hyphae count to zero. Use when reloading hyphae. | ||||
| func ResetCount() { | ||||
|   count.Lock() | ||||
|   count.value = 0 | ||||
|   count.Unlock() | ||||
| 	count.Lock() | ||||
| 	count.value = 0 | ||||
| 	count.Unlock() | ||||
| } | ||||
|  | ||||
| // Increment the value of hyphae count. | ||||
| // Increment the value of the hyphae counter. Use when creating new hyphae or loading hyphae from disk. | ||||
| func IncrementCount() { | ||||
| 	count.Lock() | ||||
| 	count.value++ | ||||
| 	count.Unlock() | ||||
| } | ||||
|  | ||||
| // Decrement the value of hyphae count. | ||||
| // Decrement the value of the hyphae counter. Use when deleting existing hyphae. | ||||
| func DecrementCount() { | ||||
| 	count.Lock() | ||||
| 	count.value-- | ||||
| @@ -33,6 +33,5 @@ func DecrementCount() { | ||||
|  | ||||
| // Count how many hyphae there are. | ||||
| func Count() int { | ||||
| 	// it is concurrent-safe to not lock here, right? | ||||
| 	return count.value | ||||
| } | ||||
|   | ||||
							
								
								
									
										66
									
								
								hyphae/files.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,66 @@ | ||||
| package hyphae | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/mimetype" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| // Index finds all hypha files in the full `path` and saves them to the hypha storage. | ||||
| func Index(path string) { | ||||
| 	byNamesMutex.Lock() | ||||
| 	defer byNamesMutex.Unlock() | ||||
| 	byNames = make(map[string]*Hypha) | ||||
| 	ch := make(chan *Hypha, 5) | ||||
|  | ||||
| 	go func(ch chan *Hypha) { | ||||
| 		indexHelper(path, 0, ch) | ||||
| 		close(ch) | ||||
| 	}(ch) | ||||
|  | ||||
| 	for h := range ch { | ||||
| 		// At this time it is safe to ignore the mutex, because there is only one worker. | ||||
| 		if oldHypha, ok := byNames[h.Name]; ok { | ||||
| 			oldHypha.MergeIn(h) | ||||
| 		} else { | ||||
| 			byNames[h.Name] = h | ||||
| 			IncrementCount() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // indexHelper finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller. | ||||
| func indexHelper(path string, nestLevel uint, ch chan *Hypha) { | ||||
| 	nodes, err := ioutil.ReadDir(path) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	for _, node := range nodes { | ||||
| 		// If this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an admnistrative importance! | ||||
| 		if node.IsDir() && | ||||
| 			util.IsCanonicalName(node.Name()) && | ||||
| 			node.Name() != ".git" && | ||||
| 			!(nestLevel == 0 && node.Name() == "static") { | ||||
| 			indexHelper(filepath.Join(path, node.Name()), nestLevel+1, ch) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var ( | ||||
| 			hyphaPartPath           = filepath.Join(path, node.Name()) | ||||
| 			hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath) | ||||
| 			hypha                   = &Hypha{Name: hyphaName, Exists: true} | ||||
| 		) | ||||
| 		if !skip { | ||||
| 			if isText { | ||||
| 				hypha.TextPath = hyphaPartPath | ||||
| 			} else { | ||||
| 				hypha.BinaryPath = hyphaPartPath | ||||
| 			} | ||||
| 			ch <- hypha | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,21 +0,0 @@ | ||||
| package hyphae | ||||
|  | ||||
| // TODO: do | ||||
| import () | ||||
|  | ||||
| type Hypha struct { | ||||
| 	Name       string | ||||
| 	Exists     bool | ||||
| 	TextPath   string | ||||
| 	BinaryPath string | ||||
| 	OutLinks   []string | ||||
| 	BackLinks  []string | ||||
| } | ||||
|  | ||||
| // AddHypha adds a hypha named `name` with such `textPath` and `binaryPath`. Both paths can be empty. Does //not// check for hypha's existence beforehand. Count is handled. | ||||
| func AddHypha(name, textPath, binaryPath string) { | ||||
| } | ||||
|  | ||||
| // DeleteHypha clears both paths and all out-links from the named hypha and marks it as non-existent. It does not actually delete it from the memdb. Count is handled. | ||||
| func DeleteHypha(name string) { | ||||
| } | ||||
							
								
								
									
										175
									
								
								hyphae/hyphae.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,175 @@ | ||||
| // The `hyphae` package is for the Hypha type, hypha storage and stuff like that. It shall not depend on mycorrhiza modules other than util. | ||||
| package hyphae | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"regexp" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // HyphaPattern is a pattern which all hyphae must match. | ||||
| var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) | ||||
|  | ||||
| type Hypha struct { | ||||
| 	sync.RWMutex | ||||
|  | ||||
| 	Name       string | ||||
| 	Exists     bool | ||||
| 	TextPath   string | ||||
| 	BinaryPath string | ||||
| 	OutLinks   []*Hypha | ||||
| 	BackLinks  []*Hypha | ||||
| } | ||||
|  | ||||
| var byNames = make(map[string]*Hypha) | ||||
| var byNamesMutex = sync.Mutex{} | ||||
|  | ||||
| // EmptyHypha returns an empty hypha struct with given name. | ||||
| func EmptyHypha(hyphaName string) *Hypha { | ||||
| 	return &Hypha{ | ||||
| 		Name:       hyphaName, | ||||
| 		Exists:     false, | ||||
| 		TextPath:   "", | ||||
| 		BinaryPath: "", | ||||
| 		OutLinks:   make([]*Hypha, 0), | ||||
| 		BackLinks:  make([]*Hypha, 0), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ByName returns a hypha by name. If h.Exists, the returned hypha pointer is known to be part of the hypha index (byNames map). | ||||
| func ByName(hyphaName string) (h *Hypha) { | ||||
| 	h, exists := byNames[hyphaName] | ||||
| 	if exists { | ||||
| 		return h | ||||
| 	} | ||||
| 	return EmptyHypha(hyphaName) | ||||
| } | ||||
|  | ||||
| // Insert inserts the hypha into the storage. It overwrites the previous record, if there was any, and returns false. If the was no previous record, return true. | ||||
| func (h *Hypha) Insert() (justCreated bool) { | ||||
| 	hp := ByName(h.Name) | ||||
|  | ||||
| 	byNamesMutex.Lock() | ||||
| 	defer byNamesMutex.Unlock() | ||||
| 	if hp.Exists { | ||||
| 		hp = h | ||||
| 	} else { | ||||
| 		h.Exists = true | ||||
| 		byNames[h.Name] = h | ||||
| 		IncrementCount() | ||||
| 	} | ||||
|  | ||||
| 	return !hp.Exists | ||||
| } | ||||
|  | ||||
| func (h *Hypha) InsertIfNew() (justCreated bool) { | ||||
| 	if !h.Exists { | ||||
| 		return h.Insert() | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (h *Hypha) InsertIfNewKeepExistence() { | ||||
| 	hp := ByName(h.Name) | ||||
|  | ||||
| 	byNamesMutex.Lock() | ||||
| 	defer byNamesMutex.Unlock() | ||||
| 	if hp.Exists { | ||||
| 		hp = h | ||||
| 	} else { | ||||
| 		byNames[h.Name] = h | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (h *Hypha) Delete() { | ||||
| 	byNamesMutex.Lock() | ||||
| 	h.Lock() | ||||
| 	delete(byNames, h.Name) | ||||
| 	DecrementCount() | ||||
| 	byNamesMutex.Unlock() | ||||
| 	h.Unlock() | ||||
|  | ||||
| 	for _, outlinkHypha := range h.OutLinks { | ||||
| 		outlinkHypha.DropBackLink(h) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (h *Hypha) RenameTo(newName string) { | ||||
| 	byNamesMutex.Lock() | ||||
| 	h.Lock() | ||||
| 	delete(byNames, h.Name) | ||||
| 	h.Name = newName | ||||
| 	byNames[h.Name] = h | ||||
| 	byNamesMutex.Unlock() | ||||
| 	h.Unlock() | ||||
| } | ||||
|  | ||||
| // MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes. | ||||
| func (h *Hypha) MergeIn(oh *Hypha) { | ||||
| 	if h.TextPath == "" && oh.TextPath != "" { | ||||
| 		h.TextPath = oh.TextPath | ||||
| 	} | ||||
| 	if oh.BinaryPath != "" { | ||||
| 		if h.BinaryPath != "" { | ||||
| 			log.Println("There is a file collision for binary part of a hypha:", h.BinaryPath, "and", oh.BinaryPath, "-- going on with the latter") | ||||
| 		} | ||||
| 		h.BinaryPath = oh.BinaryPath | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ## Link related stuff | ||||
| // Notes in pseudocode and whatnot: | ||||
| // * (Reader h) does not mutate h => safe | ||||
| // * (Rename h) reuses the same hypha object => safe | ||||
| // * (Unattach h) and (Attach h) do not change (Backlinks h) => safe | ||||
|  | ||||
| // * (Delete h) does not change (Backlinks h), but changes (Outlinks h), removing h from them => make it safe | ||||
| // * (Unattach h) and (Attach h) => h may start or stop existing => may change (Outlinks h) => make it safe | ||||
| // * (Edit h) => h may start existing => may change (Backlinks h) => make it safe | ||||
| // * (Edit h) may add or remove h to or from (Outlinks h) => make it safe | ||||
|  | ||||
| func (h *Hypha) AddOutLink(oh *Hypha) (added bool) { | ||||
| 	h.Lock() | ||||
| 	defer h.Unlock() | ||||
|  | ||||
| 	for _, outlink := range h.OutLinks { | ||||
| 		if outlink == oh { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	h.OutLinks = append(h.OutLinks, oh) | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (h *Hypha) AddBackLink(bh *Hypha) (added bool) { | ||||
| 	h.Lock() | ||||
| 	defer h.Unlock() | ||||
|  | ||||
| 	for _, backlink := range h.BackLinks { | ||||
| 		if backlink == h { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	h.BackLinks = append(h.BackLinks, bh) | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (h *Hypha) DropBackLink(bh *Hypha) { | ||||
| 	h.Lock() | ||||
| 	defer h.Unlock() | ||||
|  | ||||
| 	if len(h.BackLinks) <= 1 { | ||||
| 		h.BackLinks = make([]*Hypha, 0) | ||||
| 		return | ||||
| 	} | ||||
| 	lastBackLinkIndex := len(h.BackLinks) | ||||
| 	for i, backlink := range h.BackLinks { | ||||
| 		if backlink == bh { | ||||
| 			if i != lastBackLinkIndex { | ||||
| 				h.BackLinks[i] = h.BackLinks[lastBackLinkIndex] | ||||
| 			} | ||||
| 			h.BackLinks = h.BackLinks[:lastBackLinkIndex] | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										57
									
								
								hyphae/iterators.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,57 @@ | ||||
| // File `iterators.go` contains stuff that iterates over hyphae. | ||||
| package hyphae | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // YieldExistingHyphae iterates over all hyphae and yields all existing ones. | ||||
| func YieldExistingHyphae() chan *Hypha { | ||||
| 	ch := make(chan *Hypha) | ||||
| 	go func() { | ||||
| 		for _, h := range byNames { | ||||
| 			if h.Exists { | ||||
| 				ch <- h | ||||
| 			} | ||||
| 		} | ||||
| 		close(ch) | ||||
| 	}() | ||||
| 	return ch | ||||
| } | ||||
|  | ||||
| // FilterTextHyphae filters the source channel and yields only those hyphae than have text parts. | ||||
| func FilterTextHyphae(src chan *Hypha) chan *Hypha { | ||||
| 	sink := make(chan *Hypha) | ||||
| 	go func() { | ||||
| 		for h := range src { | ||||
| 			if h.TextPath != "" { | ||||
| 				sink <- h | ||||
| 			} | ||||
| 		} | ||||
| 		close(sink) | ||||
| 	}() | ||||
| 	return sink | ||||
| } | ||||
|  | ||||
| // Subhyphae returns slice of subhyphae. | ||||
| func (h *Hypha) Subhyphae() []*Hypha { | ||||
| 	hyphae := []*Hypha{} | ||||
| 	for subh := range YieldExistingHyphae() { | ||||
| 		if strings.HasPrefix(subh.Name, h.Name+"/") { | ||||
| 			hyphae = append(hyphae, subh) | ||||
| 		} | ||||
| 	} | ||||
| 	return hyphae | ||||
| } | ||||
|  | ||||
| // AreFreeNames checks if all given `hyphaNames` are not taken. If they are not taken, `ok` is true. If not, `firstFailure` is the name of the first met hypha that is not free. | ||||
| func AreFreeNames(hyphaNames ...string) (firstFailure string, ok bool) { | ||||
| 	for h := range YieldExistingHyphae() { | ||||
| 		for _, hn := range hyphaNames { | ||||
| 			if hn == h.Name { | ||||
| 				return hn, false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return "", true | ||||
| } | ||||
							
								
								
									
										132
									
								
								link/link.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,132 @@ | ||||
| package link | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| // LinkType tells what type the given link is. | ||||
| type LinkType int | ||||
|  | ||||
| const ( | ||||
| 	LinkInavild LinkType = iota | ||||
| 	// LinkLocalRoot is a link like "/list", "/user-list", etc. | ||||
| 	LinkLocalRoot | ||||
| 	// LinkLocalHypha is a link like "test", "../test", etc. | ||||
| 	LinkLocalHypha | ||||
| 	// LinkExternal is an external link with specified protocol. | ||||
| 	LinkExternal | ||||
| 	// LinkInterwiki is currently unused. | ||||
| 	LinkInterwiki | ||||
| ) | ||||
|  | ||||
| // Link is an abstraction for universal representation of links, be they links in mycomarkup links or whatever. | ||||
| type Link struct { | ||||
| 	// Address is what the link points to. | ||||
| 	Address string | ||||
| 	// Display is what gets nested into the <a> tag. | ||||
| 	Display            string | ||||
| 	Kind               LinkType | ||||
| 	DestinationUnknown bool | ||||
|  | ||||
| 	// #... | ||||
| 	Anchor   string | ||||
| 	Protocol string | ||||
| 	// How the link address looked originally in source text. | ||||
| 	SrcAddress string | ||||
| 	// How the link display text looked originally in source text. May be empty. | ||||
| 	SrcDisplay string | ||||
| 	// RelativeTo is hypha name to which the link is relative to. | ||||
| 	RelativeTo string | ||||
| } | ||||
|  | ||||
| // DoubtExistence sets DestinationUnknown to true if the link is local hypha link. | ||||
| func (l *Link) DoubtExistence() { | ||||
| 	if l.Kind == LinkLocalHypha { | ||||
| 		l.DestinationUnknown = true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Classes returns CSS class string for given link. | ||||
| func (l *Link) Classes() string { | ||||
| 	if l.Kind == LinkExternal { | ||||
| 		return fmt.Sprintf("wikilink wikilink_external wikilink_%s", l.Protocol) | ||||
| 	} | ||||
| 	classes := "wikilink wikilink_internal" | ||||
| 	if l.DestinationUnknown { | ||||
| 		classes += " wikilink_new" | ||||
| 	} | ||||
| 	return classes | ||||
| } | ||||
|  | ||||
| // Href returns content for the href attrubite for hyperlink. You should always use it. | ||||
| func (l *Link) Href() string { | ||||
| 	switch l.Kind { | ||||
| 	case LinkExternal, LinkLocalRoot: | ||||
| 		return l.Address | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
| 	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.Address = 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:])) | ||||
| 	case strings.HasPrefix(address, "#"): | ||||
| 		link.Kind = LinkLocalHypha | ||||
| 		link.Address = util.CanonicalName(hyphaName) | ||||
| 		link.Anchor = address | ||||
| 	default: | ||||
| 		link.Kind = LinkLocalHypha | ||||
| 		link.Address = util.CanonicalName(address) | ||||
| 	} | ||||
|  | ||||
| 	return &link | ||||
| } | ||||
							
								
								
									
										94
									
								
								main.go
									
									
									
									
									
								
							
							
						
						| @@ -1,5 +1,6 @@ | ||||
| //go:generate go get -u github.com/valyala/quicktemplate/qtc | ||||
| //go:generate qtc -dir=templates | ||||
| //go:generate qtc -dir=assets | ||||
| //go:generate qtc -dir=views | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| @@ -9,33 +10,20 @@ import ( | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/assets" | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/templates" | ||||
| 	"github.com/bouncepaw/mycorrhiza/shroom" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/bouncepaw/mycorrhiza/views" | ||||
| ) | ||||
|  | ||||
| // WikiDir is a rooted path to the wiki storage directory. | ||||
| var WikiDir string | ||||
|  | ||||
| // HyphaPattern is a pattern which all hyphae must match. | ||||
| var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) | ||||
|  | ||||
| // HyphaStorage is a mapping between canonical hypha names and their meta information. | ||||
| var HyphaStorage = make(map[string]*HyphaData) | ||||
|  | ||||
| // IterateHyphaNamesWith is a closure to be passed to subpackages to let them iterate all hypha names read-only. | ||||
| func IterateHyphaNamesWith(f func(string)) { | ||||
| 	for hyphaName := range HyphaStorage { | ||||
| 		f(hyphaName) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| @@ -46,7 +34,7 @@ func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) { | ||||
| 		base( | ||||
| 			title, | ||||
| 			fmt.Sprintf( | ||||
| 				`<main><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`, | ||||
| 				`<main class="main-width"><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`, | ||||
| 				errMsg, | ||||
| 				name, | ||||
| 			), | ||||
| @@ -58,19 +46,11 @@ func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) { | ||||
| // Show all hyphae | ||||
| func handlerList(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var ( | ||||
| 		tbody     string | ||||
| 		pageCount = hyphae.Count() | ||||
| 		u         = user.FromRequest(rq) | ||||
| 	) | ||||
| 	for hyphaName, data := range HyphaStorage { | ||||
| 		tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "") | ||||
| 	} | ||||
| 	util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u)) | ||||
| 	util.HTTP200Page(w, base("List of pages", views.HyphaListHTML(), user.FromRequest(rq))) | ||||
| } | ||||
|  | ||||
| // This part is present in all html documents. | ||||
| var base = templates.BaseHTML | ||||
| var base = views.BaseHTML | ||||
|  | ||||
| // Reindex all hyphae by checking the wiki storage directory anew. | ||||
| func handlerReindex(w http.ResponseWriter, rq *http.Request) { | ||||
| @@ -81,14 +61,15 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) { | ||||
| 		return | ||||
| 	} | ||||
| 	hyphae.ResetCount() | ||||
| 	HyphaStorage = make(map[string]*HyphaData) | ||||
| 	log.Println("Wiki storage directory is", WikiDir) | ||||
| 	log.Println("Start indexing hyphae...") | ||||
| 	Index(WikiDir) | ||||
| 	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) | ||||
| @@ -97,7 +78,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { | ||||
| 		log.Println("Rejected", rq.URL) | ||||
| 		return | ||||
| 	} | ||||
| 	setHeaderLinks() | ||||
| 	shroom.SetHeaderLinks() | ||||
| 	http.Redirect(w, rq, "/", http.StatusSeeOther) | ||||
| } | ||||
|  | ||||
| @@ -106,23 +87,25 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	var randomHyphaName string | ||||
| 	i := rand.Intn(hyphae.Count()) | ||||
| 	for hyphaName := range HyphaStorage { | ||||
| 	for h := range hyphae.YieldExistingHyphae() { | ||||
| 		if i == 0 { | ||||
| 			randomHyphaName = hyphaName | ||||
| 			break | ||||
| 			randomHyphaName = h.Name | ||||
| 		} | ||||
| 		i-- | ||||
| 	} | ||||
| 	http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther) | ||||
| 	http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther) | ||||
| } | ||||
|  | ||||
| func handlerStyle(w http.ResponseWriter, rq *http.Request) { | ||||
| 	log.Println(rq.URL) | ||||
| 	if _, err := os.Stat(WikiDir + "/static/common.css"); err == nil { | ||||
| 		http.ServeFile(w, rq, WikiDir+"/static/common.css") | ||||
| 	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(templates.DefaultCSS())) | ||||
| 		w.Write([]byte(assets.DefaultCSS())) | ||||
| 	} | ||||
| 	if bytes, err := ioutil.ReadFile(util.WikiDir + "/static/custom.css"); err == nil { | ||||
| 		w.Write(bytes) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -143,20 +126,26 @@ func handlerIcon(w http.ResponseWriter, rq *http.Request) { | ||||
| 	w.Header().Set("Content-Type", "image/svg+xml") | ||||
| 	switch iconName { | ||||
| 	case "gemini": | ||||
| 		w.Write([]byte(templates.IconGemini())) | ||||
| 		w.Write([]byte(assets.IconGemini())) | ||||
| 	case "mailto": | ||||
| 		w.Write([]byte(templates.IconMailto())) | ||||
| 		w.Write([]byte(assets.IconMailto())) | ||||
| 	case "gopher": | ||||
| 		w.Write([]byte(templates.IconGopher())) | ||||
| 		w.Write([]byte(assets.IconGopher())) | ||||
| 	default: | ||||
| 		w.Write([]byte(templates.IconHTTP())) | ||||
| 		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, templates.AboutHTML(), user.FromRequest(rq)))) | ||||
| 	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) { | ||||
| @@ -177,13 +166,19 @@ func main() { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	log.Println("Wiki storage directory is", WikiDir) | ||||
| 	Index(WikiDir) | ||||
| 	hyphae.Index(WikiDir) | ||||
| 	log.Println("Indexed", hyphae.Count(), "hyphae") | ||||
| 	shroom.FindAllBacklinks() | ||||
| 	log.Println("Found all backlinks") | ||||
|  | ||||
| 	history.Start(WikiDir) | ||||
| 	setHeaderLinks() | ||||
| 	shroom.SetHeaderLinks() | ||||
|  | ||||
| 	// See http_readers.go for /page/, /text/, /binary/ | ||||
| 	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 | ||||
| @@ -192,15 +187,16 @@ func main() { | ||||
| 	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/icon/", handlerIcon) | ||||
| 	http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { | ||||
| 		http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther) | ||||
| 	}) | ||||
| 	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)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										34
									
								
								markup/hr.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,34 @@ | ||||
| package markup | ||||
|  | ||||
| import ( | ||||
| 	"unicode" | ||||
| ) | ||||
|  | ||||
| // MatchesHorizontalLine checks if the string can be interpreted as suitable for rendering as <hr/>. | ||||
| // | ||||
| // The rule is: if there are more than 4 characters "-" in the string, then make it a horizontal line. | ||||
| // Otherwise it is a paragraph (<p>). | ||||
| func MatchesHorizontalLine(line string) bool { | ||||
| 	counter := 0 | ||||
|  | ||||
| 	// Check initially that the symbol is "-". If it is not a "-", it is most likely a space or another character. | ||||
| 	// With unicode.IsLetter() we can separate spaces and characters. | ||||
| 	for _, ch := range line { | ||||
| 		if ch == '-' { | ||||
| 			counter++ | ||||
| 			continue | ||||
| 		} | ||||
| 		// If we bump into any other character (letter) in the line, it is immediately an incorrect horizontal line. | ||||
| 		// There is no point in counting further, we end the loop. | ||||
| 		if unicode.IsLetter(ch) { | ||||
| 			counter = 0 | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if counter >= 4 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
| @@ -5,7 +5,7 @@ import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/bouncepaw/mycorrhiza/link" | ||||
| ) | ||||
|  | ||||
| var imgRe = regexp.MustCompile(`^img\s+{`) | ||||
| @@ -14,44 +14,6 @@ func MatchesImg(line string) bool { | ||||
| 	return imgRe.MatchString(line) | ||||
| } | ||||
|  | ||||
| type imgEntry struct { | ||||
| 	trimmedPath string | ||||
| 	path        strings.Builder | ||||
| 	sizeW       strings.Builder | ||||
| 	sizeH       strings.Builder | ||||
| 	desc        strings.Builder | ||||
| } | ||||
|  | ||||
| func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) { | ||||
| 	if entry.desc.Len() == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	lines := strings.Split(entry.desc.String(), "\n") | ||||
| 	for _, line := range lines { | ||||
| 		if line = strings.TrimSpace(line); line != "" { | ||||
| 			if html != "" { | ||||
| 				html += `<br>` | ||||
| 			} | ||||
| 			html += ParagraphToHtml(hyphaName, line) | ||||
| 		} | ||||
| 	} | ||||
| 	return `<figcaption>` + html + `</figcaption>` | ||||
| } | ||||
|  | ||||
| func (entry *imgEntry) sizeWAsAttr() string { | ||||
| 	if entry.sizeW.Len() == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return ` width="` + entry.sizeW.String() + `"` | ||||
| } | ||||
|  | ||||
| func (entry *imgEntry) sizeHAsAttr() string { | ||||
| 	if entry.sizeH.Len() == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return ` height="` + entry.sizeH.String() + `"` | ||||
| } | ||||
|  | ||||
| type imgState int | ||||
|  | ||||
| const ( | ||||
| @@ -71,6 +33,8 @@ type Img struct { | ||||
|  | ||||
| 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() | ||||
| @@ -177,23 +141,6 @@ func ImgFromFirstLine(line, hyphaName string) (img *Img, shouldGoBackToNormal bo | ||||
| 	return img, img.Process(line) | ||||
| } | ||||
|  | ||||
| func (img *Img) binaryPathFor(path string) string { | ||||
| 	path = strings.TrimSpace(path) | ||||
| 	if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 { | ||||
| 		return path | ||||
| 	} else { | ||||
| 		return "/binary/" + xclCanonicalName(img.hyphaName, path) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (img *Img) ogBinaryPathFor(path string) string { | ||||
| 	path = img.binaryPathFor(path) | ||||
| 	if strings.HasPrefix(path, "/binary/") { | ||||
| 		return util.URL + path | ||||
| 	} | ||||
| 	return path | ||||
| } | ||||
|  | ||||
| func (img *Img) pagePathFor(path string) string { | ||||
| 	path = strings.TrimSpace(path) | ||||
| 	if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 { | ||||
| @@ -214,30 +161,18 @@ func parseDimensions(dimensions string) (sizeW, sizeH string) { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (img *Img) checkLinks() map[string]bool { | ||||
| 	m := make(map[string]bool) | ||||
| 	for i, entry := range img.entries { | ||||
| 		// Also trim them for later use | ||||
| 		entry.trimmedPath = strings.TrimSpace(entry.path.String()) | ||||
| 		isAbsoluteUrl := strings.ContainsRune(entry.trimmedPath, ':') | ||||
| 		if !isAbsoluteUrl { | ||||
| 			entry.trimmedPath = canonicalName(entry.trimmedPath) | ||||
| 		} | ||||
| 		img.entries[i] = entry | ||||
| 		m[entry.trimmedPath] = isAbsoluteUrl | ||||
| 	} | ||||
| 	HyphaIterate(func(hyphaName string) { | ||||
| func (img *Img) markExistenceOfSrcLinks() { | ||||
| 	HyphaIterate(func(hn string) { | ||||
| 		for _, entry := range img.entries { | ||||
| 			if hyphaName == xclCanonicalName(img.hyphaName, entry.trimmedPath) { | ||||
| 				m[entry.trimmedPath] = true | ||||
| 			if hn == entry.srclink.Address { | ||||
| 				entry.srclink.DestinationUnknown = false | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| func (img *Img) ToHtml() (html string) { | ||||
| 	linkAvailabilityMap := img.checkLinks() | ||||
| 	img.markExistenceOfSrcLinks() | ||||
| 	isOneImageOnly := len(img.entries) == 1 && img.entries[0].desc.Len() == 0 | ||||
| 	if isOneImageOnly { | ||||
| 		html += `<section class="img-gallery img-gallery_one-image">` | ||||
| @@ -247,15 +182,19 @@ func (img *Img) ToHtml() (html string) { | ||||
|  | ||||
| 	for _, entry := range img.entries { | ||||
| 		html += `<figure>` | ||||
| 		// If is existing hypha or an external path | ||||
| 		if linkAvailabilityMap[entry.trimmedPath] { | ||||
| 		if entry.srclink.DestinationUnknown { | ||||
| 			html += fmt.Sprintf( | ||||
| 				`<a class="%s" href="%s">Hypha <i>%s</i> does not exist</a>`, | ||||
| 				entry.srclink.Classes(), | ||||
| 				entry.srclink.Href(), | ||||
| 				entry.srclink.Address) | ||||
| 		} else { | ||||
| 			html += fmt.Sprintf( | ||||
| 				`<a href="%s"><img src="%s" %s %s></a>`, | ||||
| 				img.pagePathFor(entry.trimmedPath), | ||||
| 				img.binaryPathFor(entry.trimmedPath), | ||||
| 				entry.sizeWAsAttr(), entry.sizeHAsAttr()) | ||||
| 		} else { // If is a non-existent hypha | ||||
| 			html += fmt.Sprintf(`<a class="wikilink_new" href="%s">Hypha <em>%s</em> does not exist</a>`, img.pagePathFor(entry.trimmedPath), entry.trimmedPath) | ||||
| 				entry.srclink.Href(), | ||||
| 				entry.srclink.ImgSrc(), | ||||
| 				entry.sizeWAsAttr(), | ||||
| 				entry.sizeHAsAttr()) | ||||
| 		} | ||||
| 		html += entry.descriptionAsHtml(img.hyphaName) | ||||
| 		html += `</figure>` | ||||
|   | ||||
							
								
								
									
										45
									
								
								markup/img_entry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| package markup | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/link" | ||||
| ) | ||||
|  | ||||
| type imgEntry struct { | ||||
| 	srclink *link.Link | ||||
| 	path    strings.Builder | ||||
| 	sizeW   strings.Builder | ||||
| 	sizeH   strings.Builder | ||||
| 	desc    strings.Builder | ||||
| } | ||||
|  | ||||
| func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) { | ||||
| 	if entry.desc.Len() == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	lines := strings.Split(entry.desc.String(), "\n") | ||||
| 	for _, line := range lines { | ||||
| 		if line = strings.TrimSpace(line); line != "" { | ||||
| 			if html != "" { | ||||
| 				html += `<br>` | ||||
| 			} | ||||
| 			html += ParagraphToHtml(hyphaName, line) | ||||
| 		} | ||||
| 	} | ||||
| 	return `<figcaption>` + html + `</figcaption>` | ||||
| } | ||||
|  | ||||
| func (entry *imgEntry) sizeWAsAttr() string { | ||||
| 	if entry.sizeW.Len() == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return ` width="` + entry.sizeW.String() + `"` | ||||
| } | ||||
|  | ||||
| func (entry *imgEntry) sizeHAsAttr() string { | ||||
| 	if entry.sizeH.Len() == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return ` height="` + entry.sizeH.String() + `"` | ||||
| } | ||||
| @@ -4,6 +4,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"html" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| // HyphaExists holds function that checks that a hypha is present. | ||||
| @@ -83,7 +85,8 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) { | ||||
| 		return strings.HasPrefix(line, token) | ||||
| 	} | ||||
| 	addHeading := func(i int) { | ||||
| 		addLine(fmt.Sprintf("<h%d id='%d'>%s</h%d>", i, state.id, ParagraphToHtml(state.name, line[i+1:]), i)) | ||||
| 		id := util.LettersNumbersOnly(line[i+1:]) | ||||
| 		addLine(fmt.Sprintf(`<h%d id='%d'>%s<a href="#%s" id="%s" class="heading__link"></a></h%d>`, i, state.id, ParagraphToHtml(state.name, line[i+1:]), id, id, i)) | ||||
| 	} | ||||
|  | ||||
| 	// Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code. | ||||
| @@ -166,7 +169,7 @@ launchpadState: | ||||
| 	switch { | ||||
| 	case startsWith("=>"): | ||||
| 		href, text, class := Rocketlink(line, state.name) | ||||
| 		state.buf += fmt.Sprintf(`	<li class="launchpad__entry"><a class="rocketlink %s" href="%s">%s</a></li>`, class, href, text) | ||||
| 		state.buf += fmt.Sprintf(`	<li class="launchpad__entry"><a href="%s" class="rocketlink %s">%s</a></li>`, href, class, text) | ||||
| 	case startsWith("```"): | ||||
| 		state.where = "pre" | ||||
| 		addLine(state.buf + "</ul>") | ||||
| @@ -235,7 +238,7 @@ normalState: | ||||
| 	case startsWith("<="): | ||||
| 		addParagraphIfNeeded() | ||||
| 		addLine(parseTransclusion(line, state.name)) | ||||
| 	case line == "----": | ||||
| 	case MatchesHorizontalLine(line): | ||||
| 		addParagraphIfNeeded() | ||||
| 		*ast = append(*ast, Line{id: -1, contents: "<hr/>"}) | ||||
| 	case MatchesImg(line): | ||||
|   | ||||
| @@ -1,47 +1,22 @@ | ||||
| package markup | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/link" | ||||
| ) | ||||
|  | ||||
| // LinkParts determines what href, text and class should resulting <a> have based on mycomarkup's addr, display and hypha name. | ||||
| // | ||||
| // => addr display | ||||
| // [[addr|display]] | ||||
| // TODO: deprecate | ||||
| func LinkParts(addr, display, hyphaName string) (href, text, class string) { | ||||
| 	if display == "" { | ||||
| 		text = addr | ||||
| 	} else { | ||||
| 		text = strings.TrimSpace(display) | ||||
| 	l := link.From(addr, display, hyphaName) | ||||
| 	if l.Kind == link.LinkLocalHypha && !HyphaExists(l.Address) { | ||||
| 		l.DestinationUnknown = true | ||||
| 	} | ||||
| 	class = "wikilink wikilink_internal" | ||||
|  | ||||
| 	switch { | ||||
| 	case strings.ContainsRune(addr, ':'): | ||||
| 		pos := strings.IndexRune(addr, ':') | ||||
| 		destination := addr[:pos] | ||||
| 		if display == "" { | ||||
| 			text = addr[pos+1:] | ||||
| 			if strings.HasPrefix(text, "//") && len(text) > 2 { | ||||
| 				text = text[2:] | ||||
| 			} | ||||
| 		} | ||||
| 		return addr, text, fmt.Sprintf("wikilink wikilink_external wikilink_%s", destination) | ||||
| 	case strings.HasPrefix(addr, "/"): | ||||
| 		return addr, text, class | ||||
| 	case strings.HasPrefix(addr, "./"): | ||||
| 		hyphaName = canonicalName(path.Join(hyphaName, addr[2:])) | ||||
| 	case strings.HasPrefix(addr, "../"): | ||||
| 		hyphaName = canonicalName(path.Join(path.Dir(hyphaName), addr[3:])) | ||||
| 	default: | ||||
| 		hyphaName = canonicalName(addr) | ||||
| 	} | ||||
| 	if !HyphaExists(hyphaName) { | ||||
| 		class += " wikilink_new" | ||||
| 	} | ||||
| 	return "/page/" + hyphaName, text, class | ||||
| 	return l.Href(), l.Display, l.Classes() | ||||
| } | ||||
|  | ||||
| // Parse markup line starting with "=>" according to wikilink rules. | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/link" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| @@ -47,6 +48,11 @@ func (md *MycoDoc) AsHTML() string { | ||||
| 	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(`<.*?>`) | ||||
|  | ||||
| @@ -57,15 +63,16 @@ func (md *MycoDoc) OpenGraphHTML() string { | ||||
| 		ogTag("title", md.hyphaName), | ||||
| 		ogTag("type", "article"), | ||||
| 		ogTag("image", md.firstImageURL), | ||||
| 		ogTag("url", util.URL+"/page/"+md.hyphaName), | ||||
| 		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 | ||||
| 	md.firstImageURL = HyphaImageForOG(md.hyphaName) | ||||
| 	foundImg := false | ||||
| 	for _, line := range md.ast { | ||||
| 		switch v := line.contents.(type) { | ||||
| 		case string: | ||||
| @@ -74,8 +81,12 @@ func (md *MycoDoc) ogFillVars() *MycoDoc { | ||||
| 				foundDesc = true | ||||
| 			} | ||||
| 		case Img: | ||||
| 			if len(v.entries) > 0 { | ||||
| 				md.firstImageURL = v.entries[0].path.String() | ||||
| 			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 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -153,6 +164,7 @@ func crawl(name, content string) []string { | ||||
| 				preAcc += html.EscapeString(line) | ||||
| 			} | ||||
| 		} | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	return []string{} | ||||
|   | ||||
							
								
								
									
										56
									
								
								markup/outlink.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,56 @@ | ||||
| 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 | ||||
| func (md *MycoDoc) OutLinks() chan string { | ||||
| 	ch := make(chan string) | ||||
| 	if !md.parsedAlready { | ||||
| 		md.Lex(0) | ||||
| 	} | ||||
| 	go func() { | ||||
| 		for _, line := range md.ast { | ||||
| 			switch v := line.contents.(type) { | ||||
| 			case string: | ||||
| 				if strings.HasPrefix(v, "<p") || strings.HasPrefix(v, "<ul class='launchpad'") { | ||||
| 					extractLinks(v, ch) | ||||
| 				} | ||||
| 			case Transclusion: | ||||
| 				ch <- v.name | ||||
| 			case Img: | ||||
| 				extractImageLinks(v, ch) | ||||
| 			} | ||||
| 		} | ||||
| 		close(ch) | ||||
| 	}() | ||||
| 	return ch | ||||
| } | ||||
|  | ||||
| var reLinks = regexp.MustCompile(`<a href="/hypha/([^"]*)".*?</a>`) | ||||
|  | ||||
| func extractLinks(html string, ch chan string) { | ||||
| 	if results := reLinks.FindAllStringSubmatch(html, -1); results != nil { | ||||
| 		for _, result := range results { | ||||
| 			// result[0] is always present at this point and is not needed, because it is the whole matched substring (which we don't need) | ||||
| 			ch <- result[1] | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func extractImageLinks(img Img, ch chan string) { | ||||
| 	for _, entry := range img.entries { | ||||
| 		if entry.srclink.Kind == link.LinkLocalHypha { | ||||
| 			ch <- entry.srclink.Address | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -55,7 +55,8 @@ func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) st | ||||
| 		} else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) { | ||||
| 			input.Next(1) | ||||
| 			break | ||||
| 		} else if !isBracketedLink && unicode.IsSpace(rune(b)) { | ||||
| 		} else if !isBracketedLink && (unicode.IsSpace(rune(b)) || strings.ContainsRune("<>{}|\\^[]`,()", rune(b))) { | ||||
| 			input.UnreadByte() | ||||
| 			break | ||||
| 		} else { | ||||
| 			currBuf.WriteByte(b) | ||||
|   | ||||
							
								
								
									
										62
									
								
								mime.go
									
									
									
									
									
								
							
							
						
						| @@ -1,62 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func MimeToExtension(mime string) string { | ||||
| 	mm := map[string]string{ | ||||
| 		"application/octet-stream": "bin", | ||||
| 		"image/jpeg":               "jpg", | ||||
| 		"image/gif":                "gif", | ||||
| 		"image/png":                "png", | ||||
| 		"image/webp":               "webp", | ||||
| 		"image/svg+xml":            "svg", | ||||
| 		"image/x-icon":             "ico", | ||||
| 		"application/ogg":          "ogg", | ||||
| 		"video/webm":               "webm", | ||||
| 		"audio/mp3":                "mp3", | ||||
| 		"video/mp4":                "mp4", | ||||
| 	} | ||||
| 	if ext, ok := mm[mime]; ok { | ||||
| 		return "." + ext | ||||
| 	} | ||||
| 	return ".bin" | ||||
| } | ||||
|  | ||||
| func ExtensionToMime(ext string) string { | ||||
| 	mm := map[string]string{ | ||||
| 		".bin":  "application/octet-stream", | ||||
| 		".jpg":  "image/jpeg", | ||||
| 		".jpeg": "image/jpeg", | ||||
| 		".gif":  "image/gif", | ||||
| 		".png":  "image/png", | ||||
| 		".webp": "image/webp", | ||||
| 		".svg":  "image/svg+xml", | ||||
| 		".ico":  "image/x-icon", | ||||
| 		".ogg":  "application/ogg", | ||||
| 		".webm": "video/webm", | ||||
| 		".mp3":  "audio/mp3", | ||||
| 		".mp4":  "video/mp4", | ||||
| 	} | ||||
| 	if mime, ok := mm[ext]; ok { | ||||
| 		return mime | ||||
| 	} | ||||
| 	return "application/octet-stream" | ||||
| } | ||||
|  | ||||
| // DataFromFilename fetches all meta information from hypha content file with path `fullPath`. If it is not a content file, `skip` is true, and you are expected to ignore this file when indexing hyphae. `name` is name of the hypha to which this file relates. `isText` is true when the content file is text, false when is binary. `mimeId` is an integer representation of content type. Cast it to TextType if `isText == true`, cast it to BinaryType if `isText == false`. | ||||
| func DataFromFilename(fullPath string) (name string, isText bool, skip bool) { | ||||
| 	shortPath := strings.TrimPrefix(fullPath, WikiDir)[1:] | ||||
| 	ext := filepath.Ext(shortPath) | ||||
| 	name = CanonicalName(strings.TrimSuffix(shortPath, ext)) | ||||
| 	switch ext { | ||||
| 	case ".myco": | ||||
| 		isText = true | ||||
| 	case "", shortPath: | ||||
| 		skip = true | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										68
									
								
								mimetype/mime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| package mimetype | ||||
|  | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| // ToExtension returns dotted extension for given mime-type. | ||||
| func ToExtension(mime string) string { | ||||
| 	if ext, ok := mapMime2Ext[mime]; ok { | ||||
| 		return "." + ext | ||||
| 	} | ||||
| 	return ".bin" | ||||
| } | ||||
|  | ||||
| // FromExtension returns mime-type for given extension. The extension must start with a dot. | ||||
| func FromExtension(ext string) string { | ||||
| 	if mime, ok := mapExt2Mime[ext]; ok { | ||||
| 		return mime | ||||
| 	} | ||||
| 	return "application/octet-stream" | ||||
| } | ||||
|  | ||||
| // DataFromFilename fetches all meta information from hypha content file with path `fullPath`. If it is not a content file, `skip` is true, and you are expected to ignore this file when indexing hyphae. `name` is name of the hypha to which this file relates. `isText` is true when the content file is text, false when is binary. | ||||
| func DataFromFilename(fullPath string) (name string, isText bool, skip bool) { | ||||
| 	shortPath := util.ShorterPath(fullPath) | ||||
| 	ext := filepath.Ext(shortPath) | ||||
| 	name = util.CanonicalName(strings.TrimSuffix(shortPath, ext)) | ||||
| 	switch ext { | ||||
| 	case ".myco": | ||||
| 		isText = true | ||||
| 	case "", shortPath: | ||||
| 		skip = true | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| var mapMime2Ext = map[string]string{ | ||||
| 	"application/octet-stream": "bin", | ||||
| 	"image/jpeg":               "jpg", | ||||
| 	"image/gif":                "gif", | ||||
| 	"image/png":                "png", | ||||
| 	"image/webp":               "webp", | ||||
| 	"image/svg+xml":            "svg", | ||||
| 	"image/x-icon":             "ico", | ||||
| 	"application/ogg":          "ogg", | ||||
| 	"video/webm":               "webm", | ||||
| 	"audio/mp3":                "mp3", | ||||
| 	"video/mp4":                "mp4", | ||||
| } | ||||
|  | ||||
| var mapExt2Mime = map[string]string{ | ||||
| 	".bin":  "application/octet-stream", | ||||
| 	".jpg":  "image/jpeg", | ||||
| 	".jpeg": "image/jpeg", | ||||
| 	".gif":  "image/gif", | ||||
| 	".png":  "image/png", | ||||
| 	".webp": "image/webp", | ||||
| 	".svg":  "image/svg+xml", | ||||
| 	".ico":  "image/x-icon", | ||||
| 	".ogg":  "application/ogg", | ||||
| 	".webm": "video/webm", | ||||
| 	".mp3":  "audio/mp3", | ||||
| 	".mp4":  "video/mp4", | ||||
| } | ||||
							
								
								
									
										58
									
								
								name.go
									
									
									
									
									
								
							
							
						
						| @@ -1,52 +1,34 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.sr.ht/~adnano/go-gemini" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| // isCanonicalName checks if the `name` is canonical. | ||||
| func isCanonicalName(name string) bool { | ||||
| 	return HyphaPattern.MatchString(name) | ||||
| } | ||||
|  | ||||
| // CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores. | ||||
| func CanonicalName(name string) string { | ||||
| 	return strings.ToLower(strings.ReplaceAll(name, " ", "_")) | ||||
| } | ||||
|  | ||||
| // naviTitle turns `canonicalName` into html string with each hypha path parts higlighted as links. | ||||
| // TODO: rework as a template | ||||
| func naviTitle(canonicalName string) string { | ||||
| 	var ( | ||||
| 		html = fmt.Sprintf(`<h1 class="navi-title" id="navi-title"> | ||||
| 	<a href="/page/%s">%s</a><span aria-hidden="true" class="navi-title__colon">:</span>`, util.HomePage, util.SiteNavIcon) | ||||
| 		prevAcc = `/page/` | ||||
| 		parts   = strings.Split(canonicalName, "/") | ||||
| 		rel     = "up" | ||||
| 	) | ||||
| 	for i, part := range parts { | ||||
| 		if i > 0 { | ||||
| 			html += `<span aria-hidden="true" class="navi-title__separator">/</span>` | ||||
| // 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+"/")) | ||||
| 		} | ||||
| 		if i == len(parts)-1 { | ||||
| 			rel = "bookmark" | ||||
| 		} | ||||
| 		html += fmt.Sprintf( | ||||
| 			`<a href="%s" rel="%s">%s</a>`, | ||||
| 			prevAcc+part, | ||||
| 			rel, | ||||
| 			util.BeautifulName(part), | ||||
| 		) | ||||
| 		prevAcc += part + "/" | ||||
| 	} | ||||
| 	return html + "</h1>" | ||||
| 	panic("HyphaNameFromRq: no matching action passed") | ||||
| } | ||||
|  | ||||
| // HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url. For url /page/hypha, the action would be "page". | ||||
| func HyphaNameFromRq(rq *http.Request, action string) string { | ||||
| 	return CanonicalName(strings.TrimPrefix(rq.URL.Path, "/"+action+"/")) | ||||
| // 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 "" | ||||
| } | ||||
|   | ||||
							
								
								
									
										36
									
								
								shroom/backlink.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/markup" | ||||
| ) | ||||
|  | ||||
| // FindAllBacklinks iterates over all hyphae that have text parts, sets their outlinks and then sets backlinks. | ||||
| func FindAllBacklinks() { | ||||
| 	for h := range hyphae.FilterTextHyphae(hyphae.YieldExistingHyphae()) { | ||||
| 		findBacklinkWorker(h) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func findBacklinkWorker(h *hyphae.Hypha) { | ||||
| 	var ( | ||||
| 		textContents, err = ioutil.ReadFile(h.TextPath) | ||||
| 	) | ||||
| 	if err == nil { | ||||
| 		for outlink := range markup.Doc(h.Name, string(textContents)).OutLinks() { | ||||
| 			outlinkHypha := hyphae.ByName(outlink) | ||||
| 			if outlinkHypha == h { | ||||
| 				break | ||||
| 			} | ||||
|  | ||||
| 			outlinkHypha.AddBackLink(h) | ||||
| 			outlinkHypha.InsertIfNewKeepExistence() | ||||
| 			h.AddOutLink(outlinkHypha) | ||||
| 		} | ||||
| 	} else { | ||||
| 		log.Println("Error when reading text contents of ‘%s’: %s", h.Name, err.Error()) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										92
									
								
								shroom/can.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,92 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| ) | ||||
|  | ||||
| func canFactory( | ||||
| 	rejectLogger func(*hyphae.Hypha, *user.User, string), | ||||
| 	action string, | ||||
| 	dispatcher func(*hyphae.Hypha, *user.User) (string, string), | ||||
| 	noRightsMsg string, | ||||
| 	notExistsMsg string, | ||||
| 	careAboutExistince bool, | ||||
| ) func(*user.User, *hyphae.Hypha) (error, string) { | ||||
| 	return func(u *user.User, h *hyphae.Hypha) (error, string) { | ||||
| 		if !u.CanProceed(action) { | ||||
| 			rejectLogger(h, u, "no rights") | ||||
| 			return errors.New(noRightsMsg), "Not enough rights" | ||||
| 		} | ||||
|  | ||||
| 		if careAboutExistince && !h.Exists { | ||||
| 			rejectLogger(h, u, "does not exist") | ||||
| 			return errors.New(notExistsMsg), "Does not exist" | ||||
| 		} | ||||
|  | ||||
| 		if dispatcher == nil { | ||||
| 			return nil, "" | ||||
| 		} | ||||
| 		errmsg, errtitle := dispatcher(h, u) | ||||
| 		if errtitle == "" { | ||||
| 			return nil, "" | ||||
| 		} | ||||
| 		return errors.New(errmsg), errtitle | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	CanDelete = canFactory( | ||||
| 		rejectDeleteLog, | ||||
| 		"delete-confirm", | ||||
| 		nil, | ||||
| 		"Not enough rights to delete, you must be a moderator", | ||||
| 		"Cannot delete this hypha because it does not exist", | ||||
| 		true, | ||||
| 	) | ||||
|  | ||||
| 	CanRename = canFactory( | ||||
| 		rejectRenameLog, | ||||
| 		"rename-confirm", | ||||
| 		nil, | ||||
| 		"Not enough rights to rename, you must be a trusted editor", | ||||
| 		"Cannot rename this hypha because it does not exist", | ||||
| 		true, | ||||
| 	) | ||||
|  | ||||
| 	CanUnattach = canFactory( | ||||
| 		rejectUnattachLog, | ||||
| 		"unattach-confirm", | ||||
| 		func(h *hyphae.Hypha, u *user.User) (errmsg, errtitle string) { | ||||
| 			if h.BinaryPath == "" { | ||||
| 				rejectUnattachLog(h, u, "no amnt") | ||||
| 				return "Cannot unattach this hypha because it has no attachment", "No attachment" | ||||
| 			} | ||||
|  | ||||
| 			return "", "" | ||||
| 		}, | ||||
| 		"Not enough rights to unattach, you must be a trusted editor", | ||||
| 		"Cannot unattach this hypha because it does not exist", | ||||
| 		true, | ||||
| 	) | ||||
|  | ||||
| 	CanEdit = canFactory( | ||||
| 		rejectEditLog, | ||||
| 		"upload-text", | ||||
| 		nil, | ||||
| 		"You must be an editor to edit a hypha", | ||||
| 		"You cannot edit a hypha that does not exist", | ||||
| 		false, | ||||
| 	) | ||||
|  | ||||
| 	CanAttach = canFactory( | ||||
| 		rejectAttachLog, | ||||
| 		"upload-binary", | ||||
| 		nil, | ||||
| 		"You must be an editor to attach a hypha", | ||||
| 		"You cannot attach a hypha that does not exist", | ||||
| 		false, | ||||
| 	) | ||||
| ) | ||||
							
								
								
									
										29
									
								
								shroom/delete.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| ) | ||||
|  | ||||
| // DeleteHypha deletes hypha and makes a history record about that. | ||||
| func DeleteHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) { | ||||
| 	hop = history.Operation(history.TypeDeleteHypha) | ||||
|  | ||||
| 	if err, errtitle := CanDelete(u, h); errtitle != "" { | ||||
| 		hop.WithErrAbort(err) | ||||
| 		return hop, errtitle | ||||
| 	} | ||||
|  | ||||
| 	hop. | ||||
| 		WithFilesRemoved(h.TextPath, h.BinaryPath). | ||||
| 		WithMsg(fmt.Sprintf("Delete ‘%s’", h.Name)). | ||||
| 		WithUser(u). | ||||
| 		Apply() | ||||
| 	if !hop.HasErrors() { | ||||
| 		h.Delete() | ||||
| 	} | ||||
| 	return hop, "" | ||||
| } | ||||
							
								
								
									
										38
									
								
								shroom/init.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/markup" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| 	"github.com/bouncepaw/mycorrhiza/views" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	markup.HyphaExists = func(hyphaName string) bool { | ||||
| 		return hyphae.ByName(hyphaName).Exists | ||||
| 	} | ||||
| 	markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { | ||||
| 		if h := hyphae.ByName(hyphaName); h.Exists { | ||||
| 			rawText, err = FetchTextPart(h) | ||||
| 			if h.BinaryPath != "" { | ||||
| 				binaryBlock = views.AttachmentHTML(h) | ||||
| 			} | ||||
| 		} else { | ||||
| 			err = errors.New("Hypha " + hyphaName + " does not exist") | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	markup.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" | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										24
									
								
								shroom/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| ) | ||||
|  | ||||
| func rejectDeleteLog(h *hyphae.Hypha, u *user.User, errmsg string) { | ||||
| 	log.Printf("Reject delete ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) | ||||
| } | ||||
| func rejectRenameLog(h *hyphae.Hypha, u *user.User, errmsg string) { | ||||
| 	log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) | ||||
| } | ||||
| func rejectUnattachLog(h *hyphae.Hypha, u *user.User, errmsg string) { | ||||
| 	log.Printf("Reject unattach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) | ||||
| } | ||||
| func rejectEditLog(h *hyphae.Hypha, u *user.User, errmsg string) { | ||||
| 	log.Printf("Reject edit ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) | ||||
| } | ||||
| func rejectAttachLog(h *hyphae.Hypha, u *user.User, errmsg string) { | ||||
| 	log.Printf("Reject attach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) | ||||
| } | ||||
							
								
								
									
										106
									
								
								shroom/rename.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,106 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User) (err error, errtitle string) { | ||||
| 	if nh.Exists { | ||||
| 		rejectRenameLog(oh, u, fmt.Sprintf("name ‘%s’ taken already", nh.Name)) | ||||
| 		return errors.New(fmt.Sprintf("Hypha named <a href='/hypha/%[1]s'>%[1]s</a> already exists, cannot rename", nh.Name)), "Name taken" | ||||
| 	} | ||||
|  | ||||
| 	if nh.Name == "" { | ||||
| 		rejectRenameLog(oh, u, "no new name given") | ||||
| 		return errors.New("No new name is given"), "No name given" | ||||
| 	} | ||||
|  | ||||
| 	if !hyphae.HyphaPattern.MatchString(nh.Name) { | ||||
| 		rejectRenameLog(oh, u, fmt.Sprintf("new name ‘%s’ invalid", nh.Name)) | ||||
| 		return errors.New("Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>"), "Invalid name" | ||||
| 	} | ||||
|  | ||||
| 	return nil, "" | ||||
| } | ||||
|  | ||||
| // RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way. | ||||
| func RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *user.User) (hop *history.HistoryOp, errtitle string) { | ||||
| 	newHypha.Lock() | ||||
| 	defer newHypha.Unlock() | ||||
| 	hop = history.Operation(history.TypeRenameHypha) | ||||
|  | ||||
| 	if err, errtitle := CanRename(u, h); errtitle != "" { | ||||
| 		hop.WithErrAbort(err) | ||||
| 		return hop, errtitle | ||||
| 	} | ||||
| 	if err, errtitle := canRenameThisToThat(h, newHypha, u); errtitle != "" { | ||||
| 		hop.WithErrAbort(err) | ||||
| 		return hop, errtitle | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		re          = regexp.MustCompile(`(?i)` + h.Name) | ||||
| 		replaceName = func(str string) string { | ||||
| 			return re.ReplaceAllString(util.CanonicalName(str), newHypha.Name) | ||||
| 		} | ||||
| 		hyphaeToRename = findHyphaeToRename(h, recursive) | ||||
| 		renameMap, err = renamingPairs(hyphaeToRename, replaceName) | ||||
| 		renameMsg      = "Rename ‘%s’ to ‘%s’" | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		hop.Errs = append(hop.Errs, err) | ||||
| 		return hop, hop.FirstErrorText() | ||||
| 	} | ||||
| 	if recursive && len(hyphaeToRename) > 0 { | ||||
| 		renameMsg += " recursively" | ||||
| 	} | ||||
| 	hop.WithFilesRenamed(renameMap). | ||||
| 		WithMsg(fmt.Sprintf(renameMsg, h.Name, newHypha.Name)). | ||||
| 		WithUser(u). | ||||
| 		Apply() | ||||
| 	if len(hop.Errs) == 0 { | ||||
| 		for _, h := range hyphaeToRename { | ||||
| 			h.RenameTo(replaceName(h.Name)) | ||||
| 			h.Lock() | ||||
| 			h.TextPath = replaceName(h.TextPath) | ||||
| 			h.BinaryPath = replaceName(h.BinaryPath) | ||||
| 			h.Unlock() | ||||
| 		} | ||||
| 	} | ||||
| 	return hop, "" | ||||
| } | ||||
|  | ||||
| func findHyphaeToRename(superhypha *hyphae.Hypha, recursive bool) []*hyphae.Hypha { | ||||
| 	hyphae := []*hyphae.Hypha{superhypha} | ||||
| 	if recursive { | ||||
| 		hyphae = append(hyphae, superhypha.Subhyphae()...) | ||||
| 	} | ||||
| 	return hyphae | ||||
| } | ||||
|  | ||||
| func renamingPairs(hyphaeToRename []*hyphae.Hypha, replaceName func(string) string) (map[string]string, error) { | ||||
| 	renameMap := make(map[string]string) | ||||
| 	newNames := make([]string, len(hyphaeToRename)) | ||||
| 	for _, h := range hyphaeToRename { | ||||
| 		h.Lock() | ||||
| 		newNames = append(newNames, replaceName(h.Name)) | ||||
| 		if h.TextPath != "" { | ||||
| 			renameMap[h.TextPath] = replaceName(h.TextPath) | ||||
| 		} | ||||
| 		if h.BinaryPath != "" { | ||||
| 			renameMap[h.BinaryPath] = replaceName(h.BinaryPath) | ||||
| 		} | ||||
| 		h.Unlock() | ||||
| 	} | ||||
| 	if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok { | ||||
| 		return nil, errors.New("Hypha " + firstFailure + " already exists") | ||||
| 	} | ||||
| 	return renameMap, nil | ||||
| } | ||||
							
								
								
									
										41
									
								
								shroom/unattach.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/user" | ||||
| ) | ||||
|  | ||||
| // UnattachHypha unattaches hypha and makes a history record about that. | ||||
| func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) { | ||||
| 	hop = history.Operation(history.TypeUnattachHypha) | ||||
|  | ||||
| 	if err, errtitle := CanUnattach(u, h); errtitle != "" { | ||||
| 		hop.WithErrAbort(err) | ||||
| 		return hop, errtitle | ||||
| 	} | ||||
|  | ||||
| 	hop. | ||||
| 		WithFilesRemoved(h.BinaryPath). | ||||
| 		WithMsg(fmt.Sprintf("Unattach ‘%s’", h.Name)). | ||||
| 		WithUser(u). | ||||
| 		Apply() | ||||
|  | ||||
| 	if len(hop.Errs) > 0 { | ||||
| 		rejectUnattachLog(h, u, "fail") | ||||
| 		// FIXME: something may be wrong here | ||||
| 		return hop.WithErrAbort(errors.New(fmt.Sprintf("Could not unattach this hypha due to internal server errors: <code>%v</code>", hop.Errs))), "Error" | ||||
| 	} | ||||
|  | ||||
| 	if h.BinaryPath != "" { | ||||
| 		h.BinaryPath = "" | ||||
| 	} | ||||
| 	// If nothing is left of the hypha | ||||
| 	if h.TextPath == "" { | ||||
| 		h.Delete() | ||||
| 	} | ||||
| 	return hop, "" | ||||
| } | ||||
							
								
								
									
										87
									
								
								shroom/upload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,87 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"mime/multipart" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/history" | ||||
| 	"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) { | ||||
| 	hop = history.Operation(history.TypeEditText) | ||||
| 	if h.Exists { | ||||
| 		hop.WithMsg(fmt.Sprintf("Edit ‘%s’", h.Name)) | ||||
| 	} else { | ||||
| 		hop.WithMsg(fmt.Sprintf("Create ‘%s’", h.Name)) | ||||
| 	} | ||||
|  | ||||
| 	if err, errtitle := CanEdit(u, h); err != nil { | ||||
| 		return hop.WithErrAbort(err), errtitle | ||||
| 	} | ||||
| 	if len(data) == 0 { | ||||
| 		return hop.WithErrAbort(errors.New("No data passed")), "Empty" | ||||
| 	} | ||||
|  | ||||
| 	return uploadHelp(h, hop, ".myco", data, u) | ||||
| } | ||||
|  | ||||
| func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.User) (*history.HistoryOp, string) { | ||||
| 	var ( | ||||
| 		hop       = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", h.Name, mime)) | ||||
| 		data, err = ioutil.ReadAll(file) | ||||
| 	) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return hop.WithErrAbort(err), err.Error() | ||||
| 	} | ||||
| 	if err, errtitle := CanAttach(u, h); err != nil { | ||||
| 		return hop.WithErrAbort(err), errtitle | ||||
| 	} | ||||
| 	if len(data) == 0 { | ||||
| 		return hop.WithErrAbort(errors.New("No data passed")), "Empty" | ||||
| 	} | ||||
|  | ||||
| 	return uploadHelp(h, hop, mimetype.ToExtension(mime), data, u) | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 		originalFullPath = &h.TextPath | ||||
| 	) | ||||
| 	if hop.Type == history.TypeEditBinary { | ||||
| 		originalFullPath = &h.BinaryPath | ||||
| 	} | ||||
|  | ||||
| 	if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { | ||||
| 		return hop.WithErrAbort(err), err.Error() | ||||
| 	} | ||||
|  | ||||
| 	if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { | ||||
| 		return hop.WithErrAbort(err), err.Error() | ||||
| 	} | ||||
|  | ||||
| 	if h.Exists && *originalFullPath != fullPath && *originalFullPath != "" { | ||||
| 		if err := history.Rename(*originalFullPath, fullPath); err != nil { | ||||
| 			return hop.WithErrAbort(err), err.Error() | ||||
| 		} | ||||
| 		log.Println("Move", *originalFullPath, "to", fullPath) | ||||
| 	} | ||||
|  | ||||
| 	h.InsertIfNew() | ||||
| 	if h.Exists && h.TextPath != "" && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) { | ||||
| 		return hop.Abort(), "No changes" | ||||
| 	} | ||||
| 	*originalFullPath = fullPath | ||||
| 	return hop.WithFiles(fullPath).WithUser(u).Apply(), "" | ||||
| } | ||||
							
								
								
									
										38
									
								
								shroom/view.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,38 @@ | ||||
| package shroom | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
|  | ||||
| 	"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. | ||||
| func FetchTextPart(h *hyphae.Hypha) (string, error) { | ||||
| 	if h.TextPath == "" { | ||||
| 		return "", nil | ||||
| 	} | ||||
| 	text, err := ioutil.ReadFile(h.TextPath) | ||||
| 	if os.IsNotExist(err) { | ||||
| 		return "", nil | ||||
| 	} else if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return string(text), nil | ||||
| } | ||||
|  | ||||
| func SetHeaderLinks() { | ||||
| 	if userLinksHypha := hyphae.ByName(util.HeaderLinksHypha); !userLinksHypha.Exists { | ||||
| 		util.SetDefaultHeaderLinks() | ||||
| 	} else { | ||||
| 		contents, err := ioutil.ReadFile(userLinksHypha.TextPath) | ||||
| 		if err != nil || len(contents) == 0 { | ||||
| 			util.SetDefaultHeaderLinks() | ||||
| 		} else { | ||||
| 			text := string(contents) | ||||
| 			util.ParseHeaderLinks(text, markup.Rocketlink) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| {% import "github.com/bouncepaw/mycorrhiza/user" %} | ||||
|  | ||||
| {% func LoginHTML() %} | ||||
| <main> | ||||
| 	<section> | ||||
| 	{% if user.AuthUsed %} | ||||
| 		<h1>Login</h1> | ||||
| 		<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data"> | ||||
| 			<p>Use the data you were given by an administrator.</p> | ||||
| 			<fieldset> | ||||
| 				<legend>Username</legend> | ||||
| 				<input type="text" required autofocus name="username" autocomplete="on"> | ||||
| 			</fieldset> | ||||
| 			<fieldset> | ||||
| 				<legend>Password</legend> | ||||
| 				<input type="password" required name="password" autocomplete="on"> | ||||
| 			</fieldset> | ||||
| 			<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> | ||||
| 			<input type="submit"> | ||||
| 			<a href="/">Cancel</a> | ||||
| 		</form> | ||||
| 	{% else %} | ||||
| 		<p>Administrator of this wiki have not configured any authorization method. You can make edits anonymously.</p> | ||||
| 		<p><a href="/">← Go home</a></p> | ||||
| 	{% endif %} | ||||
| 	</section> | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func LoginErrorHTML(err string) %} | ||||
| <main> | ||||
| 	<section> | ||||
| 	{% switch err %} | ||||
| 	{% case "unknown username" %} | ||||
| 		<p class="error">Unknown username.</p> | ||||
| 	{% case "wrong password" %} | ||||
| 		<p class="error">Wrong password.</p> | ||||
| 	{% default %} | ||||
| 		<p class="error">{%s err %}</p> | ||||
| 	{% endswitch %} | ||||
| 		<p><a href="/login">← Try again</a></p> | ||||
| 	</section> | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func LogoutHTML(can bool) %} | ||||
| <main> | ||||
| 	<section> | ||||
| 	{% if can %} | ||||
| 		<h1>Log out?</h1> | ||||
| 		<p><a href="/logout-confirm"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/">Cancel</a></p> | ||||
| 	{% else %} | ||||
| 		<p>You cannot log out because you are not logged in.</p> | ||||
| 		<p><a href="/login">Login</a></p> | ||||
| 		<p><a href="/login">← Home</a></p> | ||||
| 	{% endif %} | ||||
| 	</section> | ||||
| </main> | ||||
| {% endfunc %} | ||||
| @@ -1,218 +0,0 @@ | ||||
| // Code generated by qtc from "auth.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/auth.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/auth.qtpl:1 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line templates/auth.qtpl:3 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/auth.qtpl:3 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/auth.qtpl:3 | ||||
| func StreamLoginHTML(qw422016 *qt422016.Writer) { | ||||
| //line templates/auth.qtpl:3 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<section> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:6 | ||||
| 	if user.AuthUsed { | ||||
| //line templates/auth.qtpl:6 | ||||
| 		qw422016.N().S(` | ||||
| 		<h1>Login</h1> | ||||
| 		<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data"> | ||||
| 			<p>Use the data you were given by an administrator.</p> | ||||
| 			<fieldset> | ||||
| 				<legend>Username</legend> | ||||
| 				<input type="text" required autofocus name="username" autocomplete="on"> | ||||
| 			</fieldset> | ||||
| 			<fieldset> | ||||
| 				<legend>Password</legend> | ||||
| 				<input type="password" required name="password" autocomplete="on"> | ||||
| 			</fieldset> | ||||
| 			<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> | ||||
| 			<input type="submit"> | ||||
| 			<a href="/">Cancel</a> | ||||
| 		</form> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:22 | ||||
| 	} else { | ||||
| //line templates/auth.qtpl:22 | ||||
| 		qw422016.N().S(` | ||||
| 		<p>Administrator of this wiki have not configured any authorization method. You can make edits anonymously.</p> | ||||
| 		<p><a href="/">← Go home</a></p> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:25 | ||||
| 	} | ||||
| //line templates/auth.qtpl:25 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| </main> | ||||
| `) | ||||
| //line templates/auth.qtpl:28 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:28 | ||||
| func WriteLoginHTML(qq422016 qtio422016.Writer) { | ||||
| //line templates/auth.qtpl:28 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/auth.qtpl:28 | ||||
| 	StreamLoginHTML(qw422016) | ||||
| //line templates/auth.qtpl:28 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/auth.qtpl:28 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:28 | ||||
| func LoginHTML() string { | ||||
| //line templates/auth.qtpl:28 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/auth.qtpl:28 | ||||
| 	WriteLoginHTML(qb422016) | ||||
| //line templates/auth.qtpl:28 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/auth.qtpl:28 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/auth.qtpl:28 | ||||
| 	return qs422016 | ||||
| //line templates/auth.qtpl:28 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:30 | ||||
| func StreamLoginErrorHTML(qw422016 *qt422016.Writer, err string) { | ||||
| //line templates/auth.qtpl:30 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<section> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:33 | ||||
| 	switch err { | ||||
| //line templates/auth.qtpl:34 | ||||
| 	case "unknown username": | ||||
| //line templates/auth.qtpl:34 | ||||
| 		qw422016.N().S(` | ||||
| 		<p class="error">Unknown username.</p> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:36 | ||||
| 	case "wrong password": | ||||
| //line templates/auth.qtpl:36 | ||||
| 		qw422016.N().S(` | ||||
| 		<p class="error">Wrong password.</p> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:38 | ||||
| 	default: | ||||
| //line templates/auth.qtpl:38 | ||||
| 		qw422016.N().S(` | ||||
| 		<p class="error">`) | ||||
| //line templates/auth.qtpl:39 | ||||
| 		qw422016.E().S(err) | ||||
| //line templates/auth.qtpl:39 | ||||
| 		qw422016.N().S(`</p> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:40 | ||||
| 	} | ||||
| //line templates/auth.qtpl:40 | ||||
| 	qw422016.N().S(` | ||||
| 		<p><a href="/login">← Try again</a></p> | ||||
| 	</section> | ||||
| </main> | ||||
| `) | ||||
| //line templates/auth.qtpl:44 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:44 | ||||
| func WriteLoginErrorHTML(qq422016 qtio422016.Writer, err string) { | ||||
| //line templates/auth.qtpl:44 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/auth.qtpl:44 | ||||
| 	StreamLoginErrorHTML(qw422016, err) | ||||
| //line templates/auth.qtpl:44 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/auth.qtpl:44 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:44 | ||||
| func LoginErrorHTML(err string) string { | ||||
| //line templates/auth.qtpl:44 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/auth.qtpl:44 | ||||
| 	WriteLoginErrorHTML(qb422016, err) | ||||
| //line templates/auth.qtpl:44 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/auth.qtpl:44 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/auth.qtpl:44 | ||||
| 	return qs422016 | ||||
| //line templates/auth.qtpl:44 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:46 | ||||
| func StreamLogoutHTML(qw422016 *qt422016.Writer, can bool) { | ||||
| //line templates/auth.qtpl:46 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<section> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:49 | ||||
| 	if can { | ||||
| //line templates/auth.qtpl:49 | ||||
| 		qw422016.N().S(` | ||||
| 		<h1>Log out?</h1> | ||||
| 		<p><a href="/logout-confirm"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/">Cancel</a></p> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:53 | ||||
| 	} else { | ||||
| //line templates/auth.qtpl:53 | ||||
| 		qw422016.N().S(` | ||||
| 		<p>You cannot log out because you are not logged in.</p> | ||||
| 		<p><a href="/login">Login</a></p> | ||||
| 		<p><a href="/login">← Home</a></p> | ||||
| 	`) | ||||
| //line templates/auth.qtpl:57 | ||||
| 	} | ||||
| //line templates/auth.qtpl:57 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| </main> | ||||
| `) | ||||
| //line templates/auth.qtpl:60 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:60 | ||||
| func WriteLogoutHTML(qq422016 qtio422016.Writer, can bool) { | ||||
| //line templates/auth.qtpl:60 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/auth.qtpl:60 | ||||
| 	StreamLogoutHTML(qw422016, can) | ||||
| //line templates/auth.qtpl:60 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/auth.qtpl:60 | ||||
| } | ||||
|  | ||||
| //line templates/auth.qtpl:60 | ||||
| func LogoutHTML(can bool) string { | ||||
| //line templates/auth.qtpl:60 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/auth.qtpl:60 | ||||
| 	WriteLogoutHTML(qb422016, can) | ||||
| //line templates/auth.qtpl:60 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/auth.qtpl:60 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/auth.qtpl:60 | ||||
| 	return qs422016 | ||||
| //line templates/auth.qtpl:60 | ||||
| } | ||||
| @@ -1,214 +0,0 @@ | ||||
| // Code generated by qtc from "common.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/common.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/common.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| //line templates/common.qtpl:2 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line templates/common.qtpl:3 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| // This is the <nav> seen on top of many pages. | ||||
|  | ||||
| //line templates/common.qtpl:6 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/common.qtpl:6 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/common.qtpl:7 | ||||
| type navEntry struct { | ||||
| 	path  string | ||||
| 	title string | ||||
| } | ||||
|  | ||||
| var navEntries = []navEntry{ | ||||
| 	{"page", "Hypha"}, | ||||
| 	{"edit", "Edit"}, | ||||
| 	{"text", "Raw text"}, | ||||
| 	{"history", "History"}, | ||||
| 	{"revision", "NOT REACHED"}, | ||||
| 	{"rename-ask", "Rename"}, | ||||
| 	{"delete-ask", "Delete"}, | ||||
| } | ||||
|  | ||||
| //line templates/common.qtpl:22 | ||||
| func streamnavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) { | ||||
| //line templates/common.qtpl:22 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/common.qtpl:24 | ||||
| 	u := user.FromRequest(rq) | ||||
|  | ||||
| //line templates/common.qtpl:25 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	<nav class="hypha-tabs"> | ||||
| 		<ul class="hypha-tabs__flex"> | ||||
| `) | ||||
| //line templates/common.qtpl:29 | ||||
| 	for _, entry := range navEntries { | ||||
| //line templates/common.qtpl:30 | ||||
| 		if navType == "revision" && entry.path == "revision" { | ||||
| //line templates/common.qtpl:30 | ||||
| 			qw422016.N().S(`			<li class="hypha-tabs__tab hypha-tabs__tab_active"> | ||||
| 				`) | ||||
| //line templates/common.qtpl:32 | ||||
| 			qw422016.E().S(revisionHash[0]) | ||||
| //line templates/common.qtpl:32 | ||||
| 			qw422016.N().S(` | ||||
| 			</li> | ||||
| `) | ||||
| //line templates/common.qtpl:34 | ||||
| 		} else if navType == entry.path { | ||||
| //line templates/common.qtpl:34 | ||||
| 			qw422016.N().S(`			<li class="hypha-tabs__tab hypha-tabs__tab_active"> | ||||
| 				`) | ||||
| //line templates/common.qtpl:36 | ||||
| 			qw422016.E().S(entry.title) | ||||
| //line templates/common.qtpl:36 | ||||
| 			qw422016.N().S(` | ||||
| 			</li> | ||||
| `) | ||||
| //line templates/common.qtpl:38 | ||||
| 		} else if entry.path != "revision" && u.CanProceed(entry.path) { | ||||
| //line templates/common.qtpl:38 | ||||
| 			qw422016.N().S(`			<li class="hypha-tabs__tab"> | ||||
| 				<a href="/`) | ||||
| //line templates/common.qtpl:40 | ||||
| 			qw422016.E().S(entry.path) | ||||
| //line templates/common.qtpl:40 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line templates/common.qtpl:40 | ||||
| 			qw422016.E().S(hyphaName) | ||||
| //line templates/common.qtpl:40 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line templates/common.qtpl:40 | ||||
| 			qw422016.E().S(entry.title) | ||||
| //line templates/common.qtpl:40 | ||||
| 			qw422016.N().S(`</a> | ||||
| 			</li> | ||||
| `) | ||||
| //line templates/common.qtpl:42 | ||||
| 		} | ||||
| //line templates/common.qtpl:43 | ||||
| 	} | ||||
| //line templates/common.qtpl:43 | ||||
| 	qw422016.N().S(`		</ul> | ||||
| 	</nav> | ||||
| `) | ||||
| //line templates/common.qtpl:46 | ||||
| } | ||||
|  | ||||
| //line templates/common.qtpl:46 | ||||
| func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) { | ||||
| //line templates/common.qtpl:46 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/common.qtpl:46 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...) | ||||
| //line templates/common.qtpl:46 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/common.qtpl:46 | ||||
| } | ||||
|  | ||||
| //line templates/common.qtpl:46 | ||||
| func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string { | ||||
| //line templates/common.qtpl:46 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/common.qtpl:46 | ||||
| 	writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...) | ||||
| //line templates/common.qtpl:46 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/common.qtpl:46 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/common.qtpl:46 | ||||
| 	return qs422016 | ||||
| //line templates/common.qtpl:46 | ||||
| } | ||||
|  | ||||
| //line templates/common.qtpl:48 | ||||
| func streamuserMenuHTML(qw422016 *qt422016.Writer, u *user.User) { | ||||
| //line templates/common.qtpl:48 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/common.qtpl:49 | ||||
| 	if user.AuthUsed { | ||||
| //line templates/common.qtpl:49 | ||||
| 		qw422016.N().S(` | ||||
| <li class="header-links__entry header-links__entry_user"> | ||||
| 	`) | ||||
| //line templates/common.qtpl:51 | ||||
| 		if u.Group == "anon" { | ||||
| //line templates/common.qtpl:51 | ||||
| 			qw422016.N().S(` | ||||
| 	<a href="/login" class="header-links__link">Login</a> | ||||
| 	`) | ||||
| //line templates/common.qtpl:53 | ||||
| 		} else { | ||||
| //line templates/common.qtpl:53 | ||||
| 			qw422016.N().S(` | ||||
| 	<a href="/page/`) | ||||
| //line templates/common.qtpl:54 | ||||
| 			qw422016.E().S(util.UserHypha) | ||||
| //line templates/common.qtpl:54 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line templates/common.qtpl:54 | ||||
| 			qw422016.E().S(u.Name) | ||||
| //line templates/common.qtpl:54 | ||||
| 			qw422016.N().S(`" class="header-links__link">`) | ||||
| //line templates/common.qtpl:54 | ||||
| 			qw422016.E().S(u.Name) | ||||
| //line templates/common.qtpl:54 | ||||
| 			qw422016.N().S(`</a> | ||||
| 	`) | ||||
| //line templates/common.qtpl:55 | ||||
| 		} | ||||
| //line templates/common.qtpl:55 | ||||
| 		qw422016.N().S(` | ||||
| </li> | ||||
| `) | ||||
| //line templates/common.qtpl:57 | ||||
| 	} | ||||
| //line templates/common.qtpl:57 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/common.qtpl:58 | ||||
| } | ||||
|  | ||||
| //line templates/common.qtpl:58 | ||||
| func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) { | ||||
| //line templates/common.qtpl:58 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/common.qtpl:58 | ||||
| 	streamuserMenuHTML(qw422016, u) | ||||
| //line templates/common.qtpl:58 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/common.qtpl:58 | ||||
| } | ||||
|  | ||||
| //line templates/common.qtpl:58 | ||||
| func userMenuHTML(u *user.User) string { | ||||
| //line templates/common.qtpl:58 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/common.qtpl:58 | ||||
| 	writeuserMenuHTML(qb422016, u) | ||||
| //line templates/common.qtpl:58 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/common.qtpl:58 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/common.qtpl:58 | ||||
| 	return qs422016 | ||||
| //line templates/common.qtpl:58 | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| {% import "net/http" %} | ||||
|  | ||||
| This dialog is to be shown to a user when they try to delete a hypha. | ||||
| {% func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) %} | ||||
| {%= navHTML(rq, hyphaName, "delete-ask") %} | ||||
| <main> | ||||
| {% if isOld %} | ||||
| 	<section> | ||||
| 		<h1>Delete {%s hyphaName %}?</h1> | ||||
| 		<p>Do you really want to delete hypha <em>{%s hyphaName %}</em>?</p> | ||||
| 		<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p> | ||||
| 		<p><a href="/delete-confirm/{%s hyphaName %}"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/page/{%s hyphaName %}">Cancel</a></p> | ||||
| 	</section> | ||||
| {% else %} | ||||
| 	{%= cannotDeleteDueToNonExistence(hyphaName) %} | ||||
| {% endif %} | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func cannotDeleteDueToNonExistence(hyphaName string) %} | ||||
| 	<section> | ||||
| 		<h1>Cannot delete {%s hyphaName %}</h1> | ||||
| 		<p>This hypha does not exist.</p> | ||||
| 		<p><a href="/page/{%s hyphaName %}">Go back</a></p> | ||||
| 	</section> | ||||
| {% endfunc %} | ||||
| @@ -1,154 +0,0 @@ | ||||
| // Code generated by qtc from "delete.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/delete.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/delete.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| // This dialog is to be shown to a user when they try to delete a hypha. | ||||
|  | ||||
| //line templates/delete.qtpl:4 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/delete.qtpl:4 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/delete.qtpl:4 | ||||
| func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line templates/delete.qtpl:4 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/delete.qtpl:5 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, "delete-ask") | ||||
| //line templates/delete.qtpl:5 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| `) | ||||
| //line templates/delete.qtpl:7 | ||||
| 	if isOld { | ||||
| //line templates/delete.qtpl:7 | ||||
| 		qw422016.N().S(` | ||||
| 	<section> | ||||
| 		<h1>Delete `) | ||||
| //line templates/delete.qtpl:9 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/delete.qtpl:9 | ||||
| 		qw422016.N().S(`?</h1> | ||||
| 		<p>Do you really want to delete hypha <em>`) | ||||
| //line templates/delete.qtpl:10 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/delete.qtpl:10 | ||||
| 		qw422016.N().S(`</em>?</p> | ||||
| 		<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p> | ||||
| 		<p><a href="/delete-confirm/`) | ||||
| //line templates/delete.qtpl:12 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/delete.qtpl:12 | ||||
| 		qw422016.N().S(`"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/page/`) | ||||
| //line templates/delete.qtpl:13 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/delete.qtpl:13 | ||||
| 		qw422016.N().S(`">Cancel</a></p> | ||||
| 	</section> | ||||
| `) | ||||
| //line templates/delete.qtpl:15 | ||||
| 	} else { | ||||
| //line templates/delete.qtpl:15 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line templates/delete.qtpl:16 | ||||
| 		streamcannotDeleteDueToNonExistence(qw422016, hyphaName) | ||||
| //line templates/delete.qtpl:16 | ||||
| 		qw422016.N().S(` | ||||
| `) | ||||
| //line templates/delete.qtpl:17 | ||||
| 	} | ||||
| //line templates/delete.qtpl:17 | ||||
| 	qw422016.N().S(` | ||||
| </main> | ||||
| `) | ||||
| //line templates/delete.qtpl:19 | ||||
| } | ||||
|  | ||||
| //line templates/delete.qtpl:19 | ||||
| func WriteDeleteAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line templates/delete.qtpl:19 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/delete.qtpl:19 | ||||
| 	StreamDeleteAskHTML(qw422016, rq, hyphaName, isOld) | ||||
| //line templates/delete.qtpl:19 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/delete.qtpl:19 | ||||
| } | ||||
|  | ||||
| //line templates/delete.qtpl:19 | ||||
| func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) string { | ||||
| //line templates/delete.qtpl:19 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/delete.qtpl:19 | ||||
| 	WriteDeleteAskHTML(qb422016, rq, hyphaName, isOld) | ||||
| //line templates/delete.qtpl:19 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/delete.qtpl:19 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/delete.qtpl:19 | ||||
| 	return qs422016 | ||||
| //line templates/delete.qtpl:19 | ||||
| } | ||||
|  | ||||
| //line templates/delete.qtpl:21 | ||||
| func streamcannotDeleteDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) { | ||||
| //line templates/delete.qtpl:21 | ||||
| 	qw422016.N().S(` | ||||
| 	<section> | ||||
| 		<h1>Cannot delete `) | ||||
| //line templates/delete.qtpl:23 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/delete.qtpl:23 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 		<p>This hypha does not exist.</p> | ||||
| 		<p><a href="/page/`) | ||||
| //line templates/delete.qtpl:25 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/delete.qtpl:25 | ||||
| 	qw422016.N().S(`">Go back</a></p> | ||||
| 	</section> | ||||
| `) | ||||
| //line templates/delete.qtpl:27 | ||||
| } | ||||
|  | ||||
| //line templates/delete.qtpl:27 | ||||
| func writecannotDeleteDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) { | ||||
| //line templates/delete.qtpl:27 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/delete.qtpl:27 | ||||
| 	streamcannotDeleteDueToNonExistence(qw422016, hyphaName) | ||||
| //line templates/delete.qtpl:27 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/delete.qtpl:27 | ||||
| } | ||||
|  | ||||
| //line templates/delete.qtpl:27 | ||||
| func cannotDeleteDueToNonExistence(hyphaName string) string { | ||||
| //line templates/delete.qtpl:27 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/delete.qtpl:27 | ||||
| 	writecannotDeleteDueToNonExistence(qb422016, hyphaName) | ||||
| //line templates/delete.qtpl:27 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/delete.qtpl:27 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/delete.qtpl:27 | ||||
| 	return qs422016 | ||||
| //line templates/delete.qtpl:27 | ||||
| } | ||||
| @@ -1,67 +0,0 @@ | ||||
| {% import "net/http" %} | ||||
| {% import "path" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/user" %} | ||||
|  | ||||
| {% func HistoryHTML(rq *http.Request, hyphaName, list string) %} | ||||
| {%= navHTML(rq, hyphaName, "history") %} | ||||
| <main> | ||||
| 	<article class="history"> | ||||
| 		<h1>History of {%s hyphaName %}</h1> | ||||
| 		{%s= list %} | ||||
| 	</article> | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) %} | ||||
| {%= navHTML(rq, hyphaName, "revision", revHash) %} | ||||
| <main> | ||||
| 	<article> | ||||
| 		<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p> | ||||
| 		{%s= naviTitle %} | ||||
| 		{%s= contents %} | ||||
| 	</article> | ||||
| 	<hr/> | ||||
| 	<aside> | ||||
| 		{%s= tree %} | ||||
| 	</aside> | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| If `contents` == "", a helpful message is shown instead. | ||||
| {% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) %} | ||||
| {%= navHTML(rq, hyphaName, "page") %} | ||||
| <main> | ||||
| 	<article> | ||||
| 		{%s= naviTitle %} | ||||
| 		{% if contents == "" %} | ||||
| 			<p>This hypha has no text. Why not <a href="/edit/{%s hyphaName %}">create it</a>?</p> | ||||
| 		{% else %} | ||||
| 			{%s= contents %} | ||||
| 		{% endif %} | ||||
| 	</article> | ||||
| 	<section class="prevnext"> | ||||
| 		{% if prevHyphaName != "" %} | ||||
| 		<a class="prevnext__el prevnext__prev" href="/page/{%s prevHyphaName %}" rel="prev">← {%s path.Base(prevHyphaName) %}</a> | ||||
| 		{% endif %} | ||||
| 		{% if nextHyphaName != "" %} | ||||
| 		<a class="prevnext__el prevnext__next" href="/page/{%s nextHyphaName %}" rel="next">{%s path.Base(nextHyphaName) %} →</a> | ||||
| 		{% endif %} | ||||
| 	</section> | ||||
| {% if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" %} | ||||
| 	<form action="/upload-binary/{%s hyphaName %}" | ||||
| 			method="post" enctype="multipart/form-data" | ||||
| 			class="upload-amnt"> | ||||
| 		{% if hasAmnt %} | ||||
| 			<a class="upload-amnt__unattach" href="/unattach-ask/{%s hyphaName %}">Unattach current attachment?</a> | ||||
| 		{% endif %} | ||||
| 		<label for="upload-binary__input">Upload a new attachment</label> | ||||
| 		<br> | ||||
| 		<input type="file" id="upload-binary__input" name="binary"/> | ||||
| 		<input type="submit"/> | ||||
| 	</form> | ||||
| {% endif %} | ||||
| 	<aside> | ||||
| 		{%s= tree %} | ||||
| 	</aside> | ||||
| </main> | ||||
| {% endfunc %} | ||||
| @@ -1,301 +0,0 @@ | ||||
| // Code generated by qtc from "http_readers.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/http_readers.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/http_readers.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| //line templates/http_readers.qtpl:2 | ||||
| import "path" | ||||
|  | ||||
| //line templates/http_readers.qtpl:3 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line templates/http_readers.qtpl:5 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/http_readers.qtpl:5 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/http_readers.qtpl:5 | ||||
| func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) { | ||||
| //line templates/http_readers.qtpl:5 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/http_readers.qtpl:6 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, "history") | ||||
| //line templates/http_readers.qtpl:6 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<article class="history"> | ||||
| 		<h1>History of `) | ||||
| //line templates/http_readers.qtpl:9 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_readers.qtpl:9 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:10 | ||||
| 	qw422016.N().S(list) | ||||
| //line templates/http_readers.qtpl:10 | ||||
| 	qw422016.N().S(` | ||||
| 	</article> | ||||
| </main> | ||||
| `) | ||||
| //line templates/http_readers.qtpl:13 | ||||
| } | ||||
|  | ||||
| //line templates/http_readers.qtpl:13 | ||||
| func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) { | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	StreamHistoryHTML(qw422016, rq, hyphaName, list) | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_readers.qtpl:13 | ||||
| } | ||||
|  | ||||
| //line templates/http_readers.qtpl:13 | ||||
| func HistoryHTML(rq *http.Request, hyphaName, list string) string { | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	WriteHistoryHTML(qb422016, rq, hyphaName, list) | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_readers.qtpl:13 | ||||
| 	return qs422016 | ||||
| //line templates/http_readers.qtpl:13 | ||||
| } | ||||
|  | ||||
| //line templates/http_readers.qtpl:15 | ||||
| func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) { | ||||
| //line templates/http_readers.qtpl:15 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/http_readers.qtpl:16 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, "revision", revHash) | ||||
| //line templates/http_readers.qtpl:16 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<article> | ||||
| 		<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:20 | ||||
| 	qw422016.N().S(naviTitle) | ||||
| //line templates/http_readers.qtpl:20 | ||||
| 	qw422016.N().S(` | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:21 | ||||
| 	qw422016.N().S(contents) | ||||
| //line templates/http_readers.qtpl:21 | ||||
| 	qw422016.N().S(` | ||||
| 	</article> | ||||
| 	<hr/> | ||||
| 	<aside> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:25 | ||||
| 	qw422016.N().S(tree) | ||||
| //line templates/http_readers.qtpl:25 | ||||
| 	qw422016.N().S(` | ||||
| 	</aside> | ||||
| </main> | ||||
| `) | ||||
| //line templates/http_readers.qtpl:28 | ||||
| } | ||||
|  | ||||
| //line templates/http_readers.qtpl:28 | ||||
| func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) { | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash) | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_readers.qtpl:28 | ||||
| } | ||||
|  | ||||
| //line templates/http_readers.qtpl:28 | ||||
| func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string { | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash) | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_readers.qtpl:28 | ||||
| 	return qs422016 | ||||
| //line templates/http_readers.qtpl:28 | ||||
| } | ||||
|  | ||||
| // If `contents` == "", a helpful message is shown instead. | ||||
|  | ||||
| //line templates/http_readers.qtpl:31 | ||||
| func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) { | ||||
| //line templates/http_readers.qtpl:31 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/http_readers.qtpl:32 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, "page") | ||||
| //line templates/http_readers.qtpl:32 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<article> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:35 | ||||
| 	qw422016.N().S(naviTitle) | ||||
| //line templates/http_readers.qtpl:35 | ||||
| 	qw422016.N().S(` | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:36 | ||||
| 	if contents == "" { | ||||
| //line templates/http_readers.qtpl:36 | ||||
| 		qw422016.N().S(` | ||||
| 			<p>This hypha has no text. Why not <a href="/edit/`) | ||||
| //line templates/http_readers.qtpl:37 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/http_readers.qtpl:37 | ||||
| 		qw422016.N().S(`">create it</a>?</p> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:38 | ||||
| 	} else { | ||||
| //line templates/http_readers.qtpl:38 | ||||
| 		qw422016.N().S(` | ||||
| 			`) | ||||
| //line templates/http_readers.qtpl:39 | ||||
| 		qw422016.N().S(contents) | ||||
| //line templates/http_readers.qtpl:39 | ||||
| 		qw422016.N().S(` | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:40 | ||||
| 	} | ||||
| //line templates/http_readers.qtpl:40 | ||||
| 	qw422016.N().S(` | ||||
| 	</article> | ||||
| 	<section class="prevnext"> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:43 | ||||
| 	if prevHyphaName != "" { | ||||
| //line templates/http_readers.qtpl:43 | ||||
| 		qw422016.N().S(` | ||||
| 		<a class="prevnext__el prevnext__prev" href="/page/`) | ||||
| //line templates/http_readers.qtpl:44 | ||||
| 		qw422016.E().S(prevHyphaName) | ||||
| //line templates/http_readers.qtpl:44 | ||||
| 		qw422016.N().S(`" rel="prev">← `) | ||||
| //line templates/http_readers.qtpl:44 | ||||
| 		qw422016.E().S(path.Base(prevHyphaName)) | ||||
| //line templates/http_readers.qtpl:44 | ||||
| 		qw422016.N().S(`</a> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:45 | ||||
| 	} | ||||
| //line templates/http_readers.qtpl:45 | ||||
| 	qw422016.N().S(` | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:46 | ||||
| 	if nextHyphaName != "" { | ||||
| //line templates/http_readers.qtpl:46 | ||||
| 		qw422016.N().S(` | ||||
| 		<a class="prevnext__el prevnext__next" href="/page/`) | ||||
| //line templates/http_readers.qtpl:47 | ||||
| 		qw422016.E().S(nextHyphaName) | ||||
| //line templates/http_readers.qtpl:47 | ||||
| 		qw422016.N().S(`" rel="next">`) | ||||
| //line templates/http_readers.qtpl:47 | ||||
| 		qw422016.E().S(path.Base(nextHyphaName)) | ||||
| //line templates/http_readers.qtpl:47 | ||||
| 		qw422016.N().S(` →</a> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:48 | ||||
| 	} | ||||
| //line templates/http_readers.qtpl:48 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| `) | ||||
| //line templates/http_readers.qtpl:50 | ||||
| 	if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" { | ||||
| //line templates/http_readers.qtpl:50 | ||||
| 		qw422016.N().S(` | ||||
| 	<form action="/upload-binary/`) | ||||
| //line templates/http_readers.qtpl:51 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/http_readers.qtpl:51 | ||||
| 		qw422016.N().S(`" | ||||
| 			method="post" enctype="multipart/form-data" | ||||
| 			class="upload-amnt"> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:54 | ||||
| 		if hasAmnt { | ||||
| //line templates/http_readers.qtpl:54 | ||||
| 			qw422016.N().S(` | ||||
| 			<a class="upload-amnt__unattach" href="/unattach-ask/`) | ||||
| //line templates/http_readers.qtpl:55 | ||||
| 			qw422016.E().S(hyphaName) | ||||
| //line templates/http_readers.qtpl:55 | ||||
| 			qw422016.N().S(`">Unattach current attachment?</a> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:56 | ||||
| 		} | ||||
| //line templates/http_readers.qtpl:56 | ||||
| 		qw422016.N().S(` | ||||
| 		<label for="upload-binary__input">Upload a new attachment</label> | ||||
| 		<br> | ||||
| 		<input type="file" id="upload-binary__input" name="binary"/> | ||||
| 		<input type="submit"/> | ||||
| 	</form> | ||||
| `) | ||||
| //line templates/http_readers.qtpl:62 | ||||
| 	} | ||||
| //line templates/http_readers.qtpl:62 | ||||
| 	qw422016.N().S(` | ||||
| 	<aside> | ||||
| 		`) | ||||
| //line templates/http_readers.qtpl:64 | ||||
| 	qw422016.N().S(tree) | ||||
| //line templates/http_readers.qtpl:64 | ||||
| 	qw422016.N().S(` | ||||
| 	</aside> | ||||
| </main> | ||||
| `) | ||||
| //line templates/http_readers.qtpl:67 | ||||
| } | ||||
|  | ||||
| //line templates/http_readers.qtpl:67 | ||||
| func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) { | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt) | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_readers.qtpl:67 | ||||
| } | ||||
|  | ||||
| //line templates/http_readers.qtpl:67 | ||||
| func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) string { | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt) | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_readers.qtpl:67 | ||||
| 	return qs422016 | ||||
| //line templates/http_readers.qtpl:67 | ||||
| } | ||||
| @@ -1,78 +0,0 @@ | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/user" %} | ||||
|  | ||||
| {% func BaseHTML(title, body string, u *user.User, headElements ...string) %} | ||||
| <!doctype html> | ||||
| <html> | ||||
| 	<head> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 		<link rel="stylesheet" type="text/css" href="/static/common.css"> | ||||
| 		<title>{%s title %}</title> | ||||
| 		{% for _, el := range headElements %}{%s= el %}{% endfor %} | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<header> | ||||
| 			<nav class="header-links"> | ||||
| 				<ul class="header-links__list"> | ||||
| 					{%- for _, link := range util.HeaderLinks -%} | ||||
| 					<li class="header-links__entry"><a class="header-links__link" href="{%s link.Href %}">{%s link.Display %}</a></li> | ||||
| 					{%- endfor -%} | ||||
| 					{%s= userMenuHTML(u) %} | ||||
| 				</ul> | ||||
| 			</nav> | ||||
| 		</header> | ||||
| 		{%s= body %} | ||||
| 	</body> | ||||
| </html> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func HyphaListHTML(tbody string, pageCount int) %} | ||||
| <main> | ||||
| 	<h1>List of hyphae</h1> | ||||
| 	<p>This wiki has {%d pageCount %} hyphae.</p> | ||||
| 	<table> | ||||
| 		<thead> | ||||
| 			<tr> | ||||
| 				<th>Full name</th> | ||||
| 				<th>Binary part type</th> | ||||
| 			</tr> | ||||
| 		</thead> | ||||
| 		<tbody> | ||||
| 			{%s= tbody %} | ||||
| 		</tbody> | ||||
| 	</table> | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) %} | ||||
| 			<tr> | ||||
| 				<td><a href="/page/{%s hyphaName %}">{%s hyphaName %}</a></td> | ||||
| 			{% if binaryPresent %} | ||||
| 				<td>{%s binaryMime %}</td> | ||||
| 			{% else %} | ||||
| 				<td></td> | ||||
| 			{% endif %} | ||||
| 			</tr> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func AboutHTML() %} | ||||
| <main> | ||||
| 	<section> | ||||
| 		<h1>About {%s util.SiteName %}</h1> | ||||
| 		<ul> | ||||
| 			<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.12 indev</li> | ||||
| 		{%- if user.AuthUsed -%} | ||||
| 			<li><b>User count:</b> {%d user.Count() %}</li> | ||||
| 			<li><b>Home page:</b> <a href="/">{%s util.HomePage %}</a></li> | ||||
| 			<li><b>Administrators:</b> {%- for i, username := range user.ListUsersWithGroup("admin") -%} | ||||
| 				{%- if i > 0 -%}<span aria-hidden="true">, </span> | ||||
| 				{%- endif -%} | ||||
| 				<a href="/page/{%s util.UserHypha %}/{%s username %}">{%s username %}</a>{%- endfor -%}</li> | ||||
| 		{%- else -%} | ||||
| 			<li>This wiki does not use authorization</li> | ||||
| 		{%- endif -%} | ||||
| 		</ul> | ||||
| 		<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p> | ||||
| 	</section> | ||||
| </main> | ||||
| {% endfunc %} | ||||
| @@ -1,335 +0,0 @@ | ||||
| // Code generated by qtc from "http_stuff.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/http_stuff.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/http_stuff.qtpl:1 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| //line templates/http_stuff.qtpl:2 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line templates/http_stuff.qtpl:4 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/http_stuff.qtpl:4 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/http_stuff.qtpl:4 | ||||
| func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) { | ||||
| //line templates/http_stuff.qtpl:4 | ||||
| 	qw422016.N().S(` | ||||
| <!doctype html> | ||||
| <html> | ||||
| 	<head> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 		<link rel="stylesheet" type="text/css" href="/static/common.css"> | ||||
| 		<title>`) | ||||
| //line templates/http_stuff.qtpl:10 | ||||
| 	qw422016.E().S(title) | ||||
| //line templates/http_stuff.qtpl:10 | ||||
| 	qw422016.N().S(`</title> | ||||
| 		`) | ||||
| //line templates/http_stuff.qtpl:11 | ||||
| 	for _, el := range headElements { | ||||
| //line templates/http_stuff.qtpl:11 | ||||
| 		qw422016.N().S(el) | ||||
| //line templates/http_stuff.qtpl:11 | ||||
| 	} | ||||
| //line templates/http_stuff.qtpl:11 | ||||
| 	qw422016.N().S(` | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<header> | ||||
| 			<nav class="header-links"> | ||||
| 				<ul class="header-links__list"> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:17 | ||||
| 	for _, link := range util.HeaderLinks { | ||||
| //line templates/http_stuff.qtpl:17 | ||||
| 		qw422016.N().S(`					<li class="header-links__entry"><a class="header-links__link" href="`) | ||||
| //line templates/http_stuff.qtpl:18 | ||||
| 		qw422016.E().S(link.Href) | ||||
| //line templates/http_stuff.qtpl:18 | ||||
| 		qw422016.N().S(`">`) | ||||
| //line templates/http_stuff.qtpl:18 | ||||
| 		qw422016.E().S(link.Display) | ||||
| //line templates/http_stuff.qtpl:18 | ||||
| 		qw422016.N().S(`</a></li> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:19 | ||||
| 	} | ||||
| //line templates/http_stuff.qtpl:19 | ||||
| 	qw422016.N().S(`					`) | ||||
| //line templates/http_stuff.qtpl:20 | ||||
| 	qw422016.N().S(userMenuHTML(u)) | ||||
| //line templates/http_stuff.qtpl:20 | ||||
| 	qw422016.N().S(` | ||||
| 				</ul> | ||||
| 			</nav> | ||||
| 		</header> | ||||
| 		`) | ||||
| //line templates/http_stuff.qtpl:24 | ||||
| 	qw422016.N().S(body) | ||||
| //line templates/http_stuff.qtpl:24 | ||||
| 	qw422016.N().S(` | ||||
| 	</body> | ||||
| </html> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) { | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	StreamBaseHTML(qw422016, title, body, u, headElements...) | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| func BaseHTML(title, body string, u *user.User, headElements ...string) string { | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	WriteBaseHTML(qb422016, title, body, u, headElements...) | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| 	return qs422016 | ||||
| //line templates/http_stuff.qtpl:27 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:29 | ||||
| func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) { | ||||
| //line templates/http_stuff.qtpl:29 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<h1>List of hyphae</h1> | ||||
| 	<p>This wiki has `) | ||||
| //line templates/http_stuff.qtpl:32 | ||||
| 	qw422016.N().D(pageCount) | ||||
| //line templates/http_stuff.qtpl:32 | ||||
| 	qw422016.N().S(` hyphae.</p> | ||||
| 	<table> | ||||
| 		<thead> | ||||
| 			<tr> | ||||
| 				<th>Full name</th> | ||||
| 				<th>Binary part type</th> | ||||
| 			</tr> | ||||
| 		</thead> | ||||
| 		<tbody> | ||||
| 			`) | ||||
| //line templates/http_stuff.qtpl:41 | ||||
| 	qw422016.N().S(tbody) | ||||
| //line templates/http_stuff.qtpl:41 | ||||
| 	qw422016.N().S(` | ||||
| 		</tbody> | ||||
| 	</table> | ||||
| </main> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| func WriteHyphaListHTML(qq422016 qtio422016.Writer, tbody string, pageCount int) { | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	StreamHyphaListHTML(qw422016, tbody, pageCount) | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| func HyphaListHTML(tbody string, pageCount int) string { | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	WriteHyphaListHTML(qb422016, tbody, pageCount) | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| 	return qs422016 | ||||
| //line templates/http_stuff.qtpl:45 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:47 | ||||
| func StreamHyphaListRowHTML(qw422016 *qt422016.Writer, hyphaName, binaryMime string, binaryPresent bool) { | ||||
| //line templates/http_stuff.qtpl:47 | ||||
| 	qw422016.N().S(` | ||||
| 			<tr> | ||||
| 				<td><a href="/page/`) | ||||
| //line templates/http_stuff.qtpl:49 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_stuff.qtpl:49 | ||||
| 	qw422016.N().S(`">`) | ||||
| //line templates/http_stuff.qtpl:49 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_stuff.qtpl:49 | ||||
| 	qw422016.N().S(`</a></td> | ||||
| 			`) | ||||
| //line templates/http_stuff.qtpl:50 | ||||
| 	if binaryPresent { | ||||
| //line templates/http_stuff.qtpl:50 | ||||
| 		qw422016.N().S(` | ||||
| 				<td>`) | ||||
| //line templates/http_stuff.qtpl:51 | ||||
| 		qw422016.E().S(binaryMime) | ||||
| //line templates/http_stuff.qtpl:51 | ||||
| 		qw422016.N().S(`</td> | ||||
| 			`) | ||||
| //line templates/http_stuff.qtpl:52 | ||||
| 	} else { | ||||
| //line templates/http_stuff.qtpl:52 | ||||
| 		qw422016.N().S(` | ||||
| 				<td></td> | ||||
| 			`) | ||||
| //line templates/http_stuff.qtpl:54 | ||||
| 	} | ||||
| //line templates/http_stuff.qtpl:54 | ||||
| 	qw422016.N().S(` | ||||
| 			</tr> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| func WriteHyphaListRowHTML(qq422016 qtio422016.Writer, hyphaName, binaryMime string, binaryPresent bool) { | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	StreamHyphaListRowHTML(qw422016, hyphaName, binaryMime, binaryPresent) | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) string { | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	WriteHyphaListRowHTML(qb422016, hyphaName, binaryMime, binaryPresent) | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| 	return qs422016 | ||||
| //line templates/http_stuff.qtpl:56 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:58 | ||||
| func StreamAboutHTML(qw422016 *qt422016.Writer) { | ||||
| //line templates/http_stuff.qtpl:58 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| 	<section> | ||||
| 		<h1>About `) | ||||
| //line templates/http_stuff.qtpl:61 | ||||
| 	qw422016.E().S(util.SiteName) | ||||
| //line templates/http_stuff.qtpl:61 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 		<ul> | ||||
| 			<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.12 indev</li> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:64 | ||||
| 	if user.AuthUsed { | ||||
| //line templates/http_stuff.qtpl:64 | ||||
| 		qw422016.N().S(`			<li><b>User count:</b> `) | ||||
| //line templates/http_stuff.qtpl:65 | ||||
| 		qw422016.N().D(user.Count()) | ||||
| //line templates/http_stuff.qtpl:65 | ||||
| 		qw422016.N().S(`</li> | ||||
| 			<li><b>Home page:</b> <a href="/">`) | ||||
| //line templates/http_stuff.qtpl:66 | ||||
| 		qw422016.E().S(util.HomePage) | ||||
| //line templates/http_stuff.qtpl:66 | ||||
| 		qw422016.N().S(`</a></li> | ||||
| 			<li><b>Administrators:</b>`) | ||||
| //line templates/http_stuff.qtpl:67 | ||||
| 		for i, username := range user.ListUsersWithGroup("admin") { | ||||
| //line templates/http_stuff.qtpl:68 | ||||
| 			if i > 0 { | ||||
| //line templates/http_stuff.qtpl:68 | ||||
| 				qw422016.N().S(`<span aria-hidden="true">, </span> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:69 | ||||
| 			} | ||||
| //line templates/http_stuff.qtpl:69 | ||||
| 			qw422016.N().S(`				<a href="/page/`) | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 			qw422016.E().S(util.UserHypha) | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 			qw422016.E().S(username) | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 			qw422016.E().S(username) | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 			qw422016.N().S(`</a>`) | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 		} | ||||
| //line templates/http_stuff.qtpl:70 | ||||
| 		qw422016.N().S(`</li> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:71 | ||||
| 	} else { | ||||
| //line templates/http_stuff.qtpl:71 | ||||
| 		qw422016.N().S(`			<li>This wiki does not use authorization</li> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:73 | ||||
| 	} | ||||
| //line templates/http_stuff.qtpl:73 | ||||
| 	qw422016.N().S(`		</ul> | ||||
| 		<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p> | ||||
| 	</section> | ||||
| </main> | ||||
| `) | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| func WriteAboutHTML(qq422016 qtio422016.Writer) { | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	StreamAboutHTML(qw422016) | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| } | ||||
|  | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| func AboutHTML() string { | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	WriteAboutHTML(qb422016) | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| 	return qs422016 | ||||
| //line templates/http_stuff.qtpl:78 | ||||
| } | ||||
| @@ -1,159 +0,0 @@ | ||||
| // Code generated by qtc from "recent_changes.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/recent_changes.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/recent_changes.qtpl:1 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/recent_changes.qtpl:1 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/recent_changes.qtpl:1 | ||||
| func StreamRecentChangesHTML(qw422016 *qt422016.Writer, changes []string, n int) { | ||||
| //line templates/recent_changes.qtpl:1 | ||||
| 	qw422016.N().S(` | ||||
| <main class="recent-changes"> | ||||
| 	<h1>Recent Changes</h1> | ||||
| 	<p><a href="/">← Back</a></p> | ||||
|  | ||||
| 	<nav class="recent-changes__count"> | ||||
| 		See  | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:8 | ||||
| 	for _, m := range []int{20, 0, 50, 0, 100} { | ||||
| //line templates/recent_changes.qtpl:8 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:9 | ||||
| 		switch m { | ||||
| //line templates/recent_changes.qtpl:10 | ||||
| 		case 0: | ||||
| //line templates/recent_changes.qtpl:10 | ||||
| 			qw422016.N().S(` | ||||
| 		<span aria-hidden="true">|</span> | ||||
| 		`) | ||||
| //line templates/recent_changes.qtpl:12 | ||||
| 		case n: | ||||
| //line templates/recent_changes.qtpl:12 | ||||
| 			qw422016.N().S(` | ||||
| 		<b>`) | ||||
| //line templates/recent_changes.qtpl:13 | ||||
| 			qw422016.N().D(n) | ||||
| //line templates/recent_changes.qtpl:13 | ||||
| 			qw422016.N().S(`</b> | ||||
| 		`) | ||||
| //line templates/recent_changes.qtpl:14 | ||||
| 		default: | ||||
| //line templates/recent_changes.qtpl:14 | ||||
| 			qw422016.N().S(` | ||||
| 		<a href="/recent-changes/`) | ||||
| //line templates/recent_changes.qtpl:15 | ||||
| 			qw422016.N().D(m) | ||||
| //line templates/recent_changes.qtpl:15 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line templates/recent_changes.qtpl:15 | ||||
| 			qw422016.N().D(m) | ||||
| //line templates/recent_changes.qtpl:15 | ||||
| 			qw422016.N().S(`</a> | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:16 | ||||
| 		} | ||||
| //line templates/recent_changes.qtpl:16 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:17 | ||||
| 	} | ||||
| //line templates/recent_changes.qtpl:17 | ||||
| 	qw422016.N().S(` | ||||
| 		recent changes | ||||
| 	</nav> | ||||
|  | ||||
| 	<p><img class="icon" width="20" height="20" src="https://upload.wikimedia.org/wikipedia/commons/4/46/Generic_Feed-icon.svg">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p> | ||||
|  | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:28 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	<section class="recent-changes__list" role="feed"> | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:31 | ||||
| 	if len(changes) == 0 { | ||||
| //line templates/recent_changes.qtpl:31 | ||||
| 		qw422016.N().S(` | ||||
| 		<p>Could not find any recent changes.</p> | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:33 | ||||
| 	} else { | ||||
| //line templates/recent_changes.qtpl:33 | ||||
| 		qw422016.N().S(` | ||||
| 		`) | ||||
| //line templates/recent_changes.qtpl:34 | ||||
| 		for i, entry := range changes { | ||||
| //line templates/recent_changes.qtpl:34 | ||||
| 			qw422016.N().S(` | ||||
| 		<ul class="recent-changes__entry rc-entry" role="article" | ||||
| 		    aria-setsize="`) | ||||
| //line templates/recent_changes.qtpl:36 | ||||
| 			qw422016.N().D(n) | ||||
| //line templates/recent_changes.qtpl:36 | ||||
| 			qw422016.N().S(`" aria-posinset="`) | ||||
| //line templates/recent_changes.qtpl:36 | ||||
| 			qw422016.N().D(i) | ||||
| //line templates/recent_changes.qtpl:36 | ||||
| 			qw422016.N().S(`"> | ||||
| 			 `) | ||||
| //line templates/recent_changes.qtpl:37 | ||||
| 			qw422016.N().S(entry) | ||||
| //line templates/recent_changes.qtpl:37 | ||||
| 			qw422016.N().S(` | ||||
| 		</ul> | ||||
| 		`) | ||||
| //line templates/recent_changes.qtpl:39 | ||||
| 		} | ||||
| //line templates/recent_changes.qtpl:39 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line templates/recent_changes.qtpl:40 | ||||
| 	} | ||||
| //line templates/recent_changes.qtpl:40 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| </main> | ||||
| `) | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| } | ||||
|  | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| func WriteRecentChangesHTML(qq422016 qtio422016.Writer, changes []string, n int) { | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	StreamRecentChangesHTML(qw422016, changes, n) | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| } | ||||
|  | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| func RecentChangesHTML(changes []string, n int) string { | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	WriteRecentChangesHTML(qb422016, changes, n) | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| 	return qs422016 | ||||
| //line templates/recent_changes.qtpl:43 | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| {% import "net/http" %} | ||||
| This dialog is to be shown to a user when they try to rename a hypha. | ||||
| {% func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) %} | ||||
| {%= navHTML(rq, hyphaName, "rename-ask") %} | ||||
| <main> | ||||
| {%- if isOld -%} | ||||
| 	<section> | ||||
| 		<h1>Rename {%s hyphaName %}</h1> | ||||
| 		<form action="/rename-confirm/{%s hyphaName %}" method="post" enctype="multipart/form-data"> | ||||
| 			<fieldset> | ||||
| 				<legend>New name</legend> | ||||
| 				<input type="text" value="{%s hyphaName %}" required autofocus id="new-name" name="new-name"/> | ||||
| 			</fieldset> | ||||
|  | ||||
| 			<fieldset> | ||||
| 				<legend>Settings</legend> | ||||
| 				<input type="checkbox" id="recursive" name="recursive" value="true" checked/> | ||||
| 				<label for="recursive">Keep subhyphae</label> | ||||
| 			</fieldset> | ||||
|  | ||||
| 			<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p> | ||||
| 			<input type="submit"/> | ||||
| 		</form> | ||||
| 	</section> | ||||
| {%- else -%} | ||||
| 	{%= cannotRenameDueToNonExistence(hyphaName) %} | ||||
| {%- endif -%} | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func cannotRenameDueToNonExistence(hyphaName string) %} | ||||
| 	<section> | ||||
| 		<h1>Cannot rename {%s hyphaName %}</h1> | ||||
| 		<p>This hypha does not exist.</p> | ||||
| 		<p><a href="/page/{%s hyphaName %}">Go back</a></p> | ||||
| 	</section> | ||||
| {% endfunc %} | ||||
| @@ -1,158 +0,0 @@ | ||||
| // Code generated by qtc from "rename.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/rename.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/rename.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| // This dialog is to be shown to a user when they try to rename a hypha. | ||||
|  | ||||
| //line templates/rename.qtpl:3 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/rename.qtpl:3 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/rename.qtpl:3 | ||||
| func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line templates/rename.qtpl:3 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/rename.qtpl:4 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, "rename-ask") | ||||
| //line templates/rename.qtpl:4 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| `) | ||||
| //line templates/rename.qtpl:6 | ||||
| 	if isOld { | ||||
| //line templates/rename.qtpl:6 | ||||
| 		qw422016.N().S(`	<section> | ||||
| 		<h1>Rename `) | ||||
| //line templates/rename.qtpl:8 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/rename.qtpl:8 | ||||
| 		qw422016.N().S(`</h1> | ||||
| 		<form action="/rename-confirm/`) | ||||
| //line templates/rename.qtpl:9 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/rename.qtpl:9 | ||||
| 		qw422016.N().S(`" method="post" enctype="multipart/form-data"> | ||||
| 			<fieldset> | ||||
| 				<legend>New name</legend> | ||||
| 				<input type="text" value="`) | ||||
| //line templates/rename.qtpl:12 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/rename.qtpl:12 | ||||
| 		qw422016.N().S(`" required autofocus id="new-name" name="new-name"/> | ||||
| 			</fieldset> | ||||
|  | ||||
| 			<fieldset> | ||||
| 				<legend>Settings</legend> | ||||
| 				<input type="checkbox" id="recursive" name="recursive" value="true" checked/> | ||||
| 				<label for="recursive">Keep subhyphae</label> | ||||
| 			</fieldset> | ||||
|  | ||||
| 			<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p> | ||||
| 			<input type="submit"/> | ||||
| 		</form> | ||||
| 	</section> | ||||
| `) | ||||
| //line templates/rename.qtpl:25 | ||||
| 	} else { | ||||
| //line templates/rename.qtpl:25 | ||||
| 		qw422016.N().S(`	`) | ||||
| //line templates/rename.qtpl:26 | ||||
| 		streamcannotRenameDueToNonExistence(qw422016, hyphaName) | ||||
| //line templates/rename.qtpl:26 | ||||
| 		qw422016.N().S(` | ||||
| `) | ||||
| //line templates/rename.qtpl:27 | ||||
| 	} | ||||
| //line templates/rename.qtpl:27 | ||||
| 	qw422016.N().S(`</main> | ||||
| `) | ||||
| //line templates/rename.qtpl:29 | ||||
| } | ||||
|  | ||||
| //line templates/rename.qtpl:29 | ||||
| func WriteRenameAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line templates/rename.qtpl:29 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/rename.qtpl:29 | ||||
| 	StreamRenameAskHTML(qw422016, rq, hyphaName, isOld) | ||||
| //line templates/rename.qtpl:29 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/rename.qtpl:29 | ||||
| } | ||||
|  | ||||
| //line templates/rename.qtpl:29 | ||||
| func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) string { | ||||
| //line templates/rename.qtpl:29 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/rename.qtpl:29 | ||||
| 	WriteRenameAskHTML(qb422016, rq, hyphaName, isOld) | ||||
| //line templates/rename.qtpl:29 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/rename.qtpl:29 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/rename.qtpl:29 | ||||
| 	return qs422016 | ||||
| //line templates/rename.qtpl:29 | ||||
| } | ||||
|  | ||||
| //line templates/rename.qtpl:31 | ||||
| func streamcannotRenameDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) { | ||||
| //line templates/rename.qtpl:31 | ||||
| 	qw422016.N().S(` | ||||
| 	<section> | ||||
| 		<h1>Cannot rename `) | ||||
| //line templates/rename.qtpl:33 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/rename.qtpl:33 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 		<p>This hypha does not exist.</p> | ||||
| 		<p><a href="/page/`) | ||||
| //line templates/rename.qtpl:35 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/rename.qtpl:35 | ||||
| 	qw422016.N().S(`">Go back</a></p> | ||||
| 	</section> | ||||
| `) | ||||
| //line templates/rename.qtpl:37 | ||||
| } | ||||
|  | ||||
| //line templates/rename.qtpl:37 | ||||
| func writecannotRenameDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) { | ||||
| //line templates/rename.qtpl:37 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/rename.qtpl:37 | ||||
| 	streamcannotRenameDueToNonExistence(qw422016, hyphaName) | ||||
| //line templates/rename.qtpl:37 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/rename.qtpl:37 | ||||
| } | ||||
|  | ||||
| //line templates/rename.qtpl:37 | ||||
| func cannotRenameDueToNonExistence(hyphaName string) string { | ||||
| //line templates/rename.qtpl:37 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/rename.qtpl:37 | ||||
| 	writecannotRenameDueToNonExistence(qb422016, hyphaName) | ||||
| //line templates/rename.qtpl:37 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/rename.qtpl:37 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/rename.qtpl:37 | ||||
| 	return qs422016 | ||||
| //line templates/rename.qtpl:37 | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| {% import "net/http" %} | ||||
| {% func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) %} | ||||
| <main> | ||||
| {%= navHTML(rq, hyphaName, "unattach-ask") %} | ||||
| {%- if isOld -%} | ||||
| 	<section> | ||||
| 		<h1>Unattach {%s hyphaName %}?</h1> | ||||
| 		<p>Do you really want to unattach hypha <em>{%s hyphaName %}</em>?</p> | ||||
| 		<p><a href="/unattach-confirm/{%s hyphaName %}"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/page/{%s hyphaName %}">Cancel</a></p> | ||||
| 	</section> | ||||
| {%- else -%} | ||||
| 	{%= cannotUnattachDueToNonExistence(hyphaName) %} | ||||
| {%- endif -%} | ||||
| </main> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func cannotUnattachDueToNonExistence(hyphaName string) %} | ||||
| 	<section> | ||||
| 		<h1>Cannot unattach {%s hyphaName %}</h1> | ||||
| 		<p>This hypha does not exist.</p> | ||||
| 		<p><a href="/page/{%s hyphaName %}">Go back</a></p> | ||||
| 	</section> | ||||
| {% endfunc %} | ||||
| @@ -1,148 +0,0 @@ | ||||
| // Code generated by qtc from "unattach.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line templates/unattach.qtpl:1 | ||||
| package templates | ||||
|  | ||||
| //line templates/unattach.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| //line templates/unattach.qtpl:2 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line templates/unattach.qtpl:2 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line templates/unattach.qtpl:2 | ||||
| func StreamUnattachAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line templates/unattach.qtpl:2 | ||||
| 	qw422016.N().S(` | ||||
| <main> | ||||
| `) | ||||
| //line templates/unattach.qtpl:4 | ||||
| 	streamnavHTML(qw422016, rq, hyphaName, "unattach-ask") | ||||
| //line templates/unattach.qtpl:4 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/unattach.qtpl:5 | ||||
| 	if isOld { | ||||
| //line templates/unattach.qtpl:5 | ||||
| 		qw422016.N().S(`	<section> | ||||
| 		<h1>Unattach `) | ||||
| //line templates/unattach.qtpl:7 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/unattach.qtpl:7 | ||||
| 		qw422016.N().S(`?</h1> | ||||
| 		<p>Do you really want to unattach hypha <em>`) | ||||
| //line templates/unattach.qtpl:8 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/unattach.qtpl:8 | ||||
| 		qw422016.N().S(`</em>?</p> | ||||
| 		<p><a href="/unattach-confirm/`) | ||||
| //line templates/unattach.qtpl:9 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/unattach.qtpl:9 | ||||
| 		qw422016.N().S(`"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/page/`) | ||||
| //line templates/unattach.qtpl:10 | ||||
| 		qw422016.E().S(hyphaName) | ||||
| //line templates/unattach.qtpl:10 | ||||
| 		qw422016.N().S(`">Cancel</a></p> | ||||
| 	</section> | ||||
| `) | ||||
| //line templates/unattach.qtpl:12 | ||||
| 	} else { | ||||
| //line templates/unattach.qtpl:12 | ||||
| 		qw422016.N().S(`	`) | ||||
| //line templates/unattach.qtpl:13 | ||||
| 		streamcannotUnattachDueToNonExistence(qw422016, hyphaName) | ||||
| //line templates/unattach.qtpl:13 | ||||
| 		qw422016.N().S(` | ||||
| `) | ||||
| //line templates/unattach.qtpl:14 | ||||
| 	} | ||||
| //line templates/unattach.qtpl:14 | ||||
| 	qw422016.N().S(`</main> | ||||
| `) | ||||
| //line templates/unattach.qtpl:16 | ||||
| } | ||||
|  | ||||
| //line templates/unattach.qtpl:16 | ||||
| func WriteUnattachAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	StreamUnattachAskHTML(qw422016, rq, hyphaName, isOld) | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/unattach.qtpl:16 | ||||
| } | ||||
|  | ||||
| //line templates/unattach.qtpl:16 | ||||
| func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) string { | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	WriteUnattachAskHTML(qb422016, rq, hyphaName, isOld) | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/unattach.qtpl:16 | ||||
| 	return qs422016 | ||||
| //line templates/unattach.qtpl:16 | ||||
| } | ||||
|  | ||||
| //line templates/unattach.qtpl:18 | ||||
| func streamcannotUnattachDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) { | ||||
| //line templates/unattach.qtpl:18 | ||||
| 	qw422016.N().S(` | ||||
| 	<section> | ||||
| 		<h1>Cannot unattach `) | ||||
| //line templates/unattach.qtpl:20 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/unattach.qtpl:20 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 		<p>This hypha does not exist.</p> | ||||
| 		<p><a href="/page/`) | ||||
| //line templates/unattach.qtpl:22 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/unattach.qtpl:22 | ||||
| 	qw422016.N().S(`">Go back</a></p> | ||||
| 	</section> | ||||
| `) | ||||
| //line templates/unattach.qtpl:24 | ||||
| } | ||||
|  | ||||
| //line templates/unattach.qtpl:24 | ||||
| func writecannotUnattachDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) { | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	streamcannotUnattachDueToNonExistence(qw422016, hyphaName) | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/unattach.qtpl:24 | ||||
| } | ||||
|  | ||||
| //line templates/unattach.qtpl:24 | ||||
| func cannotUnattachDueToNonExistence(hyphaName string) string { | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	writecannotUnattachDueToNonExistence(qb422016, hyphaName) | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/unattach.qtpl:24 | ||||
| 	return qs422016 | ||||
| //line templates/unattach.qtpl:24 | ||||
| } | ||||
							
								
								
									
										210
									
								
								tree/tree.go
									
									
									
									
									
								
							
							
						
						| @@ -6,103 +6,135 @@ import ( | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bouncepaw/mycorrhiza/hyphae" | ||||
| 	"github.com/bouncepaw/mycorrhiza/util" | ||||
| ) | ||||
|  | ||||
| // If Name == "", the tree is empty. | ||||
| type tree struct { | ||||
| 	name          string | ||||
| 	exists        bool | ||||
| 	prevSibling   string | ||||
| 	nextSibling   string | ||||
| 	siblings      []string | ||||
| 	descendants   []*tree | ||||
| 	root          bool | ||||
| 	hyphaIterator func(func(string)) | ||||
| type sibling struct { | ||||
| 	name        string | ||||
| 	hasChildren bool | ||||
| } | ||||
|  | ||||
| func (s *sibling) checkThisChild(hyphaName string) { | ||||
| 	if !s.hasChildren && path.Dir(hyphaName) == s.name { | ||||
| 		s.hasChildren = true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *sibling) asHTML() string { | ||||
| 	class := "navitree__entry navitree__sibling" | ||||
| 	if s.hasChildren { | ||||
| 		class += " navitree__sibling_fertile navitree__entry_fertile" | ||||
| 	} else { | ||||
| 		class += " navitree__sibling_infertile navitree__entry_infertile" | ||||
| 	} | ||||
| 	return fmt.Sprintf( | ||||
| 		`<li class="%s"><a class="navitree__link" href="/hypha/%s">%s</a></li>`, | ||||
| 		class, | ||||
| 		s.name, | ||||
| 		util.BeautifulName(path.Base(s.name)), | ||||
| 	) | ||||
| } | ||||
|  | ||||
| type mainFamilyMember struct { | ||||
| 	name     string | ||||
| 	children []*mainFamilyMember | ||||
| } | ||||
|  | ||||
| func (m *mainFamilyMember) checkThisChild(hyphaName string) (adopted bool) { | ||||
| 	if path.Dir(hyphaName) == m.name { | ||||
| 		m.children = append(m.children, &mainFamilyMember{ | ||||
| 			name:     hyphaName, | ||||
| 			children: make([]*mainFamilyMember, 0), | ||||
| 		}) | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (m *mainFamilyMember) asHTML() string { | ||||
| 	if len(m.children) == 0 { | ||||
| 		return fmt.Sprintf(`<li class="subhyphae__entry"><a class="subhyphae__link" href="/hypha/%s">%s</a></li>`, m.name, util.BeautifulName(path.Base(m.name))) | ||||
| 	} | ||||
| 	sort.Slice(m.children, func(i, j int) bool { | ||||
| 		return m.children[i].name < m.children[j].name | ||||
| 	}) | ||||
| 	html := fmt.Sprintf(`<li class="subhyphae__entry"><a class="subhyphae__link" href="/hypha/%s">%s</a><ul>`, m.name, util.BeautifulName(path.Base(m.name))) | ||||
| 	for _, child := range m.children { | ||||
| 		html += child.asHTML() | ||||
| 	} | ||||
| 	return html + `</li></ul></li>` | ||||
| } | ||||
|  | ||||
| func mainFamilyFromPool(hyphaName string, subhyphaePool map[string]bool) *mainFamilyMember { | ||||
| 	var ( | ||||
| 		nestLevel = strings.Count(hyphaName, "/") | ||||
| 		adopted   = make([]*mainFamilyMember, 0) | ||||
| 	) | ||||
| 	for subhyphaName, _ := range subhyphaePool { | ||||
| 		subnestLevel := strings.Count(subhyphaName, "/") | ||||
| 		if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName { | ||||
| 			delete(subhyphaePool, subhyphaName) | ||||
| 			adopted = append(adopted, mainFamilyFromPool(subhyphaName, subhyphaePool)) | ||||
| 		} | ||||
| 	} | ||||
| 	return &mainFamilyMember{name: hyphaName, children: adopted} | ||||
| } | ||||
|  | ||||
| func subhyphaeMatrix(hyphaName string, subhyphaePool map[string]bool) string { | ||||
| 	var html string | ||||
| 	children := mainFamilyFromPool(hyphaName, subhyphaePool).children | ||||
| 	sort.Slice(children, func(i, j int) bool { | ||||
| 		return children[i].name < children[j].name | ||||
| 	}) | ||||
| 	for _, child := range children { | ||||
| 		html += child.asHTML() | ||||
| 	} | ||||
| 	return html | ||||
| } | ||||
|  | ||||
| // Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any. | ||||
| func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next string) { | ||||
| 	t := &tree{name: hyphaName, root: true, hyphaIterator: hyphaIterator} | ||||
| 	t.fill() | ||||
| 	return t.asHtml(), util.BeautifulName(t.prevSibling), util.BeautifulName(t.nextSibling) | ||||
| } | ||||
|  | ||||
| // subtree adds a descendant tree to `t` and returns that tree. | ||||
| func (t *tree) fork(descendantName string) *tree { | ||||
| 	subt := &tree{ | ||||
| 		name:          descendantName, | ||||
| 		root:          false, | ||||
| 		hyphaIterator: t.hyphaIterator, | ||||
| 	} | ||||
| 	t.descendants = append(t.descendants, subt) | ||||
| 	return subt | ||||
| } | ||||
|  | ||||
| // Compare current prev next hyphae and decide if any of them should be set to `name2`. | ||||
| func (t *tree) prevNextDetermine(name2 string) { | ||||
| 	if name2 < t.name && (name2 > t.prevSibling || t.prevSibling == "") { | ||||
| 		t.prevSibling = name2 | ||||
| 	} else if name2 > t.name && (name2 < t.nextSibling || t.nextSibling == "") { | ||||
| 		t.nextSibling = name2 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Compares names and does something with them, may generate a subtree. | ||||
| func (t *tree) compareNamesAndAppend(name2 string) { | ||||
| 	switch { | ||||
| 	case t.name == name2: | ||||
| 		t.exists = true | ||||
| 	case t.root && path.Dir(t.name) == path.Dir(name2): | ||||
| 		t.prevNextDetermine(name2) | ||||
| 		t.siblings = append(t.siblings, name2) | ||||
| 	case t.name == path.Dir(name2): | ||||
| 		t.fork(name2).fill() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Fills t.siblings and t.descendants, sorts them and does the same to the descendants. | ||||
| func (t *tree) fill() { | ||||
| 	t.hyphaIterator(func(hyphaName string) { | ||||
| 		t.compareNamesAndAppend(hyphaName) | ||||
| 	}) | ||||
| 	sort.Strings(t.siblings) | ||||
| 	sort.Slice(t.descendants, func(i, j int) bool { | ||||
| 		return t.descendants[i].name < t.descendants[j].name | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // asHtml returns HTML representation of a tree. | ||||
| // It applies itself recursively on the tree's children. | ||||
| func (t *tree) asHtml() (html string) { | ||||
| 	if t.root { | ||||
| 		html += navitreeEntry(t.name, "navitree__pagename") | ||||
| 	} else { | ||||
| 		html += navitreeEntry(t.name, "navitree__name") | ||||
| 	} | ||||
|  | ||||
| 	for _, subtree := range t.descendants { | ||||
| 		html += subtree.asHtml() | ||||
| 	} | ||||
|  | ||||
| 	if t.root { | ||||
| 		for _, siblingName := range t.siblings { | ||||
| 			html += navitreeEntry(siblingName, "navitree__sibling") | ||||
| func Tree(hyphaName string) (relatives, subhyphae, prev, next string) { | ||||
| 	var ( | ||||
| 		// One of the siblings is the hypha with name `hyphaName` | ||||
| 		siblings      = findSiblings(hyphaName) | ||||
| 		subhyphaePool = make(map[string]bool) | ||||
| 		I             int | ||||
| 	) | ||||
| 	for h := range hyphae.YieldExistingHyphae() { | ||||
| 		for _, s := range siblings { | ||||
| 			s.checkThisChild(h.Name) | ||||
| 		} | ||||
| 		if strings.HasPrefix(h.Name, hyphaName+"/") { | ||||
| 			subhyphaePool[h.Name] = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return `<ul class="navitree__node">` + html + `</ul>` | ||||
| 	for i, s := range siblings { | ||||
| 		if s.name == hyphaName { | ||||
| 			I = i | ||||
| 			relatives += fmt.Sprintf(`<li class="navitree__entry navitree__entry_this"><span>%s</span></li>`, util.BeautifulName(path.Base(hyphaName))) | ||||
| 		} else { | ||||
| 			relatives += s.asHTML() | ||||
| 		} | ||||
| 	} | ||||
| 	if I != 0 { | ||||
| 		prev = siblings[I-1].name | ||||
| 	} | ||||
| 	if I != len(siblings)-1 { | ||||
| 		next = siblings[I+1].name | ||||
| 	} | ||||
| 	return fmt.Sprintf(`<ul class="navitree">%s</ul>`, relatives), subhyphaeMatrix(hyphaName, subhyphaePool), prev, next | ||||
| } | ||||
|  | ||||
| // Strip hypha name from all ancestor names, replace _ with spaces, title case | ||||
| func beautifulName(uglyName string) string { | ||||
| 	return strings.Title(strings.ReplaceAll(path.Base(uglyName), "_", " ")) | ||||
| } | ||||
|  | ||||
| // navitreeEntry is a small utility function that makes generating html easier. | ||||
| func navitreeEntry(name, class string) string { | ||||
| 	return fmt.Sprintf(`<li class="navitree__entry %s"> | ||||
| 	<a class="navitree__link" href="/page/%s">%s</a> | ||||
| </li> | ||||
| `, class, name, beautifulName(name)) | ||||
| func findSiblings(hyphaName string) []*sibling { | ||||
| 	siblings := []*sibling{&sibling{name: hyphaName, hasChildren: true}} | ||||
| 	for h := range hyphae.YieldExistingHyphae() { | ||||
| 		if path.Dir(hyphaName) == path.Dir(h.Name) && hyphaName != h.Name { | ||||
| 			siblings = append(siblings, &sibling{name: h.Name, hasChildren: false}) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Slice(siblings, func(i, j int) bool { | ||||
| 		return siblings[i].name < siblings[j].name | ||||
| 	}) | ||||
| 	return siblings | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ var minimalRights = map[string]int{ | ||||
| 	"delete-ask":          3, | ||||
| 	"delete-confirm":      3, | ||||
| 	"reindex":             4, | ||||
| 	"admin/shutdown":      4, | ||||
| } | ||||
|  | ||||
| // Group — Right | ||||
|   | ||||
| @@ -8,16 +8,25 @@ var AuthUsed bool | ||||
| var users sync.Map | ||||
| var tokens sync.Map | ||||
|  | ||||
| func YieldUsers() chan *User { | ||||
| 	ch := make(chan *User) | ||||
| 	go func(ch chan *User) { | ||||
| 		users.Range(func(_, v interface{}) bool { | ||||
| 			ch <- v.(*User) | ||||
| 			return true | ||||
| 		}) | ||||
| 		close(ch) | ||||
| 	}(ch) | ||||
| 	return ch | ||||
| } | ||||
|  | ||||
| func ListUsersWithGroup(group string) []string { | ||||
| 	usersWithTheGroup := []string{} | ||||
| 	users.Range(func(_, v interface{}) bool { | ||||
| 		userobj := v.(*User) | ||||
|  | ||||
| 		if userobj.Group == group { | ||||
| 			usersWithTheGroup = append(usersWithTheGroup, userobj.Name) | ||||
| 	for u := range YieldUsers() { | ||||
| 		if u.Group == group { | ||||
| 			usersWithTheGroup = append(usersWithTheGroup, u.Name) | ||||
| 		} | ||||
| 		return true | ||||
| 	}) | ||||
| 	} | ||||
| 	return usersWithTheGroup | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										45
									
								
								util/util.go
									
									
									
									
									
								
							
							
						
						| @@ -4,7 +4,9 @@ import ( | ||||
| 	"crypto/rand" | ||||
| 	"encoding/hex" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| @@ -18,8 +20,27 @@ var ( | ||||
| 	HeaderLinksHypha     string | ||||
| 	AuthMethod           string | ||||
| 	FixedCredentialsPath string | ||||
| 	GeminiCertPath       string | ||||
| ) | ||||
|  | ||||
| // LettersNumbersOnly keeps letters and numbers only in the given string. | ||||
| func LettersNumbersOnly(s string) string { | ||||
| 	var ( | ||||
| 		ret            strings.Builder | ||||
| 		usedUnderscore bool | ||||
| 	) | ||||
| 	for _, r := range s { | ||||
| 		if unicode.IsLetter(r) || unicode.IsNumber(r) { | ||||
| 			ret.WriteRune(r) | ||||
| 			usedUnderscore = false | ||||
| 		} else if !usedUnderscore { | ||||
| 			ret.WriteRune('_') | ||||
| 			usedUnderscore = true | ||||
| 		} | ||||
| 	} | ||||
| 	return strings.Trim(ret.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) { | ||||
| @@ -39,17 +60,6 @@ func HTTP200Page(w http.ResponseWriter, page string) { | ||||
| 	w.Write([]byte(page)) | ||||
| } | ||||
|  | ||||
| // FindSubhyphae finds names of existing hyphae given the `hyphaIterator`. | ||||
| func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string { | ||||
| 	subhyphae := make([]string, 0) | ||||
| 	hyphaIterator(func(otherHyphaName string) { | ||||
| 		if strings.HasPrefix(otherHyphaName, hyphaName+"/") { | ||||
| 			subhyphae = append(subhyphae, otherHyphaName) | ||||
| 		} | ||||
| 	}) | ||||
| 	return subhyphae | ||||
| } | ||||
|  | ||||
| func RandomString(n int) (string, error) { | ||||
| 	bytes := make([]byte, n) | ||||
| 	if _, err := rand.Read(bytes); err != nil { | ||||
| @@ -65,3 +75,16 @@ func BeautifulName(uglyName string) string { | ||||
| 	} | ||||
| 	return strings.Title(strings.ReplaceAll(uglyName, "_", " ")) | ||||
| } | ||||
|  | ||||
| // CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores. | ||||
| func CanonicalName(name string) string { | ||||
| 	return strings.ToLower(strings.ReplaceAll(name, " ", "_")) | ||||
| } | ||||
|  | ||||
| // HyphaPattern is a pattern which all hyphae must match. | ||||
| var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) | ||||
|  | ||||
| // IsCanonicalName checks if the `name` is canonical. | ||||
| func IsCanonicalName(name string) bool { | ||||
| 	return HyphaPattern.MatchString(name) | ||||
| } | ||||
|   | ||||
							
								
								
									
										68
									
								
								views/auth.qtpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| {% import "github.com/bouncepaw/mycorrhiza/user" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
|  | ||||
| {% func LoginHTML() %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 	{% if user.AuthUsed %} | ||||
| 		<form class="modal" method="post" action="/login-data" id="login-form" enctype="multipart/form-data" autocomplete="on"> | ||||
| 			<fieldset class="modal__fieldset"> | ||||
| 				<legend class="modal__title">Log in to {%s util.SiteName %}</legend> | ||||
| 				<p>Use the data you were given by an administrator.</p> | ||||
| 				<label for="login-form__username">Username</label> | ||||
| 				<br> | ||||
| 				<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username"> | ||||
| 				<br> | ||||
| 				<label for="login-form__password">Password</label> | ||||
| 				<br> | ||||
| 				<input type="password" required name="password" autocomplete="current-password"> | ||||
| 				<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> | ||||
| 				<input class="modal__action modal__submit" type="submit"> | ||||
| 				<a class="modal__action modal__cancel" href="/">Cancel</a> | ||||
| 			</fieldset> | ||||
| 		</form> | ||||
| 	{% else %} | ||||
| 		<p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p> | ||||
| 		<p><a class="modal__cancel" href="/">← Go home</a></p> | ||||
| 	{% endif %} | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func LoginErrorHTML(err string) %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 	{% switch err %} | ||||
| 	{% case "unknown username" %} | ||||
| 		<p class="error">Unknown username.</p> | ||||
| 	{% case "wrong password" %} | ||||
| 		<p class="error">Wrong password.</p> | ||||
| 	{% default %} | ||||
| 		<p class="error">{%s err %}</p> | ||||
| 	{% endswitch %} | ||||
| 		<p><a href="/login">← Try again</a></p> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func LogoutHTML(can bool) %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 	{% if can %} | ||||
| 		<h1>Log out?</h1> | ||||
| 		<p><a href="/logout-confirm"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/">Cancel</a></p> | ||||
| 	{% else %} | ||||
| 		<p>You cannot log out because you are not logged in.</p> | ||||
| 		<p><a href="/login">Login</a></p> | ||||
| 		<p><a href="/login">← Home</a></p> | ||||
| 	{% endif %} | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
							
								
								
									
										232
									
								
								views/auth.qtpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,232 @@ | ||||
| // Code generated by qtc from "auth.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line views/auth.qtpl:1 | ||||
| package views | ||||
|  | ||||
| //line views/auth.qtpl:1 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line views/auth.qtpl:2 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| //line views/auth.qtpl:4 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line views/auth.qtpl:4 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line views/auth.qtpl:4 | ||||
| func StreamLoginHTML(qw422016 *qt422016.Writer) { | ||||
| //line views/auth.qtpl:4 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 	`) | ||||
| //line views/auth.qtpl:8 | ||||
| 	if user.AuthUsed { | ||||
| //line views/auth.qtpl:8 | ||||
| 		qw422016.N().S(` | ||||
| 		<form class="modal" method="post" action="/login-data" id="login-form" enctype="multipart/form-data" autocomplete="on"> | ||||
| 			<fieldset class="modal__fieldset"> | ||||
| 				<legend class="modal__title">Log in to `) | ||||
| //line views/auth.qtpl:11 | ||||
| 		qw422016.E().S(util.SiteName) | ||||
| //line views/auth.qtpl:11 | ||||
| 		qw422016.N().S(`</legend> | ||||
| 				<p>Use the data you were given by an administrator.</p> | ||||
| 				<label for="login-form__username">Username</label> | ||||
| 				<br> | ||||
| 				<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username"> | ||||
| 				<br> | ||||
| 				<label for="login-form__password">Password</label> | ||||
| 				<br> | ||||
| 				<input type="password" required name="password" autocomplete="current-password"> | ||||
| 				<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.</p> | ||||
| 				<input class="modal__action modal__submit" type="submit"> | ||||
| 				<a class="modal__action modal__cancel" href="/">Cancel</a> | ||||
| 			</fieldset> | ||||
| 		</form> | ||||
| 	`) | ||||
| //line views/auth.qtpl:25 | ||||
| 	} else { | ||||
| //line views/auth.qtpl:25 | ||||
| 		qw422016.N().S(` | ||||
| 		<p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p> | ||||
| 		<p><a class="modal__cancel" href="/">← Go home</a></p> | ||||
| 	`) | ||||
| //line views/auth.qtpl:28 | ||||
| 	} | ||||
| //line views/auth.qtpl:28 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/auth.qtpl:32 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:32 | ||||
| func WriteLoginHTML(qq422016 qtio422016.Writer) { | ||||
| //line views/auth.qtpl:32 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/auth.qtpl:32 | ||||
| 	StreamLoginHTML(qw422016) | ||||
| //line views/auth.qtpl:32 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/auth.qtpl:32 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:32 | ||||
| func LoginHTML() string { | ||||
| //line views/auth.qtpl:32 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/auth.qtpl:32 | ||||
| 	WriteLoginHTML(qb422016) | ||||
| //line views/auth.qtpl:32 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/auth.qtpl:32 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/auth.qtpl:32 | ||||
| 	return qs422016 | ||||
| //line views/auth.qtpl:32 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:34 | ||||
| func StreamLoginErrorHTML(qw422016 *qt422016.Writer, err string) { | ||||
| //line views/auth.qtpl:34 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 	`) | ||||
| //line views/auth.qtpl:38 | ||||
| 	switch err { | ||||
| //line views/auth.qtpl:39 | ||||
| 	case "unknown username": | ||||
| //line views/auth.qtpl:39 | ||||
| 		qw422016.N().S(` | ||||
| 		<p class="error">Unknown username.</p> | ||||
| 	`) | ||||
| //line views/auth.qtpl:41 | ||||
| 	case "wrong password": | ||||
| //line views/auth.qtpl:41 | ||||
| 		qw422016.N().S(` | ||||
| 		<p class="error">Wrong password.</p> | ||||
| 	`) | ||||
| //line views/auth.qtpl:43 | ||||
| 	default: | ||||
| //line views/auth.qtpl:43 | ||||
| 		qw422016.N().S(` | ||||
| 		<p class="error">`) | ||||
| //line views/auth.qtpl:44 | ||||
| 		qw422016.E().S(err) | ||||
| //line views/auth.qtpl:44 | ||||
| 		qw422016.N().S(`</p> | ||||
| 	`) | ||||
| //line views/auth.qtpl:45 | ||||
| 	} | ||||
| //line views/auth.qtpl:45 | ||||
| 	qw422016.N().S(` | ||||
| 		<p><a href="/login">← Try again</a></p> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/auth.qtpl:50 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:50 | ||||
| func WriteLoginErrorHTML(qq422016 qtio422016.Writer, err string) { | ||||
| //line views/auth.qtpl:50 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/auth.qtpl:50 | ||||
| 	StreamLoginErrorHTML(qw422016, err) | ||||
| //line views/auth.qtpl:50 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/auth.qtpl:50 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:50 | ||||
| func LoginErrorHTML(err string) string { | ||||
| //line views/auth.qtpl:50 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/auth.qtpl:50 | ||||
| 	WriteLoginErrorHTML(qb422016, err) | ||||
| //line views/auth.qtpl:50 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/auth.qtpl:50 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/auth.qtpl:50 | ||||
| 	return qs422016 | ||||
| //line views/auth.qtpl:50 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:52 | ||||
| func StreamLogoutHTML(qw422016 *qt422016.Writer, can bool) { | ||||
| //line views/auth.qtpl:52 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 	`) | ||||
| //line views/auth.qtpl:56 | ||||
| 	if can { | ||||
| //line views/auth.qtpl:56 | ||||
| 		qw422016.N().S(` | ||||
| 		<h1>Log out?</h1> | ||||
| 		<p><a href="/logout-confirm"><strong>Confirm</strong></a></p> | ||||
| 		<p><a href="/">Cancel</a></p> | ||||
| 	`) | ||||
| //line views/auth.qtpl:60 | ||||
| 	} else { | ||||
| //line views/auth.qtpl:60 | ||||
| 		qw422016.N().S(` | ||||
| 		<p>You cannot log out because you are not logged in.</p> | ||||
| 		<p><a href="/login">Login</a></p> | ||||
| 		<p><a href="/login">← Home</a></p> | ||||
| 	`) | ||||
| //line views/auth.qtpl:64 | ||||
| 	} | ||||
| //line views/auth.qtpl:64 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/auth.qtpl:68 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:68 | ||||
| func WriteLogoutHTML(qq422016 qtio422016.Writer, can bool) { | ||||
| //line views/auth.qtpl:68 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/auth.qtpl:68 | ||||
| 	StreamLogoutHTML(qw422016, can) | ||||
| //line views/auth.qtpl:68 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/auth.qtpl:68 | ||||
| } | ||||
|  | ||||
| //line views/auth.qtpl:68 | ||||
| func LogoutHTML(can bool) string { | ||||
| //line views/auth.qtpl:68 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/auth.qtpl:68 | ||||
| 	WriteLogoutHTML(qb422016, can) | ||||
| //line views/auth.qtpl:68 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/auth.qtpl:68 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/auth.qtpl:68 | ||||
| 	return qs422016 | ||||
| //line views/auth.qtpl:68 | ||||
| } | ||||
| @@ -1,5 +1,11 @@ | ||||
| {% func RecentChangesHTML(changes []string, n int) %} | ||||
| <main class="recent-changes"> | ||||
| {% import "net/http" %} | ||||
| 
 | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/history" %} | ||||
| 
 | ||||
| {% func RecentChangesHTML(n int) %} | ||||
| <div class="layout"> | ||||
| <main class="main-width recent-changes"> | ||||
| 	<h1>Recent Changes</h1> | ||||
| 	<p><a href="/">← Back</a></p> | ||||
| 
 | ||||
| @@ -27,6 +33,9 @@ | ||||
| 		How come? I'll add the role anyway. -- bouncepaw | ||||
| 	{% endcomment %} | ||||
| 
 | ||||
| 	{% code  | ||||
| 	changes := history.RecentChanges(n) | ||||
| 	%} | ||||
| 	<section class="recent-changes__list" role="feed"> | ||||
| 	{% if len(changes) == 0 %} | ||||
| 		<p>Could not find any recent changes.</p> | ||||
| @@ -34,10 +43,30 @@ | ||||
| 		{% for i, entry := range changes %} | ||||
| 		<ul class="recent-changes__entry rc-entry" role="article" | ||||
| 		    aria-setsize="{%d n %}" aria-posinset="{%d i %}"> | ||||
| 			 {%s= entry %} | ||||
| 			 {%s= recentChangesEntry(entry) %} | ||||
| 		</ul> | ||||
| 		{% endfor %} | ||||
| 	{% endif %} | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
| 
 | ||||
| {% func recentChangesEntry(rev history.Revision) %} | ||||
| <li class="rc-entry__time"><time>{%s rev.TimeString() %}</time></li> | ||||
| <li class="rc-entry__hash">{%s rev.Hash %}</li> | ||||
| <li class="rc-entry__links">{%s= rev.HyphaeLinksHTML() %}</li> | ||||
| <li class="rc-entry__msg">{%s rev.Message %} {% if rev.Username != "anon" %}<span class="rc-entry__author">by <a href="/hypha/{%s util.UserHypha %}/{%s rev.Username %}" rel="author">{%s rev.Username %}</a></span>{% endif %}</li> | ||||
| {% endfunc %} | ||||
| 
 | ||||
| {% func HistoryHTML(rq *http.Request, hyphaName, list string) %} | ||||
| {%= NavHTML(rq, hyphaName, "history") %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<article class="history"> | ||||
| 		<h1>History of {%s util.BeautifulName(hyphaName) %}</h1> | ||||
| 		{%s= list %} | ||||
| 	</article> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
							
								
								
									
										305
									
								
								views/history.qtpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,305 @@ | ||||
| // Code generated by qtc from "history.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line views/history.qtpl:1 | ||||
| package views | ||||
|  | ||||
| //line views/history.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| //line views/history.qtpl:3 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| //line views/history.qtpl:4 | ||||
| import "github.com/bouncepaw/mycorrhiza/history" | ||||
|  | ||||
| //line views/history.qtpl:6 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line views/history.qtpl:6 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line views/history.qtpl:6 | ||||
| func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) { | ||||
| //line views/history.qtpl:6 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width recent-changes"> | ||||
| 	<h1>Recent Changes</h1> | ||||
| 	<p><a href="/">← Back</a></p> | ||||
|  | ||||
| 	<nav class="recent-changes__count"> | ||||
| 		See  | ||||
| 	`) | ||||
| //line views/history.qtpl:14 | ||||
| 	for _, m := range []int{20, 0, 50, 0, 100} { | ||||
| //line views/history.qtpl:14 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line views/history.qtpl:15 | ||||
| 		switch m { | ||||
| //line views/history.qtpl:16 | ||||
| 		case 0: | ||||
| //line views/history.qtpl:16 | ||||
| 			qw422016.N().S(` | ||||
| 		<span aria-hidden="true">|</span> | ||||
| 		`) | ||||
| //line views/history.qtpl:18 | ||||
| 		case n: | ||||
| //line views/history.qtpl:18 | ||||
| 			qw422016.N().S(` | ||||
| 		<b>`) | ||||
| //line views/history.qtpl:19 | ||||
| 			qw422016.N().D(n) | ||||
| //line views/history.qtpl:19 | ||||
| 			qw422016.N().S(`</b> | ||||
| 		`) | ||||
| //line views/history.qtpl:20 | ||||
| 		default: | ||||
| //line views/history.qtpl:20 | ||||
| 			qw422016.N().S(` | ||||
| 		<a href="/recent-changes/`) | ||||
| //line views/history.qtpl:21 | ||||
| 			qw422016.N().D(m) | ||||
| //line views/history.qtpl:21 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line views/history.qtpl:21 | ||||
| 			qw422016.N().D(m) | ||||
| //line views/history.qtpl:21 | ||||
| 			qw422016.N().S(`</a> | ||||
| 	`) | ||||
| //line views/history.qtpl:22 | ||||
| 		} | ||||
| //line views/history.qtpl:22 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line views/history.qtpl:23 | ||||
| 	} | ||||
| //line views/history.qtpl:23 | ||||
| 	qw422016.N().S(` | ||||
| 		recent changes | ||||
| 	</nav> | ||||
|  | ||||
| 	<p><img class="icon" width="20" height="20" src="https://upload.wikimedia.org/wikipedia/commons/4/46/Generic_Feed-icon.svg">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p> | ||||
|  | ||||
| 	`) | ||||
| //line views/history.qtpl:34 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	`) | ||||
| //line views/history.qtpl:37 | ||||
| 	changes := history.RecentChanges(n) | ||||
|  | ||||
| //line views/history.qtpl:38 | ||||
| 	qw422016.N().S(` | ||||
| 	<section class="recent-changes__list" role="feed"> | ||||
| 	`) | ||||
| //line views/history.qtpl:40 | ||||
| 	if len(changes) == 0 { | ||||
| //line views/history.qtpl:40 | ||||
| 		qw422016.N().S(` | ||||
| 		<p>Could not find any recent changes.</p> | ||||
| 	`) | ||||
| //line views/history.qtpl:42 | ||||
| 	} else { | ||||
| //line views/history.qtpl:42 | ||||
| 		qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/history.qtpl:43 | ||||
| 		for i, entry := range changes { | ||||
| //line views/history.qtpl:43 | ||||
| 			qw422016.N().S(` | ||||
| 		<ul class="recent-changes__entry rc-entry" role="article" | ||||
| 		    aria-setsize="`) | ||||
| //line views/history.qtpl:45 | ||||
| 			qw422016.N().D(n) | ||||
| //line views/history.qtpl:45 | ||||
| 			qw422016.N().S(`" aria-posinset="`) | ||||
| //line views/history.qtpl:45 | ||||
| 			qw422016.N().D(i) | ||||
| //line views/history.qtpl:45 | ||||
| 			qw422016.N().S(`"> | ||||
| 			 `) | ||||
| //line views/history.qtpl:46 | ||||
| 			qw422016.N().S(recentChangesEntry(entry)) | ||||
| //line views/history.qtpl:46 | ||||
| 			qw422016.N().S(` | ||||
| 		</ul> | ||||
| 		`) | ||||
| //line views/history.qtpl:48 | ||||
| 		} | ||||
| //line views/history.qtpl:48 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line views/history.qtpl:49 | ||||
| 	} | ||||
| //line views/history.qtpl:49 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/history.qtpl:53 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:53 | ||||
| func WriteRecentChangesHTML(qq422016 qtio422016.Writer, n int) { | ||||
| //line views/history.qtpl:53 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/history.qtpl:53 | ||||
| 	StreamRecentChangesHTML(qw422016, n) | ||||
| //line views/history.qtpl:53 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/history.qtpl:53 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:53 | ||||
| func RecentChangesHTML(n int) string { | ||||
| //line views/history.qtpl:53 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/history.qtpl:53 | ||||
| 	WriteRecentChangesHTML(qb422016, n) | ||||
| //line views/history.qtpl:53 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/history.qtpl:53 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/history.qtpl:53 | ||||
| 	return qs422016 | ||||
| //line views/history.qtpl:53 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:55 | ||||
| func streamrecentChangesEntry(qw422016 *qt422016.Writer, rev history.Revision) { | ||||
| //line views/history.qtpl:55 | ||||
| 	qw422016.N().S(` | ||||
| <li class="rc-entry__time"><time>`) | ||||
| //line views/history.qtpl:56 | ||||
| 	qw422016.E().S(rev.TimeString()) | ||||
| //line views/history.qtpl:56 | ||||
| 	qw422016.N().S(`</time></li> | ||||
| <li class="rc-entry__hash">`) | ||||
| //line views/history.qtpl:57 | ||||
| 	qw422016.E().S(rev.Hash) | ||||
| //line views/history.qtpl:57 | ||||
| 	qw422016.N().S(`</li> | ||||
| <li class="rc-entry__links">`) | ||||
| //line views/history.qtpl:58 | ||||
| 	qw422016.N().S(rev.HyphaeLinksHTML()) | ||||
| //line views/history.qtpl:58 | ||||
| 	qw422016.N().S(`</li> | ||||
| <li class="rc-entry__msg">`) | ||||
| //line views/history.qtpl:59 | ||||
| 	qw422016.E().S(rev.Message) | ||||
| //line views/history.qtpl:59 | ||||
| 	qw422016.N().S(` `) | ||||
| //line views/history.qtpl:59 | ||||
| 	if rev.Username != "anon" { | ||||
| //line views/history.qtpl:59 | ||||
| 		qw422016.N().S(`<span class="rc-entry__author">by <a href="/hypha/`) | ||||
| //line views/history.qtpl:59 | ||||
| 		qw422016.E().S(util.UserHypha) | ||||
| //line views/history.qtpl:59 | ||||
| 		qw422016.N().S(`/`) | ||||
| //line views/history.qtpl:59 | ||||
| 		qw422016.E().S(rev.Username) | ||||
| //line views/history.qtpl:59 | ||||
| 		qw422016.N().S(`" rel="author">`) | ||||
| //line views/history.qtpl:59 | ||||
| 		qw422016.E().S(rev.Username) | ||||
| //line views/history.qtpl:59 | ||||
| 		qw422016.N().S(`</a></span>`) | ||||
| //line views/history.qtpl:59 | ||||
| 	} | ||||
| //line views/history.qtpl:59 | ||||
| 	qw422016.N().S(`</li> | ||||
| `) | ||||
| //line views/history.qtpl:60 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:60 | ||||
| func writerecentChangesEntry(qq422016 qtio422016.Writer, rev history.Revision) { | ||||
| //line views/history.qtpl:60 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/history.qtpl:60 | ||||
| 	streamrecentChangesEntry(qw422016, rev) | ||||
| //line views/history.qtpl:60 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/history.qtpl:60 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:60 | ||||
| func recentChangesEntry(rev history.Revision) string { | ||||
| //line views/history.qtpl:60 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/history.qtpl:60 | ||||
| 	writerecentChangesEntry(qb422016, rev) | ||||
| //line views/history.qtpl:60 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/history.qtpl:60 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/history.qtpl:60 | ||||
| 	return qs422016 | ||||
| //line views/history.qtpl:60 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:62 | ||||
| func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) { | ||||
| //line views/history.qtpl:62 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/history.qtpl:63 | ||||
| 	StreamNavHTML(qw422016, rq, hyphaName, "history") | ||||
| //line views/history.qtpl:63 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<article class="history"> | ||||
| 		<h1>History of `) | ||||
| //line views/history.qtpl:67 | ||||
| 	qw422016.E().S(util.BeautifulName(hyphaName)) | ||||
| //line views/history.qtpl:67 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 		`) | ||||
| //line views/history.qtpl:68 | ||||
| 	qw422016.N().S(list) | ||||
| //line views/history.qtpl:68 | ||||
| 	qw422016.N().S(` | ||||
| 	</article> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/history.qtpl:72 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:72 | ||||
| func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) { | ||||
| //line views/history.qtpl:72 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/history.qtpl:72 | ||||
| 	StreamHistoryHTML(qw422016, rq, hyphaName, list) | ||||
| //line views/history.qtpl:72 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/history.qtpl:72 | ||||
| } | ||||
|  | ||||
| //line views/history.qtpl:72 | ||||
| func HistoryHTML(rq *http.Request, hyphaName, list string) string { | ||||
| //line views/history.qtpl:72 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/history.qtpl:72 | ||||
| 	WriteHistoryHTML(qb422016, rq, hyphaName, list) | ||||
| //line views/history.qtpl:72 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/history.qtpl:72 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/history.qtpl:72 | ||||
| 	return qs422016 | ||||
| //line views/history.qtpl:72 | ||||
| } | ||||
							
								
								
									
										81
									
								
								views/hypha.qtpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,81 @@ | ||||
| {% import "path/filepath" %} | ||||
| {% import "strings" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/hyphae" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
|  | ||||
| {% func NaviTitleHTML(h *hyphae.Hypha) %} | ||||
| {% code  | ||||
| 	var ( | ||||
| 		prevAcc = "/hypha/" | ||||
| 		parts = strings.Split(h.Name, "/") | ||||
| 	) | ||||
| %} | ||||
| <h1 class="navi-title"> | ||||
| {% stripspace %} | ||||
| 	<a href="/hypha/{%s util.HomePage %}"> | ||||
| 		{%-s= util.SiteNavIcon -%} | ||||
| 		<span aria-hidden="true" class="navi-title__colon">:</span> | ||||
| 	</a> | ||||
|  | ||||
| 	{% for i, part := range parts %} | ||||
| 		{% if i > 0 %} | ||||
| 			<span aria-hidden="true" class="navi-title__separator">/</span> | ||||
| 		{% endif %} | ||||
|  | ||||
| 			<a href="{%s prevAcc + part %}" | ||||
| 				rel="{% if i == len(parts) - 1 %}bookmark{% else %}up{% endif %}"> | ||||
| 				{%s= util.BeautifulName(part) %} | ||||
| 			</a> | ||||
| 		{% code prevAcc += part + "/" %} | ||||
| 	{% endfor %} | ||||
| {% endstripspace %} | ||||
| </h1> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func BackLinksHTML(h *hyphae.Hypha) %} | ||||
| <aside class="backlinks layout-card"> | ||||
| 	<h2 class="backlinks__title layout-card__title">Backlinks</h2> | ||||
| 	<nav class="backlinks__nav"> | ||||
| 		<ul class="backlinks__list"> | ||||
| 			{% for _, backlink := range h.BackLinks %} | ||||
| 			<li class="backlinks__entry"> | ||||
| 				<a class="backlinks__link" href="/hypha/{%s backlink.Name %}"> | ||||
| 					{%s util.BeautifulName(filepath.Base(backlink.Name)) %} | ||||
| 				</a> | ||||
| 			</li> | ||||
| 			{% endfor %} | ||||
| 		</ul> | ||||
| 	</nav> | ||||
| </aside> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func AttachmentHTML(h *hyphae.Hypha) %} | ||||
| 	{% switch filepath.Ext(h.BinaryPath) %} | ||||
|  | ||||
| 	{% case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico" %} | ||||
| 	<div class="binary-container binary-container_with-img"> | ||||
| 		<a href="/binary/{%s= h.Name %}"><img src="/binary/{%s= h.Name %}"/></a> | ||||
| 	</div> | ||||
|  | ||||
| 	{% case ".ogg", ".webm", ".mp4" %} | ||||
| 	<div class="binary-container binary-container_with-video"> | ||||
| 		<video controls> | ||||
| 			<source src="/binary/{%s= h.Name %}"/> | ||||
| 			<p>Your browser does not support video. <a href="/binary/{%s= h.Name %}">Download video</a></p> | ||||
| 		</video> | ||||
| 	</div> | ||||
|  | ||||
| 	{% case ".mp3" %} | ||||
| 	<div class="binary-container binary-container_with-audio"> | ||||
| 		<audio controls> | ||||
| 			<source src="/binary/{%s= h.Name %}"/> | ||||
| 			<p>Your browser does not support audio. <a href="/binary/{%s= h.Name %}">Download audio</a></p> | ||||
| 		</audio> | ||||
| 	</div> | ||||
|  | ||||
| 	{% default %} | ||||
| 	<div class="binary-container binary-container_with-nothing"> | ||||
| 		<p><a href="/binary/{%s= h.Name %}">Download media</a></p> | ||||
| 	</div> | ||||
| {% endswitch %} | ||||
| {% endfunc %} | ||||
							
								
								
									
										297
									
								
								views/hypha.qtpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,297 @@ | ||||
| // Code generated by qtc from "hypha.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line views/hypha.qtpl:1 | ||||
| package views | ||||
|  | ||||
| //line views/hypha.qtpl:1 | ||||
| import "path/filepath" | ||||
|  | ||||
| //line views/hypha.qtpl:2 | ||||
| import "strings" | ||||
|  | ||||
| //line views/hypha.qtpl:3 | ||||
| import "github.com/bouncepaw/mycorrhiza/hyphae" | ||||
|  | ||||
| //line views/hypha.qtpl:4 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| //line views/hypha.qtpl:6 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line views/hypha.qtpl:6 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line views/hypha.qtpl:6 | ||||
| func StreamNaviTitleHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) { | ||||
| //line views/hypha.qtpl:6 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/hypha.qtpl:8 | ||||
| 	var ( | ||||
| 		prevAcc = "/hypha/" | ||||
| 		parts   = strings.Split(h.Name, "/") | ||||
| 	) | ||||
|  | ||||
| //line views/hypha.qtpl:12 | ||||
| 	qw422016.N().S(` | ||||
| <h1 class="navi-title"> | ||||
| `) | ||||
| //line views/hypha.qtpl:14 | ||||
| 	qw422016.N().S(`<a href="/hypha/`) | ||||
| //line views/hypha.qtpl:15 | ||||
| 	qw422016.E().S(util.HomePage) | ||||
| //line views/hypha.qtpl:15 | ||||
| 	qw422016.N().S(`">`) | ||||
| //line views/hypha.qtpl:16 | ||||
| 	qw422016.N().S(util.SiteNavIcon) | ||||
| //line views/hypha.qtpl:16 | ||||
| 	qw422016.N().S(`<span aria-hidden="true" class="navi-title__colon">:</span></a>`) | ||||
| //line views/hypha.qtpl:20 | ||||
| 	for i, part := range parts { | ||||
| //line views/hypha.qtpl:21 | ||||
| 		if i > 0 { | ||||
| //line views/hypha.qtpl:21 | ||||
| 			qw422016.N().S(`<span aria-hidden="true" class="navi-title__separator">/</span>`) | ||||
| //line views/hypha.qtpl:23 | ||||
| 		} | ||||
| //line views/hypha.qtpl:23 | ||||
| 		qw422016.N().S(`<a href="`) | ||||
| //line views/hypha.qtpl:25 | ||||
| 		qw422016.E().S(prevAcc + part) | ||||
| //line views/hypha.qtpl:25 | ||||
| 		qw422016.N().S(`"rel="`) | ||||
| //line views/hypha.qtpl:26 | ||||
| 		if i == len(parts)-1 { | ||||
| //line views/hypha.qtpl:26 | ||||
| 			qw422016.N().S(`bookmark`) | ||||
| //line views/hypha.qtpl:26 | ||||
| 		} else { | ||||
| //line views/hypha.qtpl:26 | ||||
| 			qw422016.N().S(`up`) | ||||
| //line views/hypha.qtpl:26 | ||||
| 		} | ||||
| //line views/hypha.qtpl:26 | ||||
| 		qw422016.N().S(`">`) | ||||
| //line views/hypha.qtpl:27 | ||||
| 		qw422016.N().S(util.BeautifulName(part)) | ||||
| //line views/hypha.qtpl:27 | ||||
| 		qw422016.N().S(`</a>`) | ||||
| //line views/hypha.qtpl:29 | ||||
| 		prevAcc += part + "/" | ||||
|  | ||||
| //line views/hypha.qtpl:30 | ||||
| 	} | ||||
| //line views/hypha.qtpl:31 | ||||
| 	qw422016.N().S(` | ||||
| </h1> | ||||
| `) | ||||
| //line views/hypha.qtpl:33 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:33 | ||||
| func WriteNaviTitleHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) { | ||||
| //line views/hypha.qtpl:33 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/hypha.qtpl:33 | ||||
| 	StreamNaviTitleHTML(qw422016, h) | ||||
| //line views/hypha.qtpl:33 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/hypha.qtpl:33 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:33 | ||||
| func NaviTitleHTML(h *hyphae.Hypha) string { | ||||
| //line views/hypha.qtpl:33 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/hypha.qtpl:33 | ||||
| 	WriteNaviTitleHTML(qb422016, h) | ||||
| //line views/hypha.qtpl:33 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/hypha.qtpl:33 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/hypha.qtpl:33 | ||||
| 	return qs422016 | ||||
| //line views/hypha.qtpl:33 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:35 | ||||
| func StreamBackLinksHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) { | ||||
| //line views/hypha.qtpl:35 | ||||
| 	qw422016.N().S(` | ||||
| <aside class="backlinks layout-card"> | ||||
| 	<h2 class="backlinks__title layout-card__title">Backlinks</h2> | ||||
| 	<nav class="backlinks__nav"> | ||||
| 		<ul class="backlinks__list"> | ||||
| 			`) | ||||
| //line views/hypha.qtpl:40 | ||||
| 	for _, backlink := range h.BackLinks { | ||||
| //line views/hypha.qtpl:40 | ||||
| 		qw422016.N().S(` | ||||
| 			<li class="backlinks__entry"> | ||||
| 				<a class="backlinks__link" href="/hypha/`) | ||||
| //line views/hypha.qtpl:42 | ||||
| 		qw422016.E().S(backlink.Name) | ||||
| //line views/hypha.qtpl:42 | ||||
| 		qw422016.N().S(`"> | ||||
| 					`) | ||||
| //line views/hypha.qtpl:43 | ||||
| 		qw422016.E().S(util.BeautifulName(filepath.Base(backlink.Name))) | ||||
| //line views/hypha.qtpl:43 | ||||
| 		qw422016.N().S(` | ||||
| 				</a> | ||||
| 			</li> | ||||
| 			`) | ||||
| //line views/hypha.qtpl:46 | ||||
| 	} | ||||
| //line views/hypha.qtpl:46 | ||||
| 	qw422016.N().S(` | ||||
| 		</ul> | ||||
| 	</nav> | ||||
| </aside> | ||||
| `) | ||||
| //line views/hypha.qtpl:50 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:50 | ||||
| func WriteBackLinksHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) { | ||||
| //line views/hypha.qtpl:50 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/hypha.qtpl:50 | ||||
| 	StreamBackLinksHTML(qw422016, h) | ||||
| //line views/hypha.qtpl:50 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/hypha.qtpl:50 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:50 | ||||
| func BackLinksHTML(h *hyphae.Hypha) string { | ||||
| //line views/hypha.qtpl:50 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/hypha.qtpl:50 | ||||
| 	WriteBackLinksHTML(qb422016, h) | ||||
| //line views/hypha.qtpl:50 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/hypha.qtpl:50 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/hypha.qtpl:50 | ||||
| 	return qs422016 | ||||
| //line views/hypha.qtpl:50 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:52 | ||||
| func StreamAttachmentHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) { | ||||
| //line views/hypha.qtpl:52 | ||||
| 	qw422016.N().S(` | ||||
| 	`) | ||||
| //line views/hypha.qtpl:53 | ||||
| 	switch filepath.Ext(h.BinaryPath) { | ||||
| //line views/hypha.qtpl:55 | ||||
| 	case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico": | ||||
| //line views/hypha.qtpl:55 | ||||
| 		qw422016.N().S(` | ||||
| 	<div class="binary-container binary-container_with-img"> | ||||
| 		<a href="/binary/`) | ||||
| //line views/hypha.qtpl:57 | ||||
| 		qw422016.N().S(h.Name) | ||||
| //line views/hypha.qtpl:57 | ||||
| 		qw422016.N().S(`"><img src="/binary/`) | ||||
| //line views/hypha.qtpl:57 | ||||
| 		qw422016.N().S(h.Name) | ||||
| //line views/hypha.qtpl:57 | ||||
| 		qw422016.N().S(`"/></a> | ||||
| 	</div> | ||||
|  | ||||
| 	`) | ||||
| //line views/hypha.qtpl:60 | ||||
| 	case ".ogg", ".webm", ".mp4": | ||||
| //line views/hypha.qtpl:60 | ||||
| 		qw422016.N().S(` | ||||
| 	<div class="binary-container binary-container_with-video"> | ||||
| 		<video controls> | ||||
| 			<source src="/binary/`) | ||||
| //line views/hypha.qtpl:63 | ||||
| 		qw422016.N().S(h.Name) | ||||
| //line views/hypha.qtpl:63 | ||||
| 		qw422016.N().S(`"/> | ||||
| 			<p>Your browser does not support video. <a href="/binary/`) | ||||
| //line views/hypha.qtpl:64 | ||||
| 		qw422016.N().S(h.Name) | ||||
| //line views/hypha.qtpl:64 | ||||
| 		qw422016.N().S(`">Download video</a></p> | ||||
| 		</video> | ||||
| 	</div> | ||||
|  | ||||
| 	`) | ||||
| //line views/hypha.qtpl:68 | ||||
| 	case ".mp3": | ||||
| //line views/hypha.qtpl:68 | ||||
| 		qw422016.N().S(` | ||||
| 	<div class="binary-container binary-container_with-audio"> | ||||
| 		<audio controls> | ||||
| 			<source src="/binary/`) | ||||
| //line views/hypha.qtpl:71 | ||||
| 		qw422016.N().S(h.Name) | ||||
| //line views/hypha.qtpl:71 | ||||
| 		qw422016.N().S(`"/> | ||||
| 			<p>Your browser does not support audio. <a href="/binary/`) | ||||
| //line views/hypha.qtpl:72 | ||||
| 		qw422016.N().S(h.Name) | ||||
| //line views/hypha.qtpl:72 | ||||
| 		qw422016.N().S(`">Download audio</a></p> | ||||
| 		</audio> | ||||
| 	</div> | ||||
|  | ||||
| 	`) | ||||
| //line views/hypha.qtpl:76 | ||||
| 	default: | ||||
| //line views/hypha.qtpl:76 | ||||
| 		qw422016.N().S(` | ||||
| 	<div class="binary-container binary-container_with-nothing"> | ||||
| 		<p><a href="/binary/`) | ||||
| //line views/hypha.qtpl:78 | ||||
| 		qw422016.N().S(h.Name) | ||||
| //line views/hypha.qtpl:78 | ||||
| 		qw422016.N().S(`">Download media</a></p> | ||||
| 	</div> | ||||
| `) | ||||
| //line views/hypha.qtpl:80 | ||||
| 	} | ||||
| //line views/hypha.qtpl:80 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/hypha.qtpl:81 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:81 | ||||
| func WriteAttachmentHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) { | ||||
| //line views/hypha.qtpl:81 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/hypha.qtpl:81 | ||||
| 	StreamAttachmentHTML(qw422016, h) | ||||
| //line views/hypha.qtpl:81 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/hypha.qtpl:81 | ||||
| } | ||||
|  | ||||
| //line views/hypha.qtpl:81 | ||||
| func AttachmentHTML(h *hyphae.Hypha) string { | ||||
| //line views/hypha.qtpl:81 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/hypha.qtpl:81 | ||||
| 	WriteAttachmentHTML(qb422016, h) | ||||
| //line views/hypha.qtpl:81 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/hypha.qtpl:81 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/hypha.qtpl:81 | ||||
| 	return qs422016 | ||||
| //line views/hypha.qtpl:81 | ||||
| } | ||||
							
								
								
									
										63
									
								
								views/modal.qtpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,63 @@ | ||||
| {% import "net/http" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
|  | ||||
| {% func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) %} | ||||
| {%= NavHTML(rq, hyphaName, "delete-ask") %} | ||||
| {%= modalBegin( | ||||
| 	"delete-confirm", | ||||
| 	hyphaName, | ||||
| 	"", | ||||
| 	"Delete "+util.BeautifulName(hyphaName)+"?") %} | ||||
| {%= modalReallyWant(hyphaName, "unattach") %} | ||||
| 		<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p> | ||||
| {%= modalEnd(hyphaName, true) %} | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) %} | ||||
| {%= NavHTML(rq, hyphaName, "unattach-ask") %} | ||||
| {%= modalBegin( | ||||
| 	"unattach", | ||||
| 	hyphaName, | ||||
| 	"", | ||||
| 	"Unattach "+util.BeautifulName(hyphaName)+"?") %} | ||||
| {%= modalReallyWant(hyphaName, "unattach") %} | ||||
| {%= modalEnd(hyphaName, true) %} | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) %} | ||||
| {%= NavHTML(rq, hyphaName, "rename-ask") %} | ||||
| {%= modalBegin( | ||||
| 	"rename-confirm", | ||||
| 	hyphaName, | ||||
| 	` method="post" enctype="multipart/form-data"`, | ||||
| 	"Rename "+util.BeautifulName(hyphaName)) %} | ||||
| 			<label for="new-name">New name</label> | ||||
| 			<input type="text" value="{%s hyphaName %}" required autofocus id="new-name" name="new-name"/> | ||||
|  | ||||
| 			<input type="checkbox" id="recursive" name="recursive" value="true" checked/> | ||||
| 			<label for="recursive">Rename subhyphae too</label> | ||||
|  | ||||
| 			<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p> | ||||
| {%= modalEnd(hyphaName, false) %} | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func modalReallyWant(hyphaName, verb string) %} | ||||
| 			<p class="modal__confirmation-msg">Do you really want to {%s verb %} hypha <em>{%s hyphaName %}</em>?</p> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func modalBegin(path, hyphaName, formAttrs, legend string) %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<form class="modal" action="/{%s path %}/{%s hyphaName %}"{%s= formAttrs %}> | ||||
| 		<fieldset class="modal__fieldset"> | ||||
| 			<legend class="modal__title">{%s= legend %}</legend> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func modalEnd(hyphaName string, shouldFocusOnConfirm bool) %} | ||||
| 			<input type="submit" value="Confirm" class="modal__action modal__submit" {% if shouldFocusOnConfirm %}autofocus{% endif %}> | ||||
| 			<a href="/hypha/{%s hyphaName %}" class="modal__action modal__cancel">Cancel</a> | ||||
| 		</fieldset> | ||||
| 	</form> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
							
								
								
									
										355
									
								
								views/modal.qtpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,355 @@ | ||||
| // Code generated by qtc from "modal.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line views/modal.qtpl:1 | ||||
| package views | ||||
|  | ||||
| //line views/modal.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| //line views/modal.qtpl:2 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| //line views/modal.qtpl:4 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line views/modal.qtpl:4 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line views/modal.qtpl:4 | ||||
| func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line views/modal.qtpl:4 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:5 | ||||
| 	StreamNavHTML(qw422016, rq, hyphaName, "delete-ask") | ||||
| //line views/modal.qtpl:5 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:6 | ||||
| 	streammodalBegin(qw422016, | ||||
| 		"delete-confirm", | ||||
| 		hyphaName, | ||||
| 		"", | ||||
| 		"Delete "+util.BeautifulName(hyphaName)+"?") | ||||
| //line views/modal.qtpl:10 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:11 | ||||
| 	streammodalReallyWant(qw422016, hyphaName, "unattach") | ||||
| //line views/modal.qtpl:11 | ||||
| 	qw422016.N().S(` | ||||
| 		<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p> | ||||
| `) | ||||
| //line views/modal.qtpl:13 | ||||
| 	streammodalEnd(qw422016, hyphaName, true) | ||||
| //line views/modal.qtpl:13 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:14 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:14 | ||||
| func WriteDeleteAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line views/modal.qtpl:14 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/modal.qtpl:14 | ||||
| 	StreamDeleteAskHTML(qw422016, rq, hyphaName, isOld) | ||||
| //line views/modal.qtpl:14 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/modal.qtpl:14 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:14 | ||||
| func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) string { | ||||
| //line views/modal.qtpl:14 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/modal.qtpl:14 | ||||
| 	WriteDeleteAskHTML(qb422016, rq, hyphaName, isOld) | ||||
| //line views/modal.qtpl:14 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/modal.qtpl:14 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/modal.qtpl:14 | ||||
| 	return qs422016 | ||||
| //line views/modal.qtpl:14 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:16 | ||||
| func StreamUnattachAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line views/modal.qtpl:16 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:17 | ||||
| 	StreamNavHTML(qw422016, rq, hyphaName, "unattach-ask") | ||||
| //line views/modal.qtpl:17 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:18 | ||||
| 	streammodalBegin(qw422016, | ||||
| 		"unattach", | ||||
| 		hyphaName, | ||||
| 		"", | ||||
| 		"Unattach "+util.BeautifulName(hyphaName)+"?") | ||||
| //line views/modal.qtpl:22 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:23 | ||||
| 	streammodalReallyWant(qw422016, hyphaName, "unattach") | ||||
| //line views/modal.qtpl:23 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:24 | ||||
| 	streammodalEnd(qw422016, hyphaName, true) | ||||
| //line views/modal.qtpl:24 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:25 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:25 | ||||
| func WriteUnattachAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line views/modal.qtpl:25 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/modal.qtpl:25 | ||||
| 	StreamUnattachAskHTML(qw422016, rq, hyphaName, isOld) | ||||
| //line views/modal.qtpl:25 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/modal.qtpl:25 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:25 | ||||
| func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) string { | ||||
| //line views/modal.qtpl:25 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/modal.qtpl:25 | ||||
| 	WriteUnattachAskHTML(qb422016, rq, hyphaName, isOld) | ||||
| //line views/modal.qtpl:25 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/modal.qtpl:25 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/modal.qtpl:25 | ||||
| 	return qs422016 | ||||
| //line views/modal.qtpl:25 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:27 | ||||
| func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line views/modal.qtpl:27 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:28 | ||||
| 	StreamNavHTML(qw422016, rq, hyphaName, "rename-ask") | ||||
| //line views/modal.qtpl:28 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:29 | ||||
| 	streammodalBegin(qw422016, | ||||
| 		"rename-confirm", | ||||
| 		hyphaName, | ||||
| 		` method="post" enctype="multipart/form-data"`, | ||||
| 		"Rename "+util.BeautifulName(hyphaName)) | ||||
| //line views/modal.qtpl:33 | ||||
| 	qw422016.N().S(` | ||||
| 			<label for="new-name">New name</label> | ||||
| 			<input type="text" value="`) | ||||
| //line views/modal.qtpl:35 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line views/modal.qtpl:35 | ||||
| 	qw422016.N().S(`" required autofocus id="new-name" name="new-name"/> | ||||
|  | ||||
| 			<input type="checkbox" id="recursive" name="recursive" value="true" checked/> | ||||
| 			<label for="recursive">Rename subhyphae too</label> | ||||
|  | ||||
| 			<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p> | ||||
| `) | ||||
| //line views/modal.qtpl:41 | ||||
| 	streammodalEnd(qw422016, hyphaName, false) | ||||
| //line views/modal.qtpl:41 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/modal.qtpl:42 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:42 | ||||
| func WriteRenameAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) { | ||||
| //line views/modal.qtpl:42 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/modal.qtpl:42 | ||||
| 	StreamRenameAskHTML(qw422016, rq, hyphaName, isOld) | ||||
| //line views/modal.qtpl:42 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/modal.qtpl:42 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:42 | ||||
| func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) string { | ||||
| //line views/modal.qtpl:42 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/modal.qtpl:42 | ||||
| 	WriteRenameAskHTML(qb422016, rq, hyphaName, isOld) | ||||
| //line views/modal.qtpl:42 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/modal.qtpl:42 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/modal.qtpl:42 | ||||
| 	return qs422016 | ||||
| //line views/modal.qtpl:42 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:44 | ||||
| func streammodalReallyWant(qw422016 *qt422016.Writer, hyphaName, verb string) { | ||||
| //line views/modal.qtpl:44 | ||||
| 	qw422016.N().S(` | ||||
| 			<p class="modal__confirmation-msg">Do you really want to `) | ||||
| //line views/modal.qtpl:45 | ||||
| 	qw422016.E().S(verb) | ||||
| //line views/modal.qtpl:45 | ||||
| 	qw422016.N().S(` hypha <em>`) | ||||
| //line views/modal.qtpl:45 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line views/modal.qtpl:45 | ||||
| 	qw422016.N().S(`</em>?</p> | ||||
| `) | ||||
| //line views/modal.qtpl:46 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:46 | ||||
| func writemodalReallyWant(qq422016 qtio422016.Writer, hyphaName, verb string) { | ||||
| //line views/modal.qtpl:46 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/modal.qtpl:46 | ||||
| 	streammodalReallyWant(qw422016, hyphaName, verb) | ||||
| //line views/modal.qtpl:46 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/modal.qtpl:46 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:46 | ||||
| func modalReallyWant(hyphaName, verb string) string { | ||||
| //line views/modal.qtpl:46 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/modal.qtpl:46 | ||||
| 	writemodalReallyWant(qb422016, hyphaName, verb) | ||||
| //line views/modal.qtpl:46 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/modal.qtpl:46 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/modal.qtpl:46 | ||||
| 	return qs422016 | ||||
| //line views/modal.qtpl:46 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:48 | ||||
| func streammodalBegin(qw422016 *qt422016.Writer, path, hyphaName, formAttrs, legend string) { | ||||
| //line views/modal.qtpl:48 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<form class="modal" action="/`) | ||||
| //line views/modal.qtpl:51 | ||||
| 	qw422016.E().S(path) | ||||
| //line views/modal.qtpl:51 | ||||
| 	qw422016.N().S(`/`) | ||||
| //line views/modal.qtpl:51 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line views/modal.qtpl:51 | ||||
| 	qw422016.N().S(`"`) | ||||
| //line views/modal.qtpl:51 | ||||
| 	qw422016.N().S(formAttrs) | ||||
| //line views/modal.qtpl:51 | ||||
| 	qw422016.N().S(`> | ||||
| 		<fieldset class="modal__fieldset"> | ||||
| 			<legend class="modal__title">`) | ||||
| //line views/modal.qtpl:53 | ||||
| 	qw422016.N().S(legend) | ||||
| //line views/modal.qtpl:53 | ||||
| 	qw422016.N().S(`</legend> | ||||
| `) | ||||
| //line views/modal.qtpl:54 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:54 | ||||
| func writemodalBegin(qq422016 qtio422016.Writer, path, hyphaName, formAttrs, legend string) { | ||||
| //line views/modal.qtpl:54 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/modal.qtpl:54 | ||||
| 	streammodalBegin(qw422016, path, hyphaName, formAttrs, legend) | ||||
| //line views/modal.qtpl:54 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/modal.qtpl:54 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:54 | ||||
| func modalBegin(path, hyphaName, formAttrs, legend string) string { | ||||
| //line views/modal.qtpl:54 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/modal.qtpl:54 | ||||
| 	writemodalBegin(qb422016, path, hyphaName, formAttrs, legend) | ||||
| //line views/modal.qtpl:54 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/modal.qtpl:54 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/modal.qtpl:54 | ||||
| 	return qs422016 | ||||
| //line views/modal.qtpl:54 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:56 | ||||
| func streammodalEnd(qw422016 *qt422016.Writer, hyphaName string, shouldFocusOnConfirm bool) { | ||||
| //line views/modal.qtpl:56 | ||||
| 	qw422016.N().S(` | ||||
| 			<input type="submit" value="Confirm" class="modal__action modal__submit" `) | ||||
| //line views/modal.qtpl:57 | ||||
| 	if shouldFocusOnConfirm { | ||||
| //line views/modal.qtpl:57 | ||||
| 		qw422016.N().S(`autofocus`) | ||||
| //line views/modal.qtpl:57 | ||||
| 	} | ||||
| //line views/modal.qtpl:57 | ||||
| 	qw422016.N().S(`> | ||||
| 			<a href="/hypha/`) | ||||
| //line views/modal.qtpl:58 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line views/modal.qtpl:58 | ||||
| 	qw422016.N().S(`" class="modal__action modal__cancel">Cancel</a> | ||||
| 		</fieldset> | ||||
| 	</form> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/modal.qtpl:63 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:63 | ||||
| func writemodalEnd(qq422016 qtio422016.Writer, hyphaName string, shouldFocusOnConfirm bool) { | ||||
| //line views/modal.qtpl:63 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/modal.qtpl:63 | ||||
| 	streammodalEnd(qw422016, hyphaName, shouldFocusOnConfirm) | ||||
| //line views/modal.qtpl:63 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/modal.qtpl:63 | ||||
| } | ||||
|  | ||||
| //line views/modal.qtpl:63 | ||||
| func modalEnd(hyphaName string, shouldFocusOnConfirm bool) string { | ||||
| //line views/modal.qtpl:63 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/modal.qtpl:63 | ||||
| 	writemodalEnd(qb422016, hyphaName, shouldFocusOnConfirm) | ||||
| //line views/modal.qtpl:63 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/modal.qtpl:63 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/modal.qtpl:63 | ||||
| 	return qs422016 | ||||
| //line views/modal.qtpl:63 | ||||
| } | ||||
| @@ -1,9 +1,11 @@ | ||||
| {% import "net/http" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
| 
 | ||||
| {% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %} | ||||
| {%s= navHTML(rq, hyphaName, "edit") %} | ||||
| <main class="edit edit_no-preview"> | ||||
| 	<h1>Edit {%s hyphaName %}</h1> | ||||
| {%s= NavHTML(rq, hyphaName, "edit") %} | ||||
| <div class="layout"> | ||||
| <main class="main-width edit edit_no-preview"> | ||||
| 	<h1 class="edit__title">Edit {%s util.BeautifulName(hyphaName) %}</h1> | ||||
| 	{%s= warning %} | ||||
| 	<form method="post" class="edit-form" | ||||
| 			action="/upload-text/{%s hyphaName %}"> | ||||
| @@ -14,12 +16,14 @@ | ||||
| 		<a href="/page/{%s hyphaName %}" class="edit-form__cancel">Cancel</a> | ||||
| 	</form> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
| 
 | ||||
| {% func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) %} | ||||
| {%s= navHTML(rq, hyphaName, "edit") %} | ||||
| <main class="edit edit_with-preview"> | ||||
| 	<h1>Edit {%s hyphaName %} (preview)</h1> | ||||
| {%s= NavHTML(rq, hyphaName, "edit") %} | ||||
| <div class="layout"> | ||||
| <main class="main-width edit edit_with-preview"> | ||||
| 	<h1>Edit {%s util.BeautifulName(hyphaName) %} (preview)</h1> | ||||
| 	{%s= warning %} | ||||
| 	<form method="post" class="edit-form" | ||||
| 			action="/upload-text/{%s hyphaName %}"> | ||||
| @@ -32,4 +36,5 @@ | ||||
| 	<p class="warning">Note that the hypha is not saved yet. You can preview the changes ↓</p> | ||||
| 	<section class="edit__preview">{%s= renderedPage %}</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
| @@ -1,169 +1,176 @@ | ||||
| // Code generated by qtc from "http_mutators.qtpl". DO NOT EDIT. | ||||
| // Code generated by qtc from "mutators.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:1 | ||||
| package templates | ||||
| //line views/mutators.qtpl:1 | ||||
| package views | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:1 | ||||
| //line views/mutators.qtpl:1 | ||||
| import "net/http" | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:3 | ||||
| //line views/mutators.qtpl:2 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
| 
 | ||||
| //line views/mutators.qtpl:4 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
| 
 | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:3 | ||||
| //line views/mutators.qtpl:4 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:3 | ||||
| //line views/mutators.qtpl:4 | ||||
| func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) { | ||||
| //line templates/http_mutators.qtpl:3 | ||||
| //line views/mutators.qtpl:4 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/http_mutators.qtpl:4 | ||||
| 	qw422016.N().S(navHTML(rq, hyphaName, "edit")) | ||||
| //line templates/http_mutators.qtpl:4 | ||||
| //line views/mutators.qtpl:5 | ||||
| 	qw422016.N().S(NavHTML(rq, hyphaName, "edit")) | ||||
| //line views/mutators.qtpl:5 | ||||
| 	qw422016.N().S(` | ||||
| <main class="edit edit_no-preview"> | ||||
| 	<h1>Edit `) | ||||
| //line templates/http_mutators.qtpl:6 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_mutators.qtpl:6 | ||||
| <div class="layout"> | ||||
| <main class="main-width edit edit_no-preview"> | ||||
| 	<h1 class="edit__title">Edit `) | ||||
| //line views/mutators.qtpl:8 | ||||
| 	qw422016.E().S(util.BeautifulName(hyphaName)) | ||||
| //line views/mutators.qtpl:8 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 	`) | ||||
| //line templates/http_mutators.qtpl:7 | ||||
| //line views/mutators.qtpl:9 | ||||
| 	qw422016.N().S(warning) | ||||
| //line templates/http_mutators.qtpl:7 | ||||
| //line views/mutators.qtpl:9 | ||||
| 	qw422016.N().S(` | ||||
| 	<form method="post" class="edit-form" | ||||
| 			action="/upload-text/`) | ||||
| //line templates/http_mutators.qtpl:9 | ||||
| //line views/mutators.qtpl:11 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_mutators.qtpl:9 | ||||
| //line views/mutators.qtpl:11 | ||||
| 	qw422016.N().S(`"> | ||||
| 		<textarea name="text">`) | ||||
| //line templates/http_mutators.qtpl:10 | ||||
| //line views/mutators.qtpl:12 | ||||
| 	qw422016.E().S(textAreaFill) | ||||
| //line templates/http_mutators.qtpl:10 | ||||
| //line views/mutators.qtpl:12 | ||||
| 	qw422016.N().S(`</textarea> | ||||
| 		<br/> | ||||
| 		<input type="submit" name="action" value="Save" class="edit-form__save"/> | ||||
| 		<input type="submit" name="action" value="Preview" class="edit-form__preview"> | ||||
| 		<a href="/page/`) | ||||
| //line templates/http_mutators.qtpl:14 | ||||
| //line views/mutators.qtpl:16 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_mutators.qtpl:14 | ||||
| //line views/mutators.qtpl:16 | ||||
| 	qw422016.N().S(`" class="edit-form__cancel">Cancel</a> | ||||
| 	</form> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| } | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) { | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning) | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| } | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string { | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning) | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| 	return qs422016 | ||||
| //line templates/http_mutators.qtpl:17 | ||||
| //line views/mutators.qtpl:20 | ||||
| } | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:19 | ||||
| //line views/mutators.qtpl:22 | ||||
| func StreamPreviewHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) { | ||||
| //line templates/http_mutators.qtpl:19 | ||||
| //line views/mutators.qtpl:22 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line templates/http_mutators.qtpl:20 | ||||
| 	qw422016.N().S(navHTML(rq, hyphaName, "edit")) | ||||
| //line templates/http_mutators.qtpl:20 | ||||
| //line views/mutators.qtpl:23 | ||||
| 	qw422016.N().S(NavHTML(rq, hyphaName, "edit")) | ||||
| //line views/mutators.qtpl:23 | ||||
| 	qw422016.N().S(` | ||||
| <main class="edit edit_with-preview"> | ||||
| <div class="layout"> | ||||
| <main class="main-width edit edit_with-preview"> | ||||
| 	<h1>Edit `) | ||||
| //line templates/http_mutators.qtpl:22 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_mutators.qtpl:22 | ||||
| //line views/mutators.qtpl:26 | ||||
| 	qw422016.E().S(util.BeautifulName(hyphaName)) | ||||
| //line views/mutators.qtpl:26 | ||||
| 	qw422016.N().S(` (preview)</h1> | ||||
| 	`) | ||||
| //line templates/http_mutators.qtpl:23 | ||||
| //line views/mutators.qtpl:27 | ||||
| 	qw422016.N().S(warning) | ||||
| //line templates/http_mutators.qtpl:23 | ||||
| //line views/mutators.qtpl:27 | ||||
| 	qw422016.N().S(` | ||||
| 	<form method="post" class="edit-form" | ||||
| 			action="/upload-text/`) | ||||
| //line templates/http_mutators.qtpl:25 | ||||
| //line views/mutators.qtpl:29 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_mutators.qtpl:25 | ||||
| //line views/mutators.qtpl:29 | ||||
| 	qw422016.N().S(`"> | ||||
| 		<textarea name="text">`) | ||||
| //line templates/http_mutators.qtpl:26 | ||||
| //line views/mutators.qtpl:30 | ||||
| 	qw422016.E().S(textAreaFill) | ||||
| //line templates/http_mutators.qtpl:26 | ||||
| //line views/mutators.qtpl:30 | ||||
| 	qw422016.N().S(`</textarea> | ||||
| 		<br/> | ||||
| 		<input type="submit" name="action" value="Save" class="edit-form__save"/> | ||||
| 		<input type="submit" name="action" value="Preview" class="edit-form__preview"> | ||||
| 		<a href="/page/`) | ||||
| //line templates/http_mutators.qtpl:30 | ||||
| //line views/mutators.qtpl:34 | ||||
| 	qw422016.E().S(hyphaName) | ||||
| //line templates/http_mutators.qtpl:30 | ||||
| //line views/mutators.qtpl:34 | ||||
| 	qw422016.N().S(`" class="edit-form__cancel">Cancel</a> | ||||
| 	</form> | ||||
| 	<p class="warning">Note that the hypha is not saved yet. You can preview the changes ↓</p> | ||||
| 	<section class="edit__preview">`) | ||||
| //line templates/http_mutators.qtpl:33 | ||||
| //line views/mutators.qtpl:37 | ||||
| 	qw422016.N().S(renderedPage) | ||||
| //line templates/http_mutators.qtpl:33 | ||||
| //line views/mutators.qtpl:37 | ||||
| 	qw422016.N().S(`</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| } | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| func WritePreviewHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) { | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	StreamPreviewHTML(qw422016, rq, hyphaName, textAreaFill, warning, renderedPage) | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| } | ||||
| 
 | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) string { | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	WritePreviewHTML(qb422016, rq, hyphaName, textAreaFill, warning, renderedPage) | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| 	return qs422016 | ||||
| //line templates/http_mutators.qtpl:35 | ||||
| //line views/mutators.qtpl:40 | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| {% import "net/http" %} | ||||
| {% import "strings" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/user" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
| 
 | ||||
| @@ -11,33 +12,33 @@ type navEntry struct { | ||||
| var navEntries = []navEntry{ | ||||
| 	{"page", "Hypha"}, | ||||
| 	{"edit", "Edit"}, | ||||
| 	{"text", "Raw text"}, | ||||
| 	{"attachment", "Attachment"}, | ||||
| 	{"history", "History"}, | ||||
| 	{"revision", "NOT REACHED"}, | ||||
| 	{"rename-ask", "Rename"}, | ||||
| 	{"delete-ask", "Delete"}, | ||||
| 	{"text", "Raw text"}, | ||||
| } | ||||
| %} | ||||
| 
 | ||||
| {% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %} | ||||
| {% func NavHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %} | ||||
| {% code  | ||||
| 	u := user.FromRequest(rq) | ||||
| %} | ||||
| 
 | ||||
| 	<nav class="hypha-tabs"> | ||||
| 	<nav class="hypha-tabs main-width"> | ||||
| 		<ul class="hypha-tabs__flex"> | ||||
| 		{%- for _, entry := range navEntries -%} | ||||
| 		{%- if navType == "revision" && entry.path == "revision" -%} | ||||
| 			<li class="hypha-tabs__tab hypha-tabs__tab_active"> | ||||
| 				{%s revisionHash[0] %} | ||||
| 				<span class="hypha-tabs__selection">{%s revisionHash[0] %}</span> | ||||
| 			</li> | ||||
| 		{%- elseif navType == entry.path -%} | ||||
| 			<li class="hypha-tabs__tab hypha-tabs__tab_active"> | ||||
| 				{%s entry.title %} | ||||
| 				<span class="hypha-tabs__selection">{%s entry.title %}</span> | ||||
| 			</li> | ||||
| 		{%- elseif entry.path != "revision" && u.CanProceed(entry.path) -%} | ||||
| 			<li class="hypha-tabs__tab"> | ||||
| 				<a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a> | ||||
| 				<a class="hypha-tabs__link" href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a> | ||||
| 			</li> | ||||
| 		{%- endif -%} | ||||
| 		{%- endfor -%} | ||||
| @@ -45,7 +46,7 @@ var navEntries = []navEntry{ | ||||
| 	</nav> | ||||
| {% endfunc %} | ||||
| 
 | ||||
| {% func userMenuHTML(u *user.User) %} | ||||
| {% func UserMenuHTML(u *user.User) %} | ||||
| {% if user.AuthUsed %} | ||||
| <li class="header-links__entry header-links__entry_user"> | ||||
| 	{% if u.Group == "anon" %} | ||||
| @@ -57,3 +58,22 @@ var navEntries = []navEntry{ | ||||
| {% endif %} | ||||
| {% endfunc %} | ||||
| 
 | ||||
| {% func RelativeHyphaeHTML(relatives string) %} | ||||
| <aside class="relative-hyphae layout-card"> | ||||
| 	<h2 class="relative-hyphae__title layout-card__title">Relative hyphae</h2> | ||||
| 	{%s= relatives %} | ||||
| </aside> | ||||
| {% endfunc %} | ||||
| 
 | ||||
| {% func SubhyphaeHTML(subhyphae string) %} | ||||
| {% if strings.TrimSpace(subhyphae) != "" %} | ||||
| <section class="subhyphae"> | ||||
| 	<h2 class="subhyphae__title">Subhyphae</h2> | ||||
| 	<nav class="subhyphae__nav"> | ||||
| 		<ul class="subhyphae__list"> | ||||
| 		{%s= subhyphae %} | ||||
| 		</ul> | ||||
| 	</nav> | ||||
| </section> | ||||
| {% endif %} | ||||
| {% endfunc %} | ||||
							
								
								
									
										315
									
								
								views/nav.qtpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,315 @@ | ||||
| // Code generated by qtc from "nav.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line views/nav.qtpl:1 | ||||
| package views | ||||
|  | ||||
| //line views/nav.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| //line views/nav.qtpl:2 | ||||
| import "strings" | ||||
|  | ||||
| //line views/nav.qtpl:3 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line views/nav.qtpl:4 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| // This is the <nav> seen on top of many pages. | ||||
|  | ||||
| //line views/nav.qtpl:7 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line views/nav.qtpl:7 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line views/nav.qtpl:8 | ||||
| type navEntry struct { | ||||
| 	path  string | ||||
| 	title string | ||||
| } | ||||
|  | ||||
| var navEntries = []navEntry{ | ||||
| 	{"page", "Hypha"}, | ||||
| 	{"edit", "Edit"}, | ||||
| 	{"attachment", "Attachment"}, | ||||
| 	{"history", "History"}, | ||||
| 	{"revision", "NOT REACHED"}, | ||||
| 	{"rename-ask", "Rename"}, | ||||
| 	{"delete-ask", "Delete"}, | ||||
| 	{"text", "Raw text"}, | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:24 | ||||
| func StreamNavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) { | ||||
| //line views/nav.qtpl:24 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/nav.qtpl:26 | ||||
| 	u := user.FromRequest(rq) | ||||
|  | ||||
| //line views/nav.qtpl:27 | ||||
| 	qw422016.N().S(` | ||||
| 	<nav class="hypha-tabs main-width"> | ||||
| 		<ul class="hypha-tabs__flex"> | ||||
| `) | ||||
| //line views/nav.qtpl:30 | ||||
| 	for _, entry := range navEntries { | ||||
| //line views/nav.qtpl:31 | ||||
| 		if navType == "revision" && entry.path == "revision" { | ||||
| //line views/nav.qtpl:31 | ||||
| 			qw422016.N().S(`			<li class="hypha-tabs__tab hypha-tabs__tab_active"> | ||||
| 				<span class="hypha-tabs__selection">`) | ||||
| //line views/nav.qtpl:33 | ||||
| 			qw422016.E().S(revisionHash[0]) | ||||
| //line views/nav.qtpl:33 | ||||
| 			qw422016.N().S(`</span> | ||||
| 			</li> | ||||
| `) | ||||
| //line views/nav.qtpl:35 | ||||
| 		} else if navType == entry.path { | ||||
| //line views/nav.qtpl:35 | ||||
| 			qw422016.N().S(`			<li class="hypha-tabs__tab hypha-tabs__tab_active"> | ||||
| 				<span class="hypha-tabs__selection">`) | ||||
| //line views/nav.qtpl:37 | ||||
| 			qw422016.E().S(entry.title) | ||||
| //line views/nav.qtpl:37 | ||||
| 			qw422016.N().S(`</span> | ||||
| 			</li> | ||||
| `) | ||||
| //line views/nav.qtpl:39 | ||||
| 		} else if entry.path != "revision" && u.CanProceed(entry.path) { | ||||
| //line views/nav.qtpl:39 | ||||
| 			qw422016.N().S(`			<li class="hypha-tabs__tab"> | ||||
| 				<a class="hypha-tabs__link" href="/`) | ||||
| //line views/nav.qtpl:41 | ||||
| 			qw422016.E().S(entry.path) | ||||
| //line views/nav.qtpl:41 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line views/nav.qtpl:41 | ||||
| 			qw422016.E().S(hyphaName) | ||||
| //line views/nav.qtpl:41 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line views/nav.qtpl:41 | ||||
| 			qw422016.E().S(entry.title) | ||||
| //line views/nav.qtpl:41 | ||||
| 			qw422016.N().S(`</a> | ||||
| 			</li> | ||||
| `) | ||||
| //line views/nav.qtpl:43 | ||||
| 		} | ||||
| //line views/nav.qtpl:44 | ||||
| 	} | ||||
| //line views/nav.qtpl:44 | ||||
| 	qw422016.N().S(`		</ul> | ||||
| 	</nav> | ||||
| `) | ||||
| //line views/nav.qtpl:47 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:47 | ||||
| func WriteNavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) { | ||||
| //line views/nav.qtpl:47 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/nav.qtpl:47 | ||||
| 	StreamNavHTML(qw422016, rq, hyphaName, navType, revisionHash...) | ||||
| //line views/nav.qtpl:47 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/nav.qtpl:47 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:47 | ||||
| func NavHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string { | ||||
| //line views/nav.qtpl:47 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/nav.qtpl:47 | ||||
| 	WriteNavHTML(qb422016, rq, hyphaName, navType, revisionHash...) | ||||
| //line views/nav.qtpl:47 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/nav.qtpl:47 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/nav.qtpl:47 | ||||
| 	return qs422016 | ||||
| //line views/nav.qtpl:47 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:49 | ||||
| func StreamUserMenuHTML(qw422016 *qt422016.Writer, u *user.User) { | ||||
| //line views/nav.qtpl:49 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/nav.qtpl:50 | ||||
| 	if user.AuthUsed { | ||||
| //line views/nav.qtpl:50 | ||||
| 		qw422016.N().S(` | ||||
| <li class="header-links__entry header-links__entry_user"> | ||||
| 	`) | ||||
| //line views/nav.qtpl:52 | ||||
| 		if u.Group == "anon" { | ||||
| //line views/nav.qtpl:52 | ||||
| 			qw422016.N().S(` | ||||
| 	<a href="/login" class="header-links__link">Login</a> | ||||
| 	`) | ||||
| //line views/nav.qtpl:54 | ||||
| 		} else { | ||||
| //line views/nav.qtpl:54 | ||||
| 			qw422016.N().S(` | ||||
| 	<a href="/page/`) | ||||
| //line views/nav.qtpl:55 | ||||
| 			qw422016.E().S(util.UserHypha) | ||||
| //line views/nav.qtpl:55 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line views/nav.qtpl:55 | ||||
| 			qw422016.E().S(u.Name) | ||||
| //line views/nav.qtpl:55 | ||||
| 			qw422016.N().S(`" class="header-links__link">`) | ||||
| //line views/nav.qtpl:55 | ||||
| 			qw422016.E().S(u.Name) | ||||
| //line views/nav.qtpl:55 | ||||
| 			qw422016.N().S(`</a> | ||||
| 	`) | ||||
| //line views/nav.qtpl:56 | ||||
| 		} | ||||
| //line views/nav.qtpl:56 | ||||
| 		qw422016.N().S(` | ||||
| </li> | ||||
| `) | ||||
| //line views/nav.qtpl:58 | ||||
| 	} | ||||
| //line views/nav.qtpl:58 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/nav.qtpl:59 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:59 | ||||
| func WriteUserMenuHTML(qq422016 qtio422016.Writer, u *user.User) { | ||||
| //line views/nav.qtpl:59 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/nav.qtpl:59 | ||||
| 	StreamUserMenuHTML(qw422016, u) | ||||
| //line views/nav.qtpl:59 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/nav.qtpl:59 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:59 | ||||
| func UserMenuHTML(u *user.User) string { | ||||
| //line views/nav.qtpl:59 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/nav.qtpl:59 | ||||
| 	WriteUserMenuHTML(qb422016, u) | ||||
| //line views/nav.qtpl:59 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/nav.qtpl:59 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/nav.qtpl:59 | ||||
| 	return qs422016 | ||||
| //line views/nav.qtpl:59 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:61 | ||||
| func StreamRelativeHyphaeHTML(qw422016 *qt422016.Writer, relatives string) { | ||||
| //line views/nav.qtpl:61 | ||||
| 	qw422016.N().S(` | ||||
| <aside class="relative-hyphae layout-card"> | ||||
| 	<h2 class="relative-hyphae__title layout-card__title">Relative hyphae</h2> | ||||
| 	`) | ||||
| //line views/nav.qtpl:64 | ||||
| 	qw422016.N().S(relatives) | ||||
| //line views/nav.qtpl:64 | ||||
| 	qw422016.N().S(` | ||||
| </aside> | ||||
| `) | ||||
| //line views/nav.qtpl:66 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:66 | ||||
| func WriteRelativeHyphaeHTML(qq422016 qtio422016.Writer, relatives string) { | ||||
| //line views/nav.qtpl:66 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/nav.qtpl:66 | ||||
| 	StreamRelativeHyphaeHTML(qw422016, relatives) | ||||
| //line views/nav.qtpl:66 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/nav.qtpl:66 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:66 | ||||
| func RelativeHyphaeHTML(relatives string) string { | ||||
| //line views/nav.qtpl:66 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/nav.qtpl:66 | ||||
| 	WriteRelativeHyphaeHTML(qb422016, relatives) | ||||
| //line views/nav.qtpl:66 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/nav.qtpl:66 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/nav.qtpl:66 | ||||
| 	return qs422016 | ||||
| //line views/nav.qtpl:66 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:68 | ||||
| func StreamSubhyphaeHTML(qw422016 *qt422016.Writer, subhyphae string) { | ||||
| //line views/nav.qtpl:68 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/nav.qtpl:69 | ||||
| 	if strings.TrimSpace(subhyphae) != "" { | ||||
| //line views/nav.qtpl:69 | ||||
| 		qw422016.N().S(` | ||||
| <section class="subhyphae"> | ||||
| 	<h2 class="subhyphae__title">Subhyphae</h2> | ||||
| 	<nav class="subhyphae__nav"> | ||||
| 		<ul class="subhyphae__list"> | ||||
| 		`) | ||||
| //line views/nav.qtpl:74 | ||||
| 		qw422016.N().S(subhyphae) | ||||
| //line views/nav.qtpl:74 | ||||
| 		qw422016.N().S(` | ||||
| 		</ul> | ||||
| 	</nav> | ||||
| </section> | ||||
| `) | ||||
| //line views/nav.qtpl:78 | ||||
| 	} | ||||
| //line views/nav.qtpl:78 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/nav.qtpl:79 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:79 | ||||
| func WriteSubhyphaeHTML(qq422016 qtio422016.Writer, subhyphae string) { | ||||
| //line views/nav.qtpl:79 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/nav.qtpl:79 | ||||
| 	StreamSubhyphaeHTML(qw422016, subhyphae) | ||||
| //line views/nav.qtpl:79 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/nav.qtpl:79 | ||||
| } | ||||
|  | ||||
| //line views/nav.qtpl:79 | ||||
| func SubhyphaeHTML(subhyphae string) string { | ||||
| //line views/nav.qtpl:79 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/nav.qtpl:79 | ||||
| 	WriteSubhyphaeHTML(qb422016, subhyphae) | ||||
| //line views/nav.qtpl:79 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/nav.qtpl:79 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/nav.qtpl:79 | ||||
| 	return qs422016 | ||||
| //line views/nav.qtpl:79 | ||||
| } | ||||
							
								
								
									
										131
									
								
								views/readers.qtpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,131 @@ | ||||
| {% import "net/http" %} | ||||
| {% import "strings" %} | ||||
| {% import "path" %} | ||||
| {% import "os" %} | ||||
|  | ||||
| {% import "github.com/bouncepaw/mycorrhiza/hyphae" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/mimetype" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/tree" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/user" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
|  | ||||
| {% func AttachmentMenuHTML(rq *http.Request, h *hyphae.Hypha, u *user.User) %} | ||||
| {%= NavHTML(rq, h.Name, "attachment") %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<h1>Attachment of {%s util.BeautifulName(h.Name) %}</h1> | ||||
| 	{% if h.BinaryPath == "" %} | ||||
| 	<p class="warning">This hypha has no attachment, you can upload it here.</p> | ||||
| 	{% else %} | ||||
| 	<p class="warning">You can manage the hypha's attachment on this page.</p> | ||||
| 	{% endif %} | ||||
|  | ||||
| 	<section class="amnt-grid"> | ||||
|  | ||||
| 	{% if h.BinaryPath != "" %} | ||||
| 		{% code | ||||
| 			mime := mimetype.FromExtension(path.Ext(h.BinaryPath)) | ||||
| 			fileinfo, err := os.Stat(h.BinaryPath) %} | ||||
| 		{% if err == nil %} | ||||
| 		<fieldset class="amnt-menu-block"> | ||||
| 			<legend class="modal__title modal__title_small">Stat</legend> | ||||
| 			<p class="modal__confirmation-msg"><b>File size:</b> {%dl fileinfo.Size() %} bytes</p> | ||||
| 			<p><b>MIME type:</b> {%s mime %}</p> | ||||
| 		</fieldset> | ||||
| 		{% endif %} | ||||
|  | ||||
| 		{% if strings.HasPrefix(mime, "image/") %} | ||||
| 		<fieldset class="amnt-menu-block"> | ||||
| 			<legend class="modal__title modal__title_small">Include</legend> | ||||
| 			<p class="modal__confirmation-msg">This attachment is an image. To include it n a hypha, use a syntax like this:</p> | ||||
| 			<pre class="codebleck"><code>img { {%s h.Name %} }</code></pre> | ||||
| 		</fieldset> | ||||
| 		{% endif %} | ||||
| 	{% endif %} | ||||
|  | ||||
| 	{% if u.CanProceed("upload-binary") %} | ||||
| 	<form action="/upload-binary/{%s h.Name %}" | ||||
| 			method="post" enctype="multipart/form-data" | ||||
| 			class="modal amnt-menu-block"> | ||||
| 		<fieldset class="modal__fieldset upload-binary"> | ||||
| 			<legend class="modal__title modal__title_small">Attach</legend> | ||||
| 			<p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p> | ||||
| 			<input type="file" class="upload-binary__input" name="binary"> | ||||
| 			<input type="submit" class="modal__action modal__submit"> | ||||
| 		</fieldset> | ||||
| 	</form> | ||||
| 	{% endif %} | ||||
|  | ||||
| 	{% if h.BinaryPath != "" && u.CanProceed("unattach-confirm") %} | ||||
| 	<form action="/unattach-confirm/{%s h.Name %}" method="post" class="modal amnt-menu-block"> | ||||
| 		<fieldset class="modal__fieldset"> | ||||
| 			<legend class="modal__title modal__title_small">Unattach</legend> | ||||
| 			<p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p> | ||||
| 			<input type="submit" class="modal__action modal__submit"> | ||||
| 		</fieldset> | ||||
| 	</form> | ||||
| 	{% endif %} | ||||
|  | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
|  | ||||
| If `contents` == "", a helpful message is shown instead. | ||||
| {% func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) %} | ||||
| {% code | ||||
| 	relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name) | ||||
| %} | ||||
| {%= NavHTML(rq, h.Name, "page") %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<article> | ||||
| 		{%s= NaviTitleHTML(h) %} | ||||
| 		{% if contents == "" %} | ||||
| 			<p>This hypha has no text. Why not <a href="/edit/{%s h.Name %}">create it</a>?</p> | ||||
| 		{% if u := user.FromRequest(rq); (!user.AuthUsed || u.Group != "anon") && !h.Exists %} | ||||
| 			<form action="/upload-binary/{%s h.Name %}" | ||||
| 					method="post" enctype="multipart/form-data" | ||||
| 					class="upload-binary"> | ||||
| 				<label for="upload-binary__input">Upload an attachment:</label> | ||||
| 				<input type="file" id="upload-binary__input" name="binary"> | ||||
| 				<input type="submit"> | ||||
| 			</form> | ||||
| 			<br> | ||||
| 		{% endif %} | ||||
| 		{% else %} | ||||
| 			{%s= contents %} | ||||
| 		{% endif %} | ||||
| 	</article> | ||||
| 	<section class="prevnext"> | ||||
| 		{% if prevHyphaName != "" %} | ||||
| 		<a class="prevnext__el prevnext__prev" href="/hypha/{%s prevHyphaName %}" rel="prev">← {%s util.BeautifulName(path.Base(prevHyphaName)) %}</a> | ||||
| 		{% endif %} | ||||
| 		{% if nextHyphaName != "" %} | ||||
| 		<a class="prevnext__el prevnext__next" href="/hypha/{%s nextHyphaName %}" rel="next">{%s util.BeautifulName(path.Base(nextHyphaName)) %} →</a> | ||||
| 		{% endif %} | ||||
| 	</section> | ||||
| {%= SubhyphaeHTML(subhyphae) %} | ||||
| </main> | ||||
| {%= RelativeHyphaeHTML(relatives) %} | ||||
| {%= BackLinksHTML(h) %} | ||||
| </div> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) %} | ||||
| {% code | ||||
| 	relatives, subhyphae, _, _ := tree.Tree(h.Name) | ||||
| %} | ||||
| {%= NavHTML(rq, h.Name, "revision", revHash) %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<article> | ||||
| 		<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p> | ||||
| 		{%s= NaviTitleHTML(h) %} | ||||
| 		{%s= contents %} | ||||
| 	</article> | ||||
| {%= SubhyphaeHTML(subhyphae) %} | ||||
| </main> | ||||
| {%= RelativeHyphaeHTML(relatives) %} | ||||
| </div> | ||||
| {% endfunc %} | ||||
							
								
								
									
										454
									
								
								views/readers.qtpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,454 @@ | ||||
| // Code generated by qtc from "readers.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line views/readers.qtpl:1 | ||||
| package views | ||||
|  | ||||
| //line views/readers.qtpl:1 | ||||
| import "net/http" | ||||
|  | ||||
| //line views/readers.qtpl:2 | ||||
| import "strings" | ||||
|  | ||||
| //line views/readers.qtpl:3 | ||||
| import "path" | ||||
|  | ||||
| //line views/readers.qtpl:4 | ||||
| import "os" | ||||
|  | ||||
| //line views/readers.qtpl:6 | ||||
| import "github.com/bouncepaw/mycorrhiza/hyphae" | ||||
|  | ||||
| //line views/readers.qtpl:7 | ||||
| import "github.com/bouncepaw/mycorrhiza/mimetype" | ||||
|  | ||||
| //line views/readers.qtpl:8 | ||||
| import "github.com/bouncepaw/mycorrhiza/tree" | ||||
|  | ||||
| //line views/readers.qtpl:9 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line views/readers.qtpl:10 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| //line views/readers.qtpl:12 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line views/readers.qtpl:12 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line views/readers.qtpl:12 | ||||
| func StreamAttachmentMenuHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) { | ||||
| //line views/readers.qtpl:12 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/readers.qtpl:13 | ||||
| 	StreamNavHTML(qw422016, rq, h.Name, "attachment") | ||||
| //line views/readers.qtpl:13 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<h1>Attachment of `) | ||||
| //line views/readers.qtpl:16 | ||||
| 	qw422016.E().S(util.BeautifulName(h.Name)) | ||||
| //line views/readers.qtpl:16 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 	`) | ||||
| //line views/readers.qtpl:17 | ||||
| 	if h.BinaryPath == "" { | ||||
| //line views/readers.qtpl:17 | ||||
| 		qw422016.N().S(` | ||||
| 	<p class="warning">This hypha has no attachment, you can upload it here.</p> | ||||
| 	`) | ||||
| //line views/readers.qtpl:19 | ||||
| 	} else { | ||||
| //line views/readers.qtpl:19 | ||||
| 		qw422016.N().S(` | ||||
| 	<p class="warning">You can manage the hypha's attachment on this page.</p> | ||||
| 	`) | ||||
| //line views/readers.qtpl:21 | ||||
| 	} | ||||
| //line views/readers.qtpl:21 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	<section class="amnt-grid"> | ||||
|  | ||||
| 	`) | ||||
| //line views/readers.qtpl:25 | ||||
| 	if h.BinaryPath != "" { | ||||
| //line views/readers.qtpl:25 | ||||
| 		qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/readers.qtpl:27 | ||||
| 		mime := mimetype.FromExtension(path.Ext(h.BinaryPath)) | ||||
| 		fileinfo, err := os.Stat(h.BinaryPath) | ||||
|  | ||||
| //line views/readers.qtpl:28 | ||||
| 		qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/readers.qtpl:29 | ||||
| 		if err == nil { | ||||
| //line views/readers.qtpl:29 | ||||
| 			qw422016.N().S(` | ||||
| 		<fieldset class="amnt-menu-block"> | ||||
| 			<legend class="modal__title modal__title_small">Stat</legend> | ||||
| 			<p class="modal__confirmation-msg"><b>File size:</b> `) | ||||
| //line views/readers.qtpl:32 | ||||
| 			qw422016.N().DL(fileinfo.Size()) | ||||
| //line views/readers.qtpl:32 | ||||
| 			qw422016.N().S(` bytes</p> | ||||
| 			<p><b>MIME type:</b> `) | ||||
| //line views/readers.qtpl:33 | ||||
| 			qw422016.E().S(mime) | ||||
| //line views/readers.qtpl:33 | ||||
| 			qw422016.N().S(`</p> | ||||
| 		</fieldset> | ||||
| 		`) | ||||
| //line views/readers.qtpl:35 | ||||
| 		} | ||||
| //line views/readers.qtpl:35 | ||||
| 		qw422016.N().S(` | ||||
|  | ||||
| 		`) | ||||
| //line views/readers.qtpl:37 | ||||
| 		if strings.HasPrefix(mime, "image/") { | ||||
| //line views/readers.qtpl:37 | ||||
| 			qw422016.N().S(` | ||||
| 		<fieldset class="amnt-menu-block"> | ||||
| 			<legend class="modal__title modal__title_small">Include</legend> | ||||
| 			<p class="modal__confirmation-msg">This attachment is an image. To include it n a hypha, use a syntax like this:</p> | ||||
| 			<pre class="codebleck"><code>img { `) | ||||
| //line views/readers.qtpl:41 | ||||
| 			qw422016.E().S(h.Name) | ||||
| //line views/readers.qtpl:41 | ||||
| 			qw422016.N().S(` }</code></pre> | ||||
| 		</fieldset> | ||||
| 		`) | ||||
| //line views/readers.qtpl:43 | ||||
| 		} | ||||
| //line views/readers.qtpl:43 | ||||
| 		qw422016.N().S(` | ||||
| 	`) | ||||
| //line views/readers.qtpl:44 | ||||
| 	} | ||||
| //line views/readers.qtpl:44 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	`) | ||||
| //line views/readers.qtpl:46 | ||||
| 	if u.CanProceed("upload-binary") { | ||||
| //line views/readers.qtpl:46 | ||||
| 		qw422016.N().S(` | ||||
| 	<form action="/upload-binary/`) | ||||
| //line views/readers.qtpl:47 | ||||
| 		qw422016.E().S(h.Name) | ||||
| //line views/readers.qtpl:47 | ||||
| 		qw422016.N().S(`" | ||||
| 			method="post" enctype="multipart/form-data" | ||||
| 			class="modal amnt-menu-block"> | ||||
| 		<fieldset class="modal__fieldset upload-binary"> | ||||
| 			<legend class="modal__title modal__title_small">Attach</legend> | ||||
| 			<p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p> | ||||
| 			<input type="file" class="upload-binary__input" name="binary"> | ||||
| 			<input type="submit" class="modal__action modal__submit"> | ||||
| 		</fieldset> | ||||
| 	</form> | ||||
| 	`) | ||||
| //line views/readers.qtpl:57 | ||||
| 	} | ||||
| //line views/readers.qtpl:57 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	`) | ||||
| //line views/readers.qtpl:59 | ||||
| 	if h.BinaryPath != "" && u.CanProceed("unattach-confirm") { | ||||
| //line views/readers.qtpl:59 | ||||
| 		qw422016.N().S(` | ||||
| 	<form action="/unattach-confirm/`) | ||||
| //line views/readers.qtpl:60 | ||||
| 		qw422016.E().S(h.Name) | ||||
| //line views/readers.qtpl:60 | ||||
| 		qw422016.N().S(`" method="post" class="modal amnt-menu-block"> | ||||
| 		<fieldset class="modal__fieldset"> | ||||
| 			<legend class="modal__title modal__title_small">Unattach</legend> | ||||
| 			<p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p> | ||||
| 			<input type="submit" class="modal__action modal__submit"> | ||||
| 		</fieldset> | ||||
| 	</form> | ||||
| 	`) | ||||
| //line views/readers.qtpl:67 | ||||
| 	} | ||||
| //line views/readers.qtpl:67 | ||||
| 	qw422016.N().S(` | ||||
|  | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/readers.qtpl:72 | ||||
| } | ||||
|  | ||||
| //line views/readers.qtpl:72 | ||||
| func WriteAttachmentMenuHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) { | ||||
| //line views/readers.qtpl:72 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/readers.qtpl:72 | ||||
| 	StreamAttachmentMenuHTML(qw422016, rq, h, u) | ||||
| //line views/readers.qtpl:72 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/readers.qtpl:72 | ||||
| } | ||||
|  | ||||
| //line views/readers.qtpl:72 | ||||
| func AttachmentMenuHTML(rq *http.Request, h *hyphae.Hypha, u *user.User) string { | ||||
| //line views/readers.qtpl:72 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/readers.qtpl:72 | ||||
| 	WriteAttachmentMenuHTML(qb422016, rq, h, u) | ||||
| //line views/readers.qtpl:72 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/readers.qtpl:72 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/readers.qtpl:72 | ||||
| 	return qs422016 | ||||
| //line views/readers.qtpl:72 | ||||
| } | ||||
|  | ||||
| // If `contents` == "", a helpful message is shown instead. | ||||
|  | ||||
| //line views/readers.qtpl:75 | ||||
| func StreamHyphaHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) { | ||||
| //line views/readers.qtpl:75 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/readers.qtpl:77 | ||||
| 	relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name) | ||||
|  | ||||
| //line views/readers.qtpl:78 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/readers.qtpl:79 | ||||
| 	StreamNavHTML(qw422016, rq, h.Name, "page") | ||||
| //line views/readers.qtpl:79 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<article> | ||||
| 		`) | ||||
| //line views/readers.qtpl:83 | ||||
| 	qw422016.N().S(NaviTitleHTML(h)) | ||||
| //line views/readers.qtpl:83 | ||||
| 	qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/readers.qtpl:84 | ||||
| 	if contents == "" { | ||||
| //line views/readers.qtpl:84 | ||||
| 		qw422016.N().S(` | ||||
| 			<p>This hypha has no text. Why not <a href="/edit/`) | ||||
| //line views/readers.qtpl:85 | ||||
| 		qw422016.E().S(h.Name) | ||||
| //line views/readers.qtpl:85 | ||||
| 		qw422016.N().S(`">create it</a>?</p> | ||||
| 		`) | ||||
| //line views/readers.qtpl:86 | ||||
| 		if u := user.FromRequest(rq); (!user.AuthUsed || u.Group != "anon") && !h.Exists { | ||||
| //line views/readers.qtpl:86 | ||||
| 			qw422016.N().S(` | ||||
| 			<form action="/upload-binary/`) | ||||
| //line views/readers.qtpl:87 | ||||
| 			qw422016.E().S(h.Name) | ||||
| //line views/readers.qtpl:87 | ||||
| 			qw422016.N().S(`" | ||||
| 					method="post" enctype="multipart/form-data" | ||||
| 					class="upload-binary"> | ||||
| 				<label for="upload-binary__input">Upload an attachment:</label> | ||||
| 				<input type="file" id="upload-binary__input" name="binary"> | ||||
| 				<input type="submit"> | ||||
| 			</form> | ||||
| 			<br> | ||||
| 		`) | ||||
| //line views/readers.qtpl:95 | ||||
| 		} | ||||
| //line views/readers.qtpl:95 | ||||
| 		qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/readers.qtpl:96 | ||||
| 	} else { | ||||
| //line views/readers.qtpl:96 | ||||
| 		qw422016.N().S(` | ||||
| 			`) | ||||
| //line views/readers.qtpl:97 | ||||
| 		qw422016.N().S(contents) | ||||
| //line views/readers.qtpl:97 | ||||
| 		qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/readers.qtpl:98 | ||||
| 	} | ||||
| //line views/readers.qtpl:98 | ||||
| 	qw422016.N().S(` | ||||
| 	</article> | ||||
| 	<section class="prevnext"> | ||||
| 		`) | ||||
| //line views/readers.qtpl:101 | ||||
| 	if prevHyphaName != "" { | ||||
| //line views/readers.qtpl:101 | ||||
| 		qw422016.N().S(` | ||||
| 		<a class="prevnext__el prevnext__prev" href="/hypha/`) | ||||
| //line views/readers.qtpl:102 | ||||
| 		qw422016.E().S(prevHyphaName) | ||||
| //line views/readers.qtpl:102 | ||||
| 		qw422016.N().S(`" rel="prev">← `) | ||||
| //line views/readers.qtpl:102 | ||||
| 		qw422016.E().S(util.BeautifulName(path.Base(prevHyphaName))) | ||||
| //line views/readers.qtpl:102 | ||||
| 		qw422016.N().S(`</a> | ||||
| 		`) | ||||
| //line views/readers.qtpl:103 | ||||
| 	} | ||||
| //line views/readers.qtpl:103 | ||||
| 	qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/readers.qtpl:104 | ||||
| 	if nextHyphaName != "" { | ||||
| //line views/readers.qtpl:104 | ||||
| 		qw422016.N().S(` | ||||
| 		<a class="prevnext__el prevnext__next" href="/hypha/`) | ||||
| //line views/readers.qtpl:105 | ||||
| 		qw422016.E().S(nextHyphaName) | ||||
| //line views/readers.qtpl:105 | ||||
| 		qw422016.N().S(`" rel="next">`) | ||||
| //line views/readers.qtpl:105 | ||||
| 		qw422016.E().S(util.BeautifulName(path.Base(nextHyphaName))) | ||||
| //line views/readers.qtpl:105 | ||||
| 		qw422016.N().S(` →</a> | ||||
| 		`) | ||||
| //line views/readers.qtpl:106 | ||||
| 	} | ||||
| //line views/readers.qtpl:106 | ||||
| 	qw422016.N().S(` | ||||
| 	</section> | ||||
| `) | ||||
| //line views/readers.qtpl:108 | ||||
| 	StreamSubhyphaeHTML(qw422016, subhyphae) | ||||
| //line views/readers.qtpl:108 | ||||
| 	qw422016.N().S(` | ||||
| </main> | ||||
| `) | ||||
| //line views/readers.qtpl:110 | ||||
| 	StreamRelativeHyphaeHTML(qw422016, relatives) | ||||
| //line views/readers.qtpl:110 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/readers.qtpl:111 | ||||
| 	StreamBackLinksHTML(qw422016, h) | ||||
| //line views/readers.qtpl:111 | ||||
| 	qw422016.N().S(` | ||||
| </div> | ||||
| `) | ||||
| //line views/readers.qtpl:113 | ||||
| } | ||||
|  | ||||
| //line views/readers.qtpl:113 | ||||
| func WriteHyphaHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) { | ||||
| //line views/readers.qtpl:113 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/readers.qtpl:113 | ||||
| 	StreamHyphaHTML(qw422016, rq, h, contents) | ||||
| //line views/readers.qtpl:113 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/readers.qtpl:113 | ||||
| } | ||||
|  | ||||
| //line views/readers.qtpl:113 | ||||
| func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) string { | ||||
| //line views/readers.qtpl:113 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/readers.qtpl:113 | ||||
| 	WriteHyphaHTML(qb422016, rq, h, contents) | ||||
| //line views/readers.qtpl:113 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/readers.qtpl:113 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/readers.qtpl:113 | ||||
| 	return qs422016 | ||||
| //line views/readers.qtpl:113 | ||||
| } | ||||
|  | ||||
| //line views/readers.qtpl:115 | ||||
| func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) { | ||||
| //line views/readers.qtpl:115 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/readers.qtpl:117 | ||||
| 	relatives, subhyphae, _, _ := tree.Tree(h.Name) | ||||
|  | ||||
| //line views/readers.qtpl:118 | ||||
| 	qw422016.N().S(` | ||||
| `) | ||||
| //line views/readers.qtpl:119 | ||||
| 	StreamNavHTML(qw422016, rq, h.Name, "revision", revHash) | ||||
| //line views/readers.qtpl:119 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<article> | ||||
| 		<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p> | ||||
| 		`) | ||||
| //line views/readers.qtpl:124 | ||||
| 	qw422016.N().S(NaviTitleHTML(h)) | ||||
| //line views/readers.qtpl:124 | ||||
| 	qw422016.N().S(` | ||||
| 		`) | ||||
| //line views/readers.qtpl:125 | ||||
| 	qw422016.N().S(contents) | ||||
| //line views/readers.qtpl:125 | ||||
| 	qw422016.N().S(` | ||||
| 	</article> | ||||
| `) | ||||
| //line views/readers.qtpl:127 | ||||
| 	StreamSubhyphaeHTML(qw422016, subhyphae) | ||||
| //line views/readers.qtpl:127 | ||||
| 	qw422016.N().S(` | ||||
| </main> | ||||
| `) | ||||
| //line views/readers.qtpl:129 | ||||
| 	StreamRelativeHyphaeHTML(qw422016, relatives) | ||||
| //line views/readers.qtpl:129 | ||||
| 	qw422016.N().S(` | ||||
| </div> | ||||
| `) | ||||
| //line views/readers.qtpl:131 | ||||
| } | ||||
|  | ||||
| //line views/readers.qtpl:131 | ||||
| func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) { | ||||
| //line views/readers.qtpl:131 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/readers.qtpl:131 | ||||
| 	StreamRevisionHTML(qw422016, rq, h, contents, revHash) | ||||
| //line views/readers.qtpl:131 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/readers.qtpl:131 | ||||
| } | ||||
|  | ||||
| //line views/readers.qtpl:131 | ||||
| func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) string { | ||||
| //line views/readers.qtpl:131 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/readers.qtpl:131 | ||||
| 	WriteRevisionHTML(qb422016, rq, h, contents, revHash) | ||||
| //line views/readers.qtpl:131 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/readers.qtpl:131 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/readers.qtpl:131 | ||||
| 	return qs422016 | ||||
| //line views/readers.qtpl:131 | ||||
| } | ||||
							
								
								
									
										147
									
								
								views/stuff.qtpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,147 @@ | ||||
| {% import "path/filepath" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/hyphae" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/user" %} | ||||
| {% import "github.com/bouncepaw/mycorrhiza/util" %} | ||||
|  | ||||
| {% func BaseHTML(title, body string, u *user.User, headElements ...string) %} | ||||
| <!doctype html> | ||||
| <html> | ||||
| 	<head> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 		<meta charset="utf-8"> | ||||
| 		<link rel="stylesheet" type="text/css" href="/static/common.css"> | ||||
| 		<title>{%s title %}</title> | ||||
| 		{% for _, el := range headElements %}{%s= el %}{% endfor %} | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<header> | ||||
| 			<nav class="header-links main-width"> | ||||
| 				<ul class="header-links__list"> | ||||
| 					{%- for _, link := range util.HeaderLinks -%} | ||||
| 					<li class="header-links__entry"><a class="header-links__link" href="{%s link.Href %}">{%s link.Display %}</a></li> | ||||
| 					{%- endfor -%} | ||||
| 					{%s= UserMenuHTML(u) %} | ||||
| 				</ul> | ||||
| 			</nav> | ||||
| 		</header> | ||||
| 		{%s= body %} | ||||
| 	</body> | ||||
| </html> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func UserListHTML() %} | ||||
| <div class="layout"> | ||||
| <main class="main-width user-list"> | ||||
| 	<h1>List of users</h1> | ||||
| {% code | ||||
| var ( | ||||
| 	admins = make([]string, 0) | ||||
| 	moderators = make([]string, 0) | ||||
| 	editors = make([]string, 0) | ||||
| ) | ||||
| for u := range user.YieldUsers() { | ||||
| 	switch u.Group { | ||||
| 	case "admin": | ||||
| 		admins = append(admins, u.Name) | ||||
| 	case "moderator": | ||||
| 		moderators = append(moderators, u.Name) | ||||
| 	case "editor", "trusted": | ||||
| 		editors = append(editors, u.Name) | ||||
| 	} | ||||
| } | ||||
| %} | ||||
| 	<section> | ||||
| 		<h2>Admins</h2> | ||||
| 		<ol>{% for _, name := range admins %} | ||||
| 			<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li> | ||||
| 		{% endfor %}</ol> | ||||
| 	</section> | ||||
| 	<section> | ||||
| 		<h2>Moderators</h2> | ||||
| 		<ol>{% for _, name := range moderators %} | ||||
| 			<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li> | ||||
| 		{% endfor %}</ol> | ||||
| 	</section> | ||||
| 	<section> | ||||
| 		<h2>Editors</h2> | ||||
| 		<ol>{% for _, name := range editors %} | ||||
| 			<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li> | ||||
| 		{% endfor %}</ol> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func HyphaListHTML() %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<h1>List of hyphae</h1> | ||||
| 	<p>This wiki has {%d hyphae.Count() %} hyphae.</p> | ||||
| 	<ul class="hypha-list"> | ||||
| 		{% for h := range hyphae.YieldExistingHyphae() %} | ||||
| 		<li class="hypha-list__entry"> | ||||
| 			<a class="hypha-list__link" href="/hypha/{%s h.Name %}">{%s util.BeautifulName(h.Name) %}</a> | ||||
| 			{% if h.BinaryPath != "" %} | ||||
| 			<span class="hypha-list__amnt-type">{%s filepath.Ext(h.BinaryPath)[1:] %}</span> | ||||
| 			{% endif %} | ||||
| 		</li> | ||||
| 		{% endfor %} | ||||
| 	</ul> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func AboutHTML() %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 		<h1>About {%s util.SiteName %}</h1> | ||||
| 		<ul> | ||||
| 			<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.13</li> | ||||
| 		{%- if user.AuthUsed -%} | ||||
| 			<li><b>User count:</b> {%d user.Count() %}</li> | ||||
| 			<li><b>Home page:</b> <a href="/">{%s util.HomePage %}</a></li> | ||||
| 			<li><b>Administrators:</b> {%- for i, username := range user.ListUsersWithGroup("admin") -%} | ||||
| 				{%- if i > 0 -%}<span aria-hidden="true">, </span> | ||||
| 				{%- endif -%} | ||||
| 				<a href="/page/{%s util.UserHypha %}/{%s username %}">{%s username %}</a>{%- endfor -%}</li> | ||||
| 		{%- else -%} | ||||
| 			<li>This wiki does not use authorization</li> | ||||
| 		{%- endif -%} | ||||
| 		</ul> | ||||
| 		<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
|  | ||||
| {% func AdminPanelHTML() %} | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<h1>Admininstrative functions</h1> | ||||
| 	<section> | ||||
| 		<h2>Safe things</h2> | ||||
| 		<ul> | ||||
| 			<li><a href="/about">About this wiki<a></li> | ||||
| 			<li><a href="/user-list">User list</a></li> | ||||
| 			<li><a href="/update-header-links">Update header links</a></li> | ||||
| 		</ul> | ||||
| 	</section> | ||||
| 	<section> | ||||
| 		<h2>Dangerous things</h2> | ||||
| 		<form action="/admin/shutdown" method="POST" style="float:left"> | ||||
| 			<fieldset> | ||||
| 				<legend>Shutdown wiki</legend> | ||||
| 				<input type="submit"> | ||||
| 			</fieldset> | ||||
| 		</form> | ||||
| 		<form action="/reindex" method="GET" style="float:left"> | ||||
| 			<fieldset> | ||||
| 				<legend>Reindex hyphae</legend> | ||||
| 				<input type="submit"> | ||||
| 			</fieldset> | ||||
| 		</form> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| {% endfunc %} | ||||
							
								
								
									
										502
									
								
								views/stuff.qtpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,502 @@ | ||||
| // Code generated by qtc from "stuff.qtpl". DO NOT EDIT. | ||||
| // See https://github.com/valyala/quicktemplate for details. | ||||
|  | ||||
| //line views/stuff.qtpl:1 | ||||
| package views | ||||
|  | ||||
| //line views/stuff.qtpl:1 | ||||
| import "path/filepath" | ||||
|  | ||||
| //line views/stuff.qtpl:2 | ||||
| import "github.com/bouncepaw/mycorrhiza/hyphae" | ||||
|  | ||||
| //line views/stuff.qtpl:3 | ||||
| import "github.com/bouncepaw/mycorrhiza/user" | ||||
|  | ||||
| //line views/stuff.qtpl:4 | ||||
| import "github.com/bouncepaw/mycorrhiza/util" | ||||
|  | ||||
| //line views/stuff.qtpl:6 | ||||
| import ( | ||||
| 	qtio422016 "io" | ||||
|  | ||||
| 	qt422016 "github.com/valyala/quicktemplate" | ||||
| ) | ||||
|  | ||||
| //line views/stuff.qtpl:6 | ||||
| var ( | ||||
| 	_ = qtio422016.Copy | ||||
| 	_ = qt422016.AcquireByteBuffer | ||||
| ) | ||||
|  | ||||
| //line views/stuff.qtpl:6 | ||||
| func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) { | ||||
| //line views/stuff.qtpl:6 | ||||
| 	qw422016.N().S(` | ||||
| <!doctype html> | ||||
| <html> | ||||
| 	<head> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 		<meta charset="utf-8"> | ||||
| 		<link rel="stylesheet" type="text/css" href="/static/common.css"> | ||||
| 		<title>`) | ||||
| //line views/stuff.qtpl:13 | ||||
| 	qw422016.E().S(title) | ||||
| //line views/stuff.qtpl:13 | ||||
| 	qw422016.N().S(`</title> | ||||
| 		`) | ||||
| //line views/stuff.qtpl:14 | ||||
| 	for _, el := range headElements { | ||||
| //line views/stuff.qtpl:14 | ||||
| 		qw422016.N().S(el) | ||||
| //line views/stuff.qtpl:14 | ||||
| 	} | ||||
| //line views/stuff.qtpl:14 | ||||
| 	qw422016.N().S(` | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<header> | ||||
| 			<nav class="header-links main-width"> | ||||
| 				<ul class="header-links__list"> | ||||
| `) | ||||
| //line views/stuff.qtpl:20 | ||||
| 	for _, link := range util.HeaderLinks { | ||||
| //line views/stuff.qtpl:20 | ||||
| 		qw422016.N().S(`					<li class="header-links__entry"><a class="header-links__link" href="`) | ||||
| //line views/stuff.qtpl:21 | ||||
| 		qw422016.E().S(link.Href) | ||||
| //line views/stuff.qtpl:21 | ||||
| 		qw422016.N().S(`">`) | ||||
| //line views/stuff.qtpl:21 | ||||
| 		qw422016.E().S(link.Display) | ||||
| //line views/stuff.qtpl:21 | ||||
| 		qw422016.N().S(`</a></li> | ||||
| `) | ||||
| //line views/stuff.qtpl:22 | ||||
| 	} | ||||
| //line views/stuff.qtpl:22 | ||||
| 	qw422016.N().S(`					`) | ||||
| //line views/stuff.qtpl:23 | ||||
| 	qw422016.N().S(UserMenuHTML(u)) | ||||
| //line views/stuff.qtpl:23 | ||||
| 	qw422016.N().S(` | ||||
| 				</ul> | ||||
| 			</nav> | ||||
| 		</header> | ||||
| 		`) | ||||
| //line views/stuff.qtpl:27 | ||||
| 	qw422016.N().S(body) | ||||
| //line views/stuff.qtpl:27 | ||||
| 	qw422016.N().S(` | ||||
| 	</body> | ||||
| </html> | ||||
| `) | ||||
| //line views/stuff.qtpl:30 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:30 | ||||
| func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) { | ||||
| //line views/stuff.qtpl:30 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/stuff.qtpl:30 | ||||
| 	StreamBaseHTML(qw422016, title, body, u, headElements...) | ||||
| //line views/stuff.qtpl:30 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/stuff.qtpl:30 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:30 | ||||
| func BaseHTML(title, body string, u *user.User, headElements ...string) string { | ||||
| //line views/stuff.qtpl:30 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/stuff.qtpl:30 | ||||
| 	WriteBaseHTML(qb422016, title, body, u, headElements...) | ||||
| //line views/stuff.qtpl:30 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/stuff.qtpl:30 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/stuff.qtpl:30 | ||||
| 	return qs422016 | ||||
| //line views/stuff.qtpl:30 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:32 | ||||
| func StreamUserListHTML(qw422016 *qt422016.Writer) { | ||||
| //line views/stuff.qtpl:32 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width user-list"> | ||||
| 	<h1>List of users</h1> | ||||
| `) | ||||
| //line views/stuff.qtpl:37 | ||||
| 	var ( | ||||
| 		admins     = make([]string, 0) | ||||
| 		moderators = make([]string, 0) | ||||
| 		editors    = make([]string, 0) | ||||
| 	) | ||||
| 	for u := range user.YieldUsers() { | ||||
| 		switch u.Group { | ||||
| 		case "admin": | ||||
| 			admins = append(admins, u.Name) | ||||
| 		case "moderator": | ||||
| 			moderators = append(moderators, u.Name) | ||||
| 		case "editor", "trusted": | ||||
| 			editors = append(editors, u.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| //line views/stuff.qtpl:52 | ||||
| 	qw422016.N().S(` | ||||
| 	<section> | ||||
| 		<h2>Admins</h2> | ||||
| 		<ol>`) | ||||
| //line views/stuff.qtpl:55 | ||||
| 	for _, name := range admins { | ||||
| //line views/stuff.qtpl:55 | ||||
| 		qw422016.N().S(` | ||||
| 			<li><a href="/page/`) | ||||
| //line views/stuff.qtpl:56 | ||||
| 		qw422016.E().S(util.UserHypha) | ||||
| //line views/stuff.qtpl:56 | ||||
| 		qw422016.N().S(`/`) | ||||
| //line views/stuff.qtpl:56 | ||||
| 		qw422016.E().S(name) | ||||
| //line views/stuff.qtpl:56 | ||||
| 		qw422016.N().S(`">`) | ||||
| //line views/stuff.qtpl:56 | ||||
| 		qw422016.E().S(name) | ||||
| //line views/stuff.qtpl:56 | ||||
| 		qw422016.N().S(`</a></li> | ||||
| 		`) | ||||
| //line views/stuff.qtpl:57 | ||||
| 	} | ||||
| //line views/stuff.qtpl:57 | ||||
| 	qw422016.N().S(`</ol> | ||||
| 	</section> | ||||
| 	<section> | ||||
| 		<h2>Moderators</h2> | ||||
| 		<ol>`) | ||||
| //line views/stuff.qtpl:61 | ||||
| 	for _, name := range moderators { | ||||
| //line views/stuff.qtpl:61 | ||||
| 		qw422016.N().S(` | ||||
| 			<li><a href="/page/`) | ||||
| //line views/stuff.qtpl:62 | ||||
| 		qw422016.E().S(util.UserHypha) | ||||
| //line views/stuff.qtpl:62 | ||||
| 		qw422016.N().S(`/`) | ||||
| //line views/stuff.qtpl:62 | ||||
| 		qw422016.E().S(name) | ||||
| //line views/stuff.qtpl:62 | ||||
| 		qw422016.N().S(`">`) | ||||
| //line views/stuff.qtpl:62 | ||||
| 		qw422016.E().S(name) | ||||
| //line views/stuff.qtpl:62 | ||||
| 		qw422016.N().S(`</a></li> | ||||
| 		`) | ||||
| //line views/stuff.qtpl:63 | ||||
| 	} | ||||
| //line views/stuff.qtpl:63 | ||||
| 	qw422016.N().S(`</ol> | ||||
| 	</section> | ||||
| 	<section> | ||||
| 		<h2>Editors</h2> | ||||
| 		<ol>`) | ||||
| //line views/stuff.qtpl:67 | ||||
| 	for _, name := range editors { | ||||
| //line views/stuff.qtpl:67 | ||||
| 		qw422016.N().S(` | ||||
| 			<li><a href="/page/`) | ||||
| //line views/stuff.qtpl:68 | ||||
| 		qw422016.E().S(util.UserHypha) | ||||
| //line views/stuff.qtpl:68 | ||||
| 		qw422016.N().S(`/`) | ||||
| //line views/stuff.qtpl:68 | ||||
| 		qw422016.E().S(name) | ||||
| //line views/stuff.qtpl:68 | ||||
| 		qw422016.N().S(`">`) | ||||
| //line views/stuff.qtpl:68 | ||||
| 		qw422016.E().S(name) | ||||
| //line views/stuff.qtpl:68 | ||||
| 		qw422016.N().S(`</a></li> | ||||
| 		`) | ||||
| //line views/stuff.qtpl:69 | ||||
| 	} | ||||
| //line views/stuff.qtpl:69 | ||||
| 	qw422016.N().S(`</ol> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/stuff.qtpl:73 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:73 | ||||
| func WriteUserListHTML(qq422016 qtio422016.Writer) { | ||||
| //line views/stuff.qtpl:73 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/stuff.qtpl:73 | ||||
| 	StreamUserListHTML(qw422016) | ||||
| //line views/stuff.qtpl:73 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/stuff.qtpl:73 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:73 | ||||
| func UserListHTML() string { | ||||
| //line views/stuff.qtpl:73 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/stuff.qtpl:73 | ||||
| 	WriteUserListHTML(qb422016) | ||||
| //line views/stuff.qtpl:73 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/stuff.qtpl:73 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/stuff.qtpl:73 | ||||
| 	return qs422016 | ||||
| //line views/stuff.qtpl:73 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:75 | ||||
| func StreamHyphaListHTML(qw422016 *qt422016.Writer) { | ||||
| //line views/stuff.qtpl:75 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<h1>List of hyphae</h1> | ||||
| 	<p>This wiki has `) | ||||
| //line views/stuff.qtpl:79 | ||||
| 	qw422016.N().D(hyphae.Count()) | ||||
| //line views/stuff.qtpl:79 | ||||
| 	qw422016.N().S(` hyphae.</p> | ||||
| 	<ul class="hypha-list"> | ||||
| 		`) | ||||
| //line views/stuff.qtpl:81 | ||||
| 	for h := range hyphae.YieldExistingHyphae() { | ||||
| //line views/stuff.qtpl:81 | ||||
| 		qw422016.N().S(` | ||||
| 		<li class="hypha-list__entry"> | ||||
| 			<a class="hypha-list__link" href="/hypha/`) | ||||
| //line views/stuff.qtpl:83 | ||||
| 		qw422016.E().S(h.Name) | ||||
| //line views/stuff.qtpl:83 | ||||
| 		qw422016.N().S(`">`) | ||||
| //line views/stuff.qtpl:83 | ||||
| 		qw422016.E().S(util.BeautifulName(h.Name)) | ||||
| //line views/stuff.qtpl:83 | ||||
| 		qw422016.N().S(`</a> | ||||
| 			`) | ||||
| //line views/stuff.qtpl:84 | ||||
| 		if h.BinaryPath != "" { | ||||
| //line views/stuff.qtpl:84 | ||||
| 			qw422016.N().S(` | ||||
| 			<span class="hypha-list__amnt-type">`) | ||||
| //line views/stuff.qtpl:85 | ||||
| 			qw422016.E().S(filepath.Ext(h.BinaryPath)[1:]) | ||||
| //line views/stuff.qtpl:85 | ||||
| 			qw422016.N().S(`</span> | ||||
| 			`) | ||||
| //line views/stuff.qtpl:86 | ||||
| 		} | ||||
| //line views/stuff.qtpl:86 | ||||
| 		qw422016.N().S(` | ||||
| 		</li> | ||||
| 		`) | ||||
| //line views/stuff.qtpl:88 | ||||
| 	} | ||||
| //line views/stuff.qtpl:88 | ||||
| 	qw422016.N().S(` | ||||
| 	</ul> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/stuff.qtpl:92 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:92 | ||||
| func WriteHyphaListHTML(qq422016 qtio422016.Writer) { | ||||
| //line views/stuff.qtpl:92 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/stuff.qtpl:92 | ||||
| 	StreamHyphaListHTML(qw422016) | ||||
| //line views/stuff.qtpl:92 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/stuff.qtpl:92 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:92 | ||||
| func HyphaListHTML() string { | ||||
| //line views/stuff.qtpl:92 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/stuff.qtpl:92 | ||||
| 	WriteHyphaListHTML(qb422016) | ||||
| //line views/stuff.qtpl:92 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/stuff.qtpl:92 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/stuff.qtpl:92 | ||||
| 	return qs422016 | ||||
| //line views/stuff.qtpl:92 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:94 | ||||
| func StreamAboutHTML(qw422016 *qt422016.Writer) { | ||||
| //line views/stuff.qtpl:94 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<section> | ||||
| 		<h1>About `) | ||||
| //line views/stuff.qtpl:98 | ||||
| 	qw422016.E().S(util.SiteName) | ||||
| //line views/stuff.qtpl:98 | ||||
| 	qw422016.N().S(`</h1> | ||||
| 		<ul> | ||||
| 			<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.13 indev</li> | ||||
| `) | ||||
| //line views/stuff.qtpl:101 | ||||
| 	if user.AuthUsed { | ||||
| //line views/stuff.qtpl:101 | ||||
| 		qw422016.N().S(`			<li><b>User count:</b> `) | ||||
| //line views/stuff.qtpl:102 | ||||
| 		qw422016.N().D(user.Count()) | ||||
| //line views/stuff.qtpl:102 | ||||
| 		qw422016.N().S(`</li> | ||||
| 			<li><b>Home page:</b> <a href="/">`) | ||||
| //line views/stuff.qtpl:103 | ||||
| 		qw422016.E().S(util.HomePage) | ||||
| //line views/stuff.qtpl:103 | ||||
| 		qw422016.N().S(`</a></li> | ||||
| 			<li><b>Administrators:</b>`) | ||||
| //line views/stuff.qtpl:104 | ||||
| 		for i, username := range user.ListUsersWithGroup("admin") { | ||||
| //line views/stuff.qtpl:105 | ||||
| 			if i > 0 { | ||||
| //line views/stuff.qtpl:105 | ||||
| 				qw422016.N().S(`<span aria-hidden="true">, </span> | ||||
| `) | ||||
| //line views/stuff.qtpl:106 | ||||
| 			} | ||||
| //line views/stuff.qtpl:106 | ||||
| 			qw422016.N().S(`				<a href="/page/`) | ||||
| //line views/stuff.qtpl:107 | ||||
| 			qw422016.E().S(util.UserHypha) | ||||
| //line views/stuff.qtpl:107 | ||||
| 			qw422016.N().S(`/`) | ||||
| //line views/stuff.qtpl:107 | ||||
| 			qw422016.E().S(username) | ||||
| //line views/stuff.qtpl:107 | ||||
| 			qw422016.N().S(`">`) | ||||
| //line views/stuff.qtpl:107 | ||||
| 			qw422016.E().S(username) | ||||
| //line views/stuff.qtpl:107 | ||||
| 			qw422016.N().S(`</a>`) | ||||
| //line views/stuff.qtpl:107 | ||||
| 		} | ||||
| //line views/stuff.qtpl:107 | ||||
| 		qw422016.N().S(`</li> | ||||
| `) | ||||
| //line views/stuff.qtpl:108 | ||||
| 	} else { | ||||
| //line views/stuff.qtpl:108 | ||||
| 		qw422016.N().S(`			<li>This wiki does not use authorization</li> | ||||
| `) | ||||
| //line views/stuff.qtpl:110 | ||||
| 	} | ||||
| //line views/stuff.qtpl:110 | ||||
| 	qw422016.N().S(`		</ul> | ||||
| 		<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/stuff.qtpl:116 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:116 | ||||
| func WriteAboutHTML(qq422016 qtio422016.Writer) { | ||||
| //line views/stuff.qtpl:116 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/stuff.qtpl:116 | ||||
| 	StreamAboutHTML(qw422016) | ||||
| //line views/stuff.qtpl:116 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/stuff.qtpl:116 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:116 | ||||
| func AboutHTML() string { | ||||
| //line views/stuff.qtpl:116 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/stuff.qtpl:116 | ||||
| 	WriteAboutHTML(qb422016) | ||||
| //line views/stuff.qtpl:116 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/stuff.qtpl:116 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/stuff.qtpl:116 | ||||
| 	return qs422016 | ||||
| //line views/stuff.qtpl:116 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:118 | ||||
| func StreamAdminPanelHTML(qw422016 *qt422016.Writer) { | ||||
| //line views/stuff.qtpl:118 | ||||
| 	qw422016.N().S(` | ||||
| <div class="layout"> | ||||
| <main class="main-width"> | ||||
| 	<h1>Admininstrative functions</h1> | ||||
| 	<section> | ||||
| 		<h2>Safe things</h2> | ||||
| 		<ul> | ||||
| 			<li><a href="/about">About this wiki<a></li> | ||||
| 			<li><a href="/user-list">User list</a></li> | ||||
| 			<li><a href="/update-header-links">Update header links</a></li> | ||||
| 		</ul> | ||||
| 	</section> | ||||
| 	<section> | ||||
| 		<h2>Dangerous things</h2> | ||||
| 		<form action="/admin/shutdown" method="POST" style="float:left"> | ||||
| 			<fieldset> | ||||
| 				<legend>Shutdown wiki</legend> | ||||
| 				<input type="submit"> | ||||
| 			</fieldset> | ||||
| 		</form> | ||||
| 		<form action="/reindex" method="GET" style="float:left"> | ||||
| 			<fieldset> | ||||
| 				<legend>Reindex hyphae</legend> | ||||
| 				<input type="submit"> | ||||
| 			</fieldset> | ||||
| 		</form> | ||||
| 	</section> | ||||
| </main> | ||||
| </div> | ||||
| `) | ||||
| //line views/stuff.qtpl:147 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:147 | ||||
| func WriteAdminPanelHTML(qq422016 qtio422016.Writer) { | ||||
| //line views/stuff.qtpl:147 | ||||
| 	qw422016 := qt422016.AcquireWriter(qq422016) | ||||
| //line views/stuff.qtpl:147 | ||||
| 	StreamAdminPanelHTML(qw422016) | ||||
| //line views/stuff.qtpl:147 | ||||
| 	qt422016.ReleaseWriter(qw422016) | ||||
| //line views/stuff.qtpl:147 | ||||
| } | ||||
|  | ||||
| //line views/stuff.qtpl:147 | ||||
| func AdminPanelHTML() string { | ||||
| //line views/stuff.qtpl:147 | ||||
| 	qb422016 := qt422016.AcquireByteBuffer() | ||||
| //line views/stuff.qtpl:147 | ||||
| 	WriteAdminPanelHTML(qb422016) | ||||
| //line views/stuff.qtpl:147 | ||||
| 	qs422016 := string(qb422016.B) | ||||
| //line views/stuff.qtpl:147 | ||||
| 	qt422016.ReleaseByteBuffer(qb422016) | ||||
| //line views/stuff.qtpl:147 | ||||
| 	return qs422016 | ||||
| //line views/stuff.qtpl:147 | ||||
| } | ||||
 Timur Ismagilov
					Timur Ismagilov